SOLVED

spfx React > REST API > Map search results

Iron Contributor

I have a woking example where I map (rest) search results to an object like this:

 

export interface IReactGetItemsState{  
  items:[  
        {  
          "EmployeeName": "",  
          "EmployeeId": "",  
          "Experience":"",  
          "Location":""
        }]  
}  

export default class ReactGetItems extends React.Component<IReactGetItemsProps, IReactGetItemsState> {
  
    public constructor(props: IReactGetItemsProps, state: IReactGetItemsState){  
      super(props);  
      this.state = {  
        items: [  
          {  
            "EmployeeName": "",  
            "EmployeeId": "",  
            "Experience":"",  
            "Location":""
          }  
        ]  
      };  
    }  
-----------------------------------
jquery.ajax({
       url: `${this.props.siteurl}/_api/web/lists/getbytitle('EmployeeList')/items`,
       type: "GET",
       headers:{'Accept': 'application/json; odata=verbose;'},
       success: function(resultData) {
       reactHandler.setState({
---->       items: resultData.d.results

Now I want to do my own variant using an Ajax call using a querystring. So my result object is different

I try to map the results in this way:

 

items: resultData.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results.Cells

This is not working. My results remain empty.

 

I'm just beginning SPFX/React so I do not know how to solve this.

Can someone help me out?

 

Thanks, Mike

34 Replies

An example...

 

var reactModule = React.createClass({
getInitialState:function(){

},
render: function() {
return (
<div>
... content here
</div>
);
},
componentDidMount: function() {
var ajaxSuccess=this.ajaxSuccess;

$.ajax({
type: "POST",
url:`${this.props.siteurl}/_api/web/lists/getbytitle('EmployeeList')/items`,
more props here,
success: ajaxSuccess
});
},
ajaxSuccess:function(e){
//e is the result. update state here.
}
});

Thanks again @Maggan Wåhlin.

I'll give it a try.

Read up on the react component lifecycle, it helped me a lot.

http://busypeoples.github.io/post/react-component-lifecycle/

Things are a bit more clear but my main issue is the mapping of my query results to the "Item" object.

I try:

items: resultData.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results.Cells

But I need to solve it some other way. Still do not know howto.... sorry.

 

An example would be great....

Can someone help me out on this? Still not able to solve it.

 

Thanks, Mike

Hi Mike,
Could you post your code? Meanwhile, you could try getbyid(<listguid>) instead of the list title in the REST query. It's a long shot... I always use Postman to test my queries, it's a Chrome app.

Hi @Maggan Wåhlin (I should buy you a beer for your help, already).

 

The complete code is this:
import * as React from 'react';
import styles from './ReactGetItems.module.scss';
import { IReactGetItemsProps } from './IReactGetItemsProps';
import { escape } from '@microsoft/sp-lodash-subset';
import * as jquery from 'jquery';

export interface IReactGetItemsState{  
  items:[  
        {  
          "RefinableString10": "",  
          "CreatedBy": "",  
          "Created":""  
  
        }]  
}  

export default class ReactGetItems extends React.Component<IReactGetItemsProps, IReactGetItemsState> {
      public constructor(props: IReactGetItemsProps, state: IReactGetItemsState){  
      super(props);  
      this.state = {  
        items: [  
          {  
            "RefinableString10": "",  
            "CreatedBy": "",  
            "Created":""  
         
          }  
        ]  
      };  
    }  

 

    public componentDidMount(){
      var reactHandler = this;
      jquery.ajax({
        url: "https://Blabla.sharepoint.com/sites/bla/_api/search/query?querytext='ContentType:Test_matters'&selectproperties='RefinableString10%2cCreatedBy%2cCreated'&rowlimit=500'",
        type: "GET",
        headers:{'Accept': 'application/json;  odata=verbose;'},                     
        success: function(resultData) {
 
        reactHandler.setState({
          items: resultData.PrimaryQueryResult.RelevantResults.Table.Rows
        });
       },
       error : function(jqXHR, textStatus, errorThrown) {
       }
       });
       }

       public render(): React.ReactElement<IReactGetItemsProps> {
        return (  
   
           <div className={styles.panelStyle} > 
             <br></br>
      
             <br></br> 
             <div className={styles.tableCaptionStyle} > Demo : Retrieve SharePoint List Items using SPFx , REST API  & React JS  </div>
             <br></br>
              <div className={styles.headerCaptionStyle} > Employee Details</div>
             <div className={styles.tableStyle} >   
               
               <div className={styles.headerStyle} >  
                 <div className={styles.CellStyle}>Employee Name</div>  
                 <div className={styles.CellStyle}>Employee Id </div>  
                 <div className={styles.CellStyle}>Experience</div>  

               </div>  
               
                 
                  {this.state.items.map(function(item,key){ 
                   
                   return (<div className={styles.rowStyle} key={key}>  
                       <div className={styles.CellStyle}>{item.RefinableString10}</div>  
                       <div className={styles.CellStyle}>{item.CreatedBy}</div>  
                        <div className={styles.CellStyle}>{item.Created}</div>
      
                     </div>);  
                 })}  
                       
             </div>  
           </div>  
   
   
       );  
     }  
     
   }
   
The line: "items: resultData.PrimaryQueryResult.RelevantResults.Table.Rows" is the problem.
My Json return is not formatted good enough to do the binding like this.
 
For example, if I need the value of "RefinableString10" I have to dig deep into the json to get it:
"resultData.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results[i].Cells.results[2].Value"
 
 
 
My query is fine. In debug I see the expected results in "resultData" 
All I did was change the query to get some other results.
I tried a similar query in Postman and now I see what you mean... To get a "clean" JSON, remove odata=verbose from the accept header. It will remove all the metadata you don't need :smiling_face_with_smiling_eyes:

https://blogs.office.com/en-us/2014/08/13/json-light-support-rest-sharepoint-api-released/?eu=true

Cheers :smiling_face_with_smiling_eyes:

So now I did this:

items: resultData.PrimaryQueryResult.RelevantResults.Table.Rows

No errors and I get the number of rows I expect but..... the rows are empty. 

 

<div className={styles.CellStyle}>{item.RefinableString10}</div>  

"item.RefinableString10" does not seem to have any value....

 

Do you get values för Created and Created by?

Nope.

Everything is empty

I am a bit confused... when you debug, do you get the data? If the data is empty after running the query, the query must be the problem. But if you get data from the query and you can't get react to keep state, that's another story...

I really recommend Postman to test REST queries. You will save tons of time! Use Postman Interceptor to authenticate against Sharepoint.

1. Download Postman: https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop.
2. Download Postman Interceptor:
https://chrome.google.com/webstore/detail/postman-interceptor/aicmkgpgakddgnaphhhpliifpcfhicfo.
3. Turn Interceptor on in the Postman top menubar.
4. Add the query and the Accept header.
5. Run the query.




Thanks again,

I'll dive into this today and will post my results. I hope I can clarify my issue.

 

 

I did some testing. The query (from my working example gives the following xml (in postman):

<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="https://blabla.sharepoint.com/sites/bla/_api/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
    <id>98c9b15e-6fc6-4a6e-b44b-8b8fe0b7d210</id>
    <title />
    <updated>2017-10-23T07:48:54Z</updated>
    <entry m:etag="&quot;1&quot;">
        <id>a393667a-4c26-4832-bc85-9c26bf682b75</id>
        <category term="SP.Data.EmployeeListListItem" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
        <link rel="edit" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/FirstUniqueAncestorSecurableObject" type="application/atom+xml;type=entry" title="FirstUniqueAncestorSecurableObject" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/FirstUniqueAncestorSecurableObject" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/RoleAssignments" type="application/atom+xml;type=feed" title="RoleAssignments" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/RoleAssignments" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Activities" type="application/atom+xml;type=feed" title="Activities" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/Activities" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/AttachmentFiles" type="application/atom+xml;type=feed" title="AttachmentFiles" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/AttachmentFiles" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ContentType" type="application/atom+xml;type=entry" title="ContentType" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/ContentType" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/GetDlpPolicyTip" type="application/atom+xml;type=entry" title="GetDlpPolicyTip" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/GetDlpPolicyTip" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/FieldValuesAsHtml" type="application/atom+xml;type=entry" title="FieldValuesAsHtml" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/FieldValuesAsHtml" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/FieldValuesAsText" type="application/atom+xml;type=entry" title="FieldValuesAsText" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/FieldValuesAsText" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/FieldValuesForEdit" type="application/atom+xml;type=entry" title="FieldValuesForEdit" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/FieldValuesForEdit" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/File" type="application/atom+xml;type=entry" title="File" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/File" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Folder" type="application/atom+xml;type=entry" title="Folder" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/Folder" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ParentList" type="application/atom+xml;type=entry" title="ParentList" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/ParentList" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Properties" type="application/atom+xml;type=entry" title="Properties" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/Properties" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Versions" type="application/atom+xml;type=feed" title="Versions" href="Web/Lists(guid'5437d1aa-85cd-4c1c-973c-135d5ecf3952')/Items(1)/Versions" />
        <title />
        <updated>2017-10-23T07:48:54Z</updated>
        <author>
            <name />
        </author>
etc......

When I change the output to json I get:

Unexpected '<'

However, this json error does not seem to matter because the application is returning the expected results.

 

When I run MY query in postman I get:

<?xml version="1.0" encoding="utf-8"?>
<d:query xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" m:type="Microsoft.Office.Server.Search.REST.SearchResult">
    <d:ElapsedTime m:type="Edm.Int32">270</d:ElapsedTime>
    <d:PrimaryQueryResult m:type="Microsoft.Office.Server.Search.REST.QueryResult">
        <d:CustomResults m:type="Collection(Microsoft.Office.Server.Search.REST.CustomResult)" />
        <d:QueryId>d683c430-ba34-4db3-bd8c-c70feca21de5</d:QueryId>
        <d:QueryRuleId m:type="Edm.Guid">00000000-0000-0000-0000-000000000000</d:QueryRuleId>
        <d:RefinementResults m:null="true" />
        <d:RelevantResults m:type="Microsoft.Office.Server.Search.REST.RelevantResults">
            <d:GroupTemplateId m:null="true" />
            <d:ItemTemplateId m:null="true" />
            <d:Properties m:type="Collection(SP.KeyValue)">
                <d:element>
                    <d:Key>GenerationId</d:Key>
                    <d:Value>9223372036854775806</d:Value>
                    <d:ValueType>Edm.Int64</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>indexSystem</d:Key>
                    <d:Value></d:Value>
                    <d:ValueType>Edm.String</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>ExecutionTimeMs</d:Key>
                    <d:Value>47</d:Value>
                    <d:ValueType>Edm.Int32</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>QueryModification</d:Key>
                    <d:Value>ContentType:ILSS_matters -ContentClass=urn:content-class:SPSPeople</d:Value>
                    <d:ValueType>Edm.String</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>RenderTemplateId</d:Key>
                    <d:Value>~sitecollection/_catalogs/masterpage/Display Templates/Search/Group_Default.js</d:Value>
                    <d:ValueType>Edm.String</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>StartRecord</d:Key>
                    <d:Value>0</d:Value>
                    <d:ValueType>Edm.Int32</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>IsLastBlockInSubstrate</d:Key>
                    <d:Value>true</d:Value>
                    <d:ValueType>Edm.Boolean</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>IsFirstBlockInSubstrate</d:Key>
                    <d:Value>false</d:Value>
                    <d:ValueType>Edm.Boolean</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>IsFirstPinnedResultBlock</d:Key>
                    <d:Value>false</d:Value>
                    <d:ValueType>Edm.Boolean</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>IsLastPinnedResultBlock</d:Key>
                    <d:Value>false</d:Value>
                    <d:ValueType>Edm.Boolean</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>IsFirstRankedResultBlock</d:Key>
                    <d:Value>true</d:Value>
                    <d:ValueType>Edm.Boolean</d:ValueType>
                </d:element>
                <d:element>
                    <d:Key>IsLastRankedResultBlock</d:Key>
                    <d:Value>true</d:Value>
                    <d:ValueType>Edm.Boolean</d:ValueType>
                </d:element>
            </d:Properties>
            <d:ResultTitle m:null="true" />
            <d:ResultTitleUrl m:null="true" />
            <d:RowCount m:type="Edm.Int32">63</d:RowCount>
            <d:Table m:type="SP.SimpleDataTable">
                <d:Rows>
                    <d:element m:type="SP.SimpleDataRow">
                        <d:Cells>
                            <d:element m:type="SP.KeyValue">
                                <d:Key>Rank</d:Key>
                                <d:Value>16.2882633209229</d:Value>
                                <d:ValueType>Edm.Double</d:ValueType>
                            </d:element>
                            <d:element m:type="SP.KeyValue">
                                <d:Key>DocId</d:Key>
                                <d:Value>458931375</d:Value>
                                <d:ValueType>Edm.Int64</d:ValueType>
                            </d:element>
                            <d:element m:type="SP.KeyValue">
                                <d:Key>RefinableString10</d:Key>
                                <d:Value>42914 Research and Conservation Project</d:Value>
                                <d:ValueType>Edm.String</d:ValueType>
                            </d:element>
Etc....

When I change the output to json I also get:

 

Unexpected '<'

So my query returns the expected results but the format is different. I guess that is why the mapping is not working but I don't have a clue how to solve this.

 

When checking the json I notice that the type of the results is [object(Array)] and in my working example it is "[[object Object].object Object]]"

 

I hope this explains my problem a bit more.

I think, I'm getting closer.

My object is like this:

export interface IReactGetItemsState{  
  items:[  
        {  
          "RefinableString10": "",  
          "CreatedBy": "",  
          "Created":""  
  
        }]  
}  

I map it to my returned json like this:

  reactHandler.setState({
          items: resultData.PrimaryQueryResult.RelevantResults.Table.Rows
        });

To retrieve the values I do:

{this.state.items.map(function(item,key){ 
                   
                   return (<div className={styles.rowStyle} key={key}>  
                       <div className={styles.CellStyle}>{item.RefinableString10}</div>  
                       <div className={styles.CellStyle}>{item.CreatedBy}</div>  
                        <div className={styles.CellStyle}>{item.Created}</div>

This is not working because the values are "deeper" away in the object. What I would like to do is this:

 

{this.state.items.map(function(item,key){ 
                   
                   return (<div className={styles.rowStyle} key={key}>  
                       <div className={styles.CellStyle}>{item.Cells[2].value}</div>  
                       <div className={styles.CellStyle}>{item.Cells[3].value}</div>  
                        <div className={styles.CellStyle}>{item.Cells[4].value}</div>

I'm not allowed to do that because "Cells does not exist on type....."

I think I must define my object in another way or something. 

 

 

Hi,

 

Look at my example data below. If you run the query with header Accept: application/json, you get a different structure. You should then be able to get the Cells directly from the item. 

 

A question, do you need the Rows? You could map your items directly to the Cells.

 

 reactHandler.setState({
          items: resultData.PrimaryQueryResult.RelevantResults.Table.Rows.Cells
        });

 

 

 

 

data.PNG

 

 

 

 

 

Hi @Maggan Wåhlin

 

No, I do not need the rows. So I did:

 reactHandler.setState({
          items: resultData.PrimaryQueryResult.RelevantResults.Table.Rows.Cells
        });

But that gives me the error: "Unable to get property 'map' of undefined or null reference"

 

This is my Json:

Capture.PNG

 

Is there something wrong here?

export interface IReactGetItemsState{  
  items:[  
        {  
          "RefinableString10": "",  
          "CreatedBy": "",  
          "Created":""  
  
        }]  
}  

export default class ReactGetItems extends React.Component<IReactGetItemsProps, IReactGetItemsState> {
      public constructor(props: IReactGetItemsProps, state: IReactGetItemsState){  
      super(props);  
      this.state = {  
        items: [  
          {  
            "RefinableString10": "",  
            "CreatedBy": "",  
            "Created":""  
         
          }  
        ]  
      };  
    }  

Make sure that you have the data in this.state before you try to render it. This is how I get the data (not using ajax):

 

private loadDocuments(): Promise<any> {
    var url = "your rest url";
    return this.props.spHttpClient.get(url, SPHttpClient.configurations.v1).then((response: SPHttpClientResponse) => {
      if (response.ok) {
        return response.json();
      } else {
        console.log("WARNING - failed to hit URL " + url + ". Error = " + response.statusText);
        return null;
      }
    });
  }

 

componentDidMount() {
         this.loadDocuments().then((response) => {
         this.setState({
          items: response.value
        });
      });
    }
  }

 

I rebuild my application using your example.

I think I make a small error in the URL:

      var url = "https://Bla.sharepoint.com/sites/bla/_api/search/query?querytext='ContentType:TEST_matters'&selectproperties='RefinableString10%2cCreatedBy%2cCreated'&rowlimit=500'";

I receive an error which tells me the query is wrong.

1 best response

Accepted Solutions
best response confirmed by Mike Jansen (Iron Contributor)
Solution

I had to create my own version of the web part to test the code. This works for me :)

 

 

import * as React from 'react';
import styles from './HelloWorld.module.scss';
import { IHelloWorldProps } from './IHelloWorldProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { SPHttpClient, SPHttpClientConfiguration, SPHttpClientResponse, ODataVersion, ISPHttpClientConfiguration } from '@microsoft/sp-http';

export default class HelloWorld extends React.Component<IHelloWorldProps, any> {

  constructor(props) {
    super(props);
    this.state = {
      items: [],
    };
  }

  private search(): Promise<any> {

    const spSearchConfig: ISPHttpClientConfiguration = {
      defaultODataVersion: ODataVersion.v3
    };
    const clientConfigODataV3: SPHttpClientConfiguration = SPHttpClient.configurations.v1.overrideWith(spSearchConfig);

    var url = "https://xxx.sharepoint.com/sites/vmf-lab/_api/search/query?querytext='ContentType:Kvalitetsdokument'&selectproperties='Title,RefinableString05,CreatedBy,Created'&rowlimit=500"; //this.properties.siteUrl + "/_api/web/lists";
    return this.props.context.spHttpClient.get(`${url}`, clientConfigODataV3).then((response: SPHttpClientResponse) => {
      if (response.ok) {
        return response.json();
      } else {
        console.log("WARNING - failed to hit URL " + url + ". Error = " + response.statusText);
        return null;
      }
    });
  }

  componentDidMount() {
    this.search().then((response) => {
      this.setState({
        items: response.PrimaryQueryResult.RelevantResults.Table.Rows
      });
    });
  }

  public render(): React.ReactElement<IHelloWorldProps> {
    return (
      <div className={styles.helloWorld}>
        <div className={styles.container}>
          {this.state.items.map(row =>
            <div className={`ms-Grid-row ${styles.row}`}>
              <div className={`ms-Grid-col`}>
                {row.Cells.map(col =>
                  <div>{col.Key} - {col.Value}</div>
                )}
              </div>
            </div>
          )}
        </div>
      </div>
    );
  }
}

Hope it works.

 

 

View solution in original post