Forum Discussion
spfx React > REST API > Map search results
- Oct 23, 2017
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.
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=""1"">
<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.
- Franck CornuOct 24, 2017Brass ContributorTotally agree!
- Franck CornuOct 24, 2017Brass Contributor
Ok I didn't see this, my bad. I had similar issues with typings due to the version of TypeScript and React used by the SharePoint Framework.
I suggest you to update your SPFx generator with the latest drop (1.3.0), and use the latest version of sp-np-js. Don't forget to wipe your node_modules folder before.
- Maggan WåhlinOct 24, 2017Iron ContributorThis is really good advice, I have tried the library and it is awesome 😊. Why is there not more information out there in this? It should be top ranked by Google 😊
- Mike JansenOct 24, 2017Iron Contributor
Hi Franck Cornu, Thanks
It is never too late to see a different approach. Will have a look at your example.
As a matter of fact, I even tried it before but got stuck. You can see my problem here https://techcommunity.microsoft.com/t5/SharePoint-Developer/SPFX-gt-pnp-sp-reference-in-React-project/m-p/115349#M3522
- Franck CornuOct 24, 2017Brass Contributor
Hi Mike Jansen,
Why not use the sp-pnp-js library to play with SharePoint search? This library offers you built-in methods and typings to manipulate search results, refiners and so on more easily. Maybe it is little bit tool late but here is a working example using this library with react and SPFx. This sample using a generic TypeScript interface to map results:
https://github.com/FranckyC/sp-dev-fx-webparts/tree/react-search-refiners/samples/react-search-refiners
Hope it could help!
- Mike JansenOct 24, 2017Iron Contributor
Maggan WåhlinOne more question.
Can you tell me how to filter data here:
{row.Cells.map(col => <div>{col.Key} - {col.Value}</div> )}Suppose I only want to show "CreatedBy" an "Created"
If col.Key = "CreatedBy" { }Something like that?
EDIT:
Got it:
{row.Cells.map(col => { if (col.Key == "CreatedBy") { return <div>{col.Key} - {col.Value}</div> } } - Mike JansenOct 24, 2017Iron Contributor
Again thanks a lot for all your help and patience.
This gets me started using the SharePoint framework. I try to migrate an SharePoint classic application to the modern variant. The old one heavily uses CSWP and display templates. Now I have one of the most important techniques covered. Diving into layout (UI Fabric) now.
- Maggan WåhlinOct 24, 2017Iron ContributorGlad it worked :-)
- Mike JansenOct 24, 2017Iron Contributor
Got it!!!
Was some quote pasting problem.
Had '${url}' instead of `${url}`
Thanks a lot!!!
- Mike JansenOct 24, 2017Iron Contributor
I'm not very lucky. Getting an error again.
For sure the url is ok but it looks like it is not passed in a decent way.
WARNING - failed to hit URL https://Blabla.sharepoint.com/sites/bla/_api/search/query?querytext='ContentType:BLA_matters'&selectproperties='Title,RefinableString10,CreatedBy,Created'&rowlimit=500. Error = Not Found react-get-items-web-part.js (214,17) HTTP404: NOT FOUND - The server has not found anything matching the requested URI (Uniform Resource Identifier). (Fetch)GET - https://Blabla.sharepoint.com/sites/bla/_layouts/15/$%7Burl%7D
When I copy the url from the source code and paste it in the browser it's working.
- Maggan WåhlinOct 24, 2017Iron Contributor
It was a way to get a hold of the context object, maybe there is another way of doing it, but here it goes:
HelloWorldWebPart.ts:
public render(): void { const element: React.ReactElement<IHelloWorldProps > = React.createElement( HelloWorld, { description: this.properties.description, context: this.context } ); ReactDom.render(element, this.domElement); }HelloWorldProps.ts:
export interface IHelloWorldProps { description: string; context: any; } - Mike JansenOct 23, 2017Iron Contributor
One, I hope, last question.
"return this.props.context.spHttpClient"
I get an error on "context" Saying it does not exist on type .....
I guess it must be in my "----Props.ts"
What is you line?
- Maggan WåhlinOct 23, 2017Iron Contributor
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.
- Mike JansenOct 23, 2017Iron Contributor
Not yet but I'll keep on digging. Getting close now.
- Maggan WåhlinOct 23, 2017Iron Contributor
The error is the extra ' in the end, right after 500...
This should work:
var url = "https://xxx.sharepoint.com/_api/search/query?querytext='ContentType:TEST_matters'&selectproperties='Title,RefinableString10,CreatedBy,Created'&rowlimit=500"
- Mike JansenOct 23, 2017Iron Contributor
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.
- Maggan WåhlinOct 23, 2017Iron Contributor
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 }); }); } } - Mike JansenOct 23, 2017Iron Contributor
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:
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":"" } ] }; } - Maggan WåhlinOct 23, 2017Iron Contributor
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 });