Elasticsearch search-ui with React - reactjs

I have a React site with aside and main content. I want to use search-ui for searching on the site.
The search bar should be on the aside, and when the user searches for something, results should be displayed on the main content. Aside and main content are two separated react components.
In my aside, I'm configuring search-ui SearchBox like this
<SearchBox
autocompleteResults={{
titleField: "title",
urlField: "url"
}}
autocompleteSuggestions={true}
onSubmit={searchTerm => {
navigate("/elastic-search?q=" + searchTerm);
}}
onSelectAutocomplete={(selection, {}, defaultOnSelectAutocomplete) => {
if (selection.suggestion) {
navigate("/elastic-search?q=" + selection.suggestion);
} else {
defaultOnSelectAutocomplete(selection);
}
}}
/>
So when the user searches something the app will redirect to a separate page named elastic-search and I'm passing the searchTerm in the URL through navigate method.
On MainContent I have results like this:
<Results titleField='title' urlField='url'/>
Now the question is how can I fetch searchTerm and display the results on main content. The structure of the app is like this:
<App>
<SearchProvider config={config}>
<Aside /> ---- Here I have <SearchBox>
<MainContent /> ---- Here I have <Results>
</SearchProvider>
</App>
When I search the app redirects to /elastic-search with searchTerm in URL, but the results are not displaying. If I refresh the page they are displayed. How can I notify Results or re-render the page, so I can show the searched results.

Your Results seems to be missing some parameters and should look something like this:
<>
<Results
titleField="title"
urlField=""
view={SearchView}
resultView={SearchResultView}
/>
</>
And your SearchView (Used to override the default view for this Component.) and SearchResultView (Used to override individual Result views.) components, should look something like this:
const SearchView = ({ children }) => {
return <div>{children}</div>
};
const SearchResultView = ({ result: searchResult }) => {
return <div>{searchResult.content}</div>
}
Additional suggestion
This is a working example in the Next.js app with import { useRouter } from "next/router"; that needs to be replaced with your routing solution. In the SearchBox component:
export const SearchBoxComponent = () => {
const router = useRouter();
return (
<>
<SearchBox
searchAsYouType={true}
autocompleteResults={{
titleField: "title",
urlField: "",
shouldTrackClickThrough: true,
clickThroughTags: ["test"],
}}
autocompleteSuggestions={true}
onSubmit={(searchTerm) => {
const urlEncodedQuery = encodeURI(searchTerm).replace(/%20/g, "+");
router.push(`/search?q=${urlEncodedQuery}`);
}}
...
</>
)
}

Related

Match background with users current weather conditions

I am new to React, trying to learn and I have this unsolvable problem. I have developed a weather app, I'm still working on it, but at this moment I am stuck for 3 days trying to have a background image that changes depending on the users weather conditions. I have tried something using the icon, from openweather API. I used the same method to get the icon (image from my folder) to match users weather conditions.
import React from "react";
export default function Background(props) {
const codeMapping = {
"01d": "clear-sky-day",
"01n": "clear-sky-night",
"02d": "cloudy-day",
"02n": "cloudy-night",
"03d": "cloudy-day",
"03n": "cloudy-night",
"04d": "cloudy-day",
"04n": "cloudy-night",
"09d": "shower-rain-day",
"09n": "shower-rain-night",
"10d": "rain-day",
"10n": "rain-night",
"11d": "thunderstorm-day",
"11n": "thunderstorm-night",
"13d": "snow-day",
"13n": "snow-night",
"50d": "fog-day",
"50n": "fog-night",
};
let name = codeMapping[props.code];
return (
<img
className="background"
src={`background/${name}.jpg`}
alt={props.alt}
size="cover"
/>
);
}
So... in order to get "icon" of the input city by the user I have to call "<Background cod={weatherData.icon} alt={weatherData.description} />" from the function "Search" which is the function handling the submit form and running api call for input city. But the image is not showing(img1), but to have the img as a background I would call <Background> from my App function(img2), but in this case I will not have access to the real icon value from the input city. I should mention I have a folder in "src" called background and the images names match the codes name from the mapping.
Thank you in advance!
current preview of my app
how I see in other documentation I should set a background
You can pass the code from Search.js as the state.
App.js
const codeMapping = {
"01d": "clear-sky-day",
"01n": "clear-sky-night",
};
export const App = () => {
const [code, setCode] = useState(null) // <-- We'll update this from Search.js
const [backgroundImage, setBackgroundImage] = useState("")
useEffect(() => {
// Set background value based on the code
setBackgroundImage(codeMapping[`${code}`])
}, [code]); // <-- useEffect will run everytime the code changes
return (
<div style={{
height: '100px',
width: '100px',
backgroundImage: `${backgroundImage || "defaultBackgroundImage"}`
}}>
<Search setCode={setCode} />
</div>
)
}
Search.js
import { WeatherContext } from './App';
export const Search = ({ setCode }) => {
const handleClick = (apiResponse) => {
// Some API call returning the actual code value here //
setCode(apiResponse)
}
return (
<input
onClick={() => handleClick("01n")}
type="button"
value="Change city"
/>
)
}

Next.js: How do you pass data to a route created dynamically

I have a component that is receiving data:
const ProductTile = ({ data }) => {
let { productList } = data
var [products] = productList
var { products } = products;
return (
<div>
<div className="p-10 grid grid-cols-1 sm:grid-cols-1 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-3 gap-5">
{products.reduce((products, product) => products.find(x => x.productId === product.productId) ? products : [...products, product], []).map(({ colorCode, defaultColorCode, now, productId, productCode, productDescription, }, index) => {
return (
<Link key={`${productId}${index}`}
href={{
pathname: '/s7-img-facade/[slug]',
query: { slug: productCode },
}}
passHref>
/* template */
</Link>
)
})}
</div>
</div>
)
}
export default ProductTile
It creates a grid of templates each wrapped in a <Link> component which is rendering a dynamic component;
/s7-img-facade/[product]
What I would like is for the dynamic component to have access to products object which is in the ProductTile .
I know that I can do a getStaticProps in the dynamic component to do another request but that seems redundant and not dry...
Any ideas how the dynamic component get access to the products object?
Thanks in advance!
You've got the right idea - you can pass additional properties in the query field, but you'll need to use getServerSideProps to extract those from the query param and pass it to the page component as props. Something like this:
// pages/product.js
...
<Link key={`${productId}${index}`}
href={{
pathname: '/s7-img-facade/[slug]',
query: {
description: productDescription,
slug: productCode
},
}}
passHref
>
/* template */
</Link>
...
// pages/s7-img-facase/[slug].js
export default function S7ImgFacasePage({ description }) {
return <p>{ description }</p>
}
export const getServerSideProps = ({ params }) => {
const description = { params }
return {
props: {
description
}
}
}
So basically you pass it from the first page in params, read that in getServerSideProps of the second page, and then pass that as a prop to the second page component.
You mentioned getStaticProps - this won't work with static pages because getStaticProps is only run at build time so it won't know anything about the params you send at run time. If you need a fully static site, then you should consider passing it as a url parameter and reading it in useEffect or passing all possible pages through getStaticPaths and getStaticProps to generate all your static pages.

Gatsby GraphQL Variables In Component

I'm an iOS developer and I've been struggling for what seems like the longest time making my portfolio site from scratch. I've tried a bunch of different technologies and have finally settled on using Gatsby to create it.
So far things have been fairly straightforward but I can not figure out for the life of me how to get a component that looks like the picture below. I've gotten most of the layout design working, but I can't seem to use graphql to query the images I need in the component.
Desired Layout
I've found plenty of Gatsby example templates such as this one and this one that are similar. However the main difference is that each of these only have one image and they seem to be using Gatsby 2.0 instead of 3.0.
I can get one image using "useStaticQuery", however I need access to different images for each component. From my understanding this is not possible to do within a component, only on a page. I also can not pass the image path as a variable to StaticImage either.
export default function App(props) {
const query = useStaticQuery(graphql`
query AppSectionImages {
icon: file(relativePath: { eq: "EzMaxRequest/AppIcon_180.png" }) {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
`);
const image = getImage(query.icon);
const app = props.app;
return (
<div>
<h1>{app.title}</h1>
<GatsbyImage image={image} />
</div>
);
Result
Can anyone please explain to me how I can get the desired layout in a component?
Edit
Here is some relevant code of what I am doing.
This is my index.js home page.
export default function IndexPage({ data }) {
const projects = data.apps.edges;
return (
<Layout>
<SEO title="Home" />
<HeroSection />
<DescriptionSection />
<div>
{projects.map(({ node: project }) => (
<AppSection app={project} />
))}
</div>
<FooterSection />
</Layout>
);
}
//export page query
export const query = graphql`
query Apps {
apps: allAppsJson(sort: { order: ASC, fields: order }) {
edges {
node {
appLink
title
tagline
moreLink
order
icon
}
}
}
}
`;
Here is the component.
export default function App(props) {
const query = useStaticQuery(graphql`
query AppSectionImages {
icon: file(relativePath: { eq: "EzMaxRequest/AppIcon_180.png" }) {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
`);
const image = getImage(query.icon);
const app = props.app;
return (
<div>
<h1>{app.title}</h1>
<GatsbyImage image={image} alt={app.title} />
</div>
);
}
You have a few options:
Query for all of your image data in your page query and prop-drill the data to the component that uses it to display the image.
Using Gatsby v3+, hardcode the image references for each component using the new StaticImage component.
If you have a single component used multiple times with different content/images, but a static parent component with your content, you can leverage option #2 above but pass the image component down as a prop or children.

Fixed side menu on an specific Next.js route

I'm starting a new project and I would like to always have a top nav and only in a specific route have a sidebar where I can click and the content will change.
The top nav should always be visible if I am in site.com or site.com/*/**
If I go to site.com/posts I want to see a sidebar with all the posts title
If I click a post on the left it will redirect to site.com/posts/1 and only the right side should change
I'm having trouble with the second and third bullet. My pages path is pages/posts/index.js and pages/posts/[id].js but how can I declare only one file and avoid duplicating code? 🤔
I tried pages/posts/[[...slug]].js but I'm seeing this error: Error: Optional catch-all routes are currently experimental and cannot be used by default ("/posts/[[...slug]]")
I'm looking for examples but so far I couldn't do it.
Any ideas?
Update: Next.js now recommends to use function getLayout()
New Documentation ✨
Create a file: /layouts/RequiredLayout.js
const RequiredLayout = ({ children }) => {
return (
<>
<Navbar />
<main>{children}</main>
</>
);
};
export default RequiredLayout;
Then edit _app.js
function MyApp({ Component, pageProps }) {
const Layout = Component.Layout || EmptyLayout;
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
const EmptyLayout = ({ children }) => <>{children}</>;
export default MyApp;
Now whenever you require to use this layout, then add only a single line at the end
NameOfComponent.Layout = RequiredLayout;
For in your case: In **pages/posts/[id].js**
import React from 'react'
const ID = () => {
return(
<>
<p>page with id details</p>
</>
)
}
export deafult ID;
ID.Layout = RequiredLayout //responsible for layout
I was able to enable this experimental option by having my next.config.js look like this (I'm using Next v9.4.4):
module.exports = {
experimental: {
optionalCatchAll: true,
},
};

Isomorphic react redux sending Dynamic Id

I am using react router with redux for isomorphic app. I have an use case where href needs to work both on client and server side. This is mainly for SEO reasons. for bots it should use href and when user clicks it should work too
my route looks like this
<Route name="app" component={ProductPage} path="/product/:Id">
<IndexRoute component={ProductContainer}/>
</Route>
http://localhost:3000/product/1234 (this works from server side)
Once the above URL renders data, i have page with different URLS of that kind
http://localhost:3000/product/3456
http://localhost:3000/product/9999
when user clicks the above URLS, i don't want to do a post back, i want to use browserHistory api and render the component, is it possible?
I have this in the container
static needs = [
productActions.getProductData
]
Any direction will be appreicated
Server Side
You could require a function call that would do your dispatches onEnter of the specific route, which would populate the app state that you need to render your component.
the route would look something like this:
<Route component={ProductPage} path="/product/:Id" onEnter={requireProductData} />
requireProductData would be a function in your main.js something like this:
function requireProductData(nextState, replace, callback) {
const { params } = nextState
var idNum = parseInt(params.id.toString().split("-")[0])
const currentState = store.getState()
if (idNum > 0) {
store.dispatch(ProductActionCreators.productInfo({ id: idNum })).then((event) => {
callback()
}, (error) => {
callback()
})
} else {
callback()
}
}
this would do your action creator calls and then your reducer would take care of the app state. then you could render your component from that url.
Client Side
Yeah, you're right about doing an onClick handler. Here's an example of where clicking a button causes an API call with a dispatch:
class SubscribeUserDialog extends Component {
subscribeUser(privacy) {
const { dispatch, readingPlan, auth } = this.props
if (!auth.isLoggedIn) window.location.replace(`/sign-in`)
// if user isn't subscribed, then subscribe!
if (!readingPlan.subscription_id) {
dispatch(ActionCreators.readingplanSubscribeUser({ id: readingPlan.id , private: privacy }, auth.isLoggedIn)).then(() => {
// redirect to plan
this.goToPlan()
})
} else {
// user already subscribed
this.goToPlan()
}
}
render() {
return (
<div className='plan-privacy-buttons text-center'>
<p className='detail-text'><FormattedMessage id="plans.privacy.visible to friends?" /></p>
<div className='yes-no-buttons'>
<a className='yes solid-button green' onClick={this.subscribeUser.bind(this, false)}><FormattedMessage id="ui.yes button"/></a>
<a className='no solid-button gray' onClick={this.subscribeUser.bind(this, true)}><FormattedMessage id="ui.no button" /></a>
</div>
</div>
)
}
}
export default SubscribeUserDialog
In your case, you would have the onClick attached to your links.
Does this help you?

Resources