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.
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....
- 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 }); - Mike JansenOct 23, 2017Iron Contributor
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.
- Mike JansenOct 23, 2017Iron Contributor
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.
- Mike JansenOct 20, 2017Iron Contributor
Thanks again,
I'll dive into this today and will post my results. I hope I can clarify my issue.
- Maggan WåhlinOct 20, 2017Iron ContributorI 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. - Mike JansenOct 19, 2017Iron Contributor
Nope.
Everything is empty