Am implementing the Algolia search in my NextJS app. I have the datasource and indices already setup. What am trying to setup is something like what Gucci is doing in their search. Gucci is using Algolia for their search functionality.
I tried using the react-instantsearch-dom package of Algolia. And I updated my /pages/_app.js file like this(only relevant code is written here):
/**
* /pages/_app.js
*
*/
//-------- Algolia
import algoliasearch from 'algoliasearch/lite';
import { InstantSearch } from 'react-instantsearch-dom';
const searchClient = algoliasearch( 'xxxxxxxxxx', 'yyyyyyyyyyyyyyy' );
//-------- /Algolia
function MyApp({ Component, pageProps }) {
return (
<>
<InstantSearch searchClient={searchClient} indexName={ 'abc_test_products' }>
<Component {...pageProps} />
</InstantSearch>
</>
)
}
export default MyApp
This way I could use the components of react-instantsearch-dom anywhere.
Am confused at three things here.
Doubt 1:
How can I pass the algolia query and filters to the search results page like here and display the results using the components : https://www.gucci.com/us/en/st/newsearchpage?facetFilters=categoryLevel1_en%3AChildren&searchString=handbags&search-cat=header-search
So basically when the user clicks one of the algolia search suggestions(from the dropdown after clicking the search box at the top right corner of the page), they are taken to a search results page and there it seems like Algolia search is instantiated and displays the results.
Doubt 2:
How can I display the auto suggestions and product images side by side?
Doubt 3:
Displaying dynamic filter/refinement options. I understood that if there's a brand attribute in our Algoia indices/dataset, I can include that in the search filter like this:
<RefinementList attribute="brand" />
But if there are different attribute that I want to display the refinement list, say "Color", "Brand", etc. how would I display the title of the refinement option and the list dynamically from the search results.
Your UI example looks like a mash-up of a Query Suggestions panel on the left and a Hits list on the right. You can customize what a hit looks like before you render to get the image in there. And the filter menus automatically update as the user filters.
I haven't tried to get the query params into the URL, that would be interesting. I think you'd have to add useRouter into your Next page and then push the Algolia params onto the string using onClick.
Below is an example I built, maybe it helps you:
const Search = () => {
const Hit = ({ hit }) => {
return (
<HitContainer>
<span>{hit.type}</span>
<h2><a href={hit.path}>{hit.title}</a></h2>
{
hit.content &&
<p>{ `${hit.content.substring(0,150)} ...` }</p>
}
<hr />
</HitContainer>
)
}
return (
<>
<InstantSearch
searchClient={ AlgoliaReactClient }
indexName="MAINSITE" >
<CustomSearchBox
translations={{
submitTitle: 'Submit your search query.',
resetTitle: 'Clear your search query.',
placeholder: 'What are you looking for?',
}}
/>
<HitsAndFilters>
<AllFilters>
<h3>Topics</h3>
<FilterMenu
attribute="topics"
limit={5}
showMore
/>
<h3>Locations</h3>
<FilterMenu
attribute="locations"
limit={5}
showMore
/>
</AllFilters>
<AllHits hitComponent={Hit} />
</HitsAndFilters>
</InstantSearch>
</>
)
}
Related
I'm using React Admin, and I have this result :
As you can see, React admin displays /media_objects/:id.
I would like to display the title instead of the id.
Here is my code :
export const MediaAreasList = props => (
<ListGuesser {...props}>
<FieldGuesser label={labelLabel} source="label" />
<FieldGuesser source="title" label={titleLabel} />
<FieldGuesser source="description" />
<FieldGuesser source="mediaObjects" label={mediaObjectsLabel} />
</ListGuesser>
);
Is there a way to display titles instead of id ?
I didn't find anything in the doc
Thank you !
Do not use ListGuesser in production https://marmelab.com/react-admin/Tutorial.html
you’ll have to replace the ListGuesser component in the users
resource by a custom React component. Fortunately, ListGuesser dumps
the code of the list it has guessed to the console
In your case FieldGuesser for mediaObjects should be replaced with ReferenceManyField component:
<ReferenceManyField label={mediaObjectsLabel} reference="mediaObjects" target="...">
<SingleFieldList>
<ChipField source="title" />
</SingleFieldList>
</ReferenceManyField>
docs: https://marmelab.com/react-admin/ReferenceManyField.html
Issue:
Right now, I have a dynamic route that fetches data using getServerSideProps(). Within this page, there are multiple tabs that renders different data depending on state (which tab is selected).
I wish to transition from using multiple tabs on this page, to instead using nested routes. However, I am having difficulty obtaining the data originally fetched in these nested routes. Is there an efficient way of doing so, without having to call getServerSideProps() again?
My intended setup looks like this, where [page] calls getServerSideProps():
[page].jsx
|_tab1.jsx
|_tab2.jsx
|_tab3.jsx
My current [page].jsx, where I would like to use separate, nested pages that have access to these props (instead of rendering each tab based on state):
export default function Page(props) {
const [currentTab, setCurrentTab] = useState("home");
return (
<div>
<div id="tab1" onClick={() => setCurrentTab("home")}>
home
</div>
<div id="tab2" onClick={() => setCurrentTab("posts")}>
posts
</div>
<div id="tab3" onClick={() => setCurrentTab("info")}>
info
</div>
{currentTab === "home" ? (
<HomeTab props={props}/>
) : currentTab === "posts" ? (
<PostsTab props={props}/>
) : (
<InfoTab props={props}/>
)}
</div>
);
}
Attempts
I've attempted using the context API to utilize data globally, which my other pages can use. However, this requires the user to visit the original dynamic route first.
Call getServerSideProps() on each nested route. Although this works, I wish to find a better solution, since I'm fetching data on each nested route while the route they're nested under has all of this data available already.
You can use shallow routing in next/route or next/link
Note that, in the below example, I'm using next/link for the demonstration. Without your tab data, I'd assume you have an array of tabs in data
import { useEffect } from 'react'
import Link from 'next/link'
//the path is `/tab/:tabId`
function Tab({ data }) {
const [tabData, setTabData] = useState(data[0]) //first tab data as default for example
useEffect(() => {
setTabData(data[tabId])
}, [router.query.tabId])
return <>
<Link href="/tab/0" shallow />
<Link href="/tab/1" shallow />
<div>
{tabData}
</div>
</>
}
export async function getServerSideProps(context) {
return {
props: {
data: [], //tabs' data you fetched from the API
},
}
}
export default Tab
I want to list all wordpress posts on my gatsby.js page, and to filter them by category when the user clicks on category-tab.
When the category is selected, I save it as "choosenCategory"variable and it is a string. I have a problem finding a way to pass a variable to my query and this approach doesn't work:
const chosenCategory = "myCategory";
const PostListingData = (props) => (
<StaticQuery
query={graphql`
query($name: String = chosenCategory) {
allWordpressPost(filter:
{ categories:
{ elemMatch:
{ name:
{ eq:
$name
}
}
}
}
)
{
edges {
node {
id
title
categories {
name
}
}
}
}
}
`}
render={data => <PostsListing data={data} {...props} />}
/>
)
const PostsListing = ({ data }) => {
return (
<div>
{data.allWordpressPost.edges.map(({ node }, i) => (
*** some code ***
))}
</div>
)}
Just to clarify, the reason this won't work is that Gatsby has no way to generate a site that dynamically loads content based on a variable set at runtime. If it did accept the code you wrote, it would only be able to generate one category "myCategory". Instead of doing that, Gatsby just rejects variables in the queries.
In my experience, there are a few options:
Generate a page for every category using gatsby-node.js. https://www.gatsbyjs.org/tutorial/part-seven/
Use a search plug-in. Essentially this option is generating a tree based off of all the posts and putting that on the page that the search shows up on.
Make your own search. This is similar to #2. You will have to bring in ALL posts, make components for all of them, and then set them to visible based off of the search component's state.
so i am playing with wordpress gutenberg block dev. and already made some simple one. now i am trying to make a more complicated one of a slider.
everything works untill the save function where i get validation error and a bizzare notice about EndTag which i do not get.
this is my save function , for now i am just trying to save the slides titles. if i console log within the map, i do get what i want.
save: function( props ) {
const { slides } = props.attributes;
const id = props.attributes.id;
const displaySlides = slides.map((slide) => {
return (
<div>
<span>{slide.title}</span>
</div>
)
});
return (
<div>
{displaySlides}
</div>
)
},
and here is the error i get in my dev tools:
notice that the titles are there but still i get validation error!!
what an i missing here? and what is it the EndTag thing?
best regards
Your edit function node list doesn't match up with save function node list. That's why you are getting this error. Look closely, there's a node list WP expected but it got a different node list.
I'm building an app with Meteor using the react-komposer package. It is very simple: There's a top-level component (App) containing a search form and a list of results. The list gets its entries through the props, provided by the komposer container (AppContainer). It works perfectly well, until I try to implement the search, to narrow down the results displayed in the list.
This is the code I've started with (AppContainer.jsx):
import { Meteor } from 'meteor/meteor';
import { composeWithTracker } from 'react-komposer';
import React, { Component } from 'react';
import Entries from '../api/entries.js';
import App from '../ui/App.jsx';
function composer(props, onData) {
if (Meteor.subscribe('entries').ready()) {
const entries = Entries.find({}).fetch();
onData(null, {entries});
};
};
export default composeWithTracker(composer)(App);
App simply renders out the whole list of entries.
What I'd like to achieve, is to pass query parameters to Entries.find({}).fetch(); with data coming from the App component (captured via a text input e.g.).
In other words: How can I feed a parameter into the AppContainer from the App (child) component, in order to search for specific entries and ultimately re-render the corresponding results?
To further clarify, here is the code for App.jsx:
import React, { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
<form>
<input type="text" placeholder="Search" />
</form>
<ul>
{this.props.entries.map((entry) => (
<li key={entry._id}>{entry.name}</li>
))}
</ul>
</div>
);
}
}
Thanks in advance!
I was going to write a comment for this to clarify on nupac's answer, but the amount of characters was too restrictive.
The sample code you're looking for is in the search tutorial link provided by nupac. Here is the composer function with the corresponding changes:
function composer(props, onData) {
if (Meteor.subscribe('entries', Session.get("searchValues")).ready()) {
const entries = Entries.find({}).fetch();
onData(null, {entries});
};
};
The solution is the session package. You may need to add it to your packages file and it should be available without having to import it. Otherwise try with import { Session } from 'meteor/session';
You just need to set the session when submitting the search form. Like this for instance:
Session.set("searchValues", {
key: value
});
The subscription will fetch the data automatically every time the specific session value changes.
Finally, you'll be able to access the values in the publish method on the server side:
Meteor.publish('entries', (query) => {
if (query) {
return Entries.find(query);
} else {
return Entries.find();
}
});
Hope this helps. If that's not the case, just let me know.
There are 2 approaches that you can take.
The Subscription way,
The Meteor.call way,
The Subscription way
It involves you setting a property that you fetch from the url. So you setup your routes to send a query property to you Component.Your component uses that property as a param to send to your publication and only subscribe to stuff that fits the search criteria. Then you put your query in your fetch statement and render the result.
The Meteor.call way
Forget subscription and do it the old way. Send your query to an endpoint, in this case a Meteor method, and render the results. I prefer this method for one reason, $text. Minimongo does not support $text so you cannot use $text to search for stuff on the client. Instead you can set up your server's mongo with text indexes and meteor method to handle the search and render the results.
See what suits your priorities. The meteor.call way requires you to do a bit more work to make a "Search result" shareable through url but you get richer search results. The subscription way is easier to implement.
Here is a link to a search tutorial for meteor and read about $text if you are interested