Rendering search results in react - reactjs

In my webapp, I have a Navbar with a searchbar. I want to be able to enter a search query, make an API call, and render the search results in a ResultsPage component. Think just a typical website with a searchbar at the top as an example.
I am having trouble with what approach is best for this. Should I get the query string and call the api in the Navbar component, and then pass the result object in the ResultsPage for displaying the elements? Or should instead take in the search query in the Navbar, and then pass the string to the ResultsPage component where I call the api and display the elements there?
Some guidance would be appreciated, thank you.

This is the simpliest example. No loading, and error processing.
const App = () => {
const [query, setQuery] = React.useState("");
const [result, setResult] = React.useState("");
React.useEffect(() => {
Api.invokeQuery(query)
.then(result => setResult(result));
}, [query]);
return (
<div>
<Navbar value={query} onChange={setQuery} />
<ResultsPage> { result } </ResultsPage>
</div>
)
}
As a conclusion, you should handle the state at the parent levels of Navbar and ResultPage so that the state can be shared by both components.

You can call the API with a GET request in the body of the function onChange() in react and then render a carousel combobox just under the searchbar in a SearchForm JSX class <input type="text" value={this.state.value} onChange={this.handleChange} />

You might use Redux to share state between all components which are not inherited as the following.
-- NavBar.js --
...
const dispatch = useDispatch();
const onValueChanged = (e) => {
fetchDataFromAPI(e.target.value)
.then((res) => {
dispatch('SEARCH_RESULT_CHANGED', res);
})
.catch(e=>{
console.error(e);
}
}
-- reducer --
...
swicth(action.type){
case 'SEARCH_RESULT_CHANGED':
return {...state, value: action.value}
...
guess the above reducer was combined as "state.search"
-- ResultsPage.js --
...
const searchResult = useSelector(state=>state.search)
You can use searchResult.value in every components.
Of course, it will be better to use Redux-saga or Thunk than to call fetchDataFromAPI function directly in the NavBar component.

Related

React hooks not showing correct data when component is rendered

I have created a hook in a component as below.
useEffect(() => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
getStatusCode(response.data.code);
console.log("responseCode",responseCode);
getMatchdata(response.data.result);
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
})
.catch((error) => console.log(error));
},[]);
This is a state function used in the above effect
const [info, setInfo] = useState();
I expected the above useEffect should return me some data in the below block
<div> {info} </div>
but it is showing wrong data, whereas I have created another function to trigger on Refresh button as
function refresh() {
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
}
this function is returning me correct data. I want to create a functionality that will dynamically update the div element with change in state of {info}, by default when the page is loaded first, it should fetch data from the endpoint used here only. I'm new to React. Where I'm going wrong and how do I achieve it?
I don't want to say this is wrong, but this seems like an atypical approach from what I've seen in the wild. Specifically I am talking about storing a JS/JSX or TS/TSX element in a state object. I have more commonly seen a value stored in that type of variable and that value changing when necessary via the set dispatch function. Then the state object is passed to the component who needs it to do something. In react, when the value of that state object changes, it will cause the component who uses it to re-render. If I were coding this, this is what my code would look like.
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
Unless you only specifically want something to happen once at component mount, you would not use useEffect() like you did in your code. If the decision to refresh were coming from an external object with state instead of the refresh button, you could add that object whose state changes to the dependency array of the useEffect function. This would cause the refresh to run any time that object's state value changes. In the code above, getData() (which might need to be async) will only run when called. Then you have a component called divComponent which is expecting info to have value. When rendering this component you would want a null check like I coded below. Finally the refreshButton component will call getData() when it is clicked.
Then in your code that renders this, I would have something like this:
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
The code above will check if the state object info has value, and if it does it will render the divComponent with your data values. If it does not, instead it will show the p tag explaining that there is no data. Either way it will render the refreshButton, which would run the getData() function again when clicked.
** EDIT **
Based on your comment, here is another approach so you can have a value on page load and update when necessary:
import {useState, useEffect} from "react";
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
useEffect(()=> {
getData();
}, [])
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
export const Page = () => (
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
);
your method is quite complex. I believe you need to add your MatchData Component Inside the div in this way.Also Don't Need To Call State Method setInfo() in useEffect hook.Only responseCode and matchdata Needed that is already adjusted by you in useEffect Hook.

React, how to make state work between childs?

I am trying to figure out how to make a searchFilter work. Here is my situation:
In App.js I hold a state for my items that i get from an api.
I also hold a state for the SearchFilter.
The items arrive and I can render them just fine.
Further, in App.js, I render the items and also a search component. So my code looks something like this:
const App = () => {
const [items, setItems] = useState([])
const [searchFilter, setSearchFilter] = useState("")
useEffect(() => {
const fetchItems = async () => {
// FETCHING ITEMS AND SETTING VIA setItems...
// This part works as expected
}
fetchItems()
},[])
return (
<>
<SearchBar setSearchFilter={setSearchFilter} />
<RenderItems items={items} searchFilter={searchFilter} />
</>
)
}
The problem I face is, that the searchFilter remains undefined in the RenderItems component. Why?
It gets updated correctly in App.js, but somehow doesn't make it's way to RenderItems
Inside component SearchBar:
const SearchBar = ({setSearchFilter}) => {
return (
<>
<input type="text" placeholder="Search" onChange={(e) => setSearchFilter(e.target.value) }/ >
</>
)
{
Any clues?
Thank you all for the replies #Mandeep Kaur and #KcH
I found the problem was in the data that came from the api when trying this scenario out in a codesandbox.
I keep the link here for future reference: https://codesandbox.io/s/nostalgic-booth-p1tqsv?file=/src/App.js
Closed from my side.
I think this happens because RenderItems component is not re-render after updating the state in SearchBar component.
You can try with adding one useEffect that makes it re-render and it gives the latest data to RenderItems
useEffect(() => {
},[searchFilter])
I am assuming that the updated value you getting in the App.js file.

React functional component with useSelector and useDispatch creates loop

I'm sure this is a case of my brain just not getting it... BUT...
I'm used to using class Components and not functional components in general and with React Redux I'm trying to code a component that dispatches an action. The action of course causes a reducer to update the Redux state (store) as you probably know. Trying to replace mapStateToProps and mapDispatchToProps with useSelector and useDispatch however has me creating a loop... I'm guessing that I'm using useSelector incorrectly.
import { fetchPostsByTerm } from "../../_actions/_postActions";
import { useSelector, useDispatch } from "react-redux";
const payload = { vocabulary: "tags", term: "thiphif" };
export const PostsByTerm = () => {
const dispatch = useDispatch();
dispatch(fetchPostsByTerm(payload));
const posts = useSelector((state) => state.postsByTerm);
return (
<div>
<h1 className="post_heading">Posts</h1>
{posts ? posts.map((post) => <h1>{post.entityLable}</h1>) : <h1>no posts</h1>}
</div>
);
};
maybe I am using it correctly? there are other components updating state on the same page
You must not dispatch directly in the functional component. Instead use a useEffect hook to perform a dispatch. If your objective is to only dispatch the action on initial render, pass on the dependency to useEffect as an empty array
export const PostsByTerm = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchPostsByTerm(payload));
}, [])
const posts = useSelector((state) => state.postsByTerm);
return (
<div>
<h1 className="post_heading">Posts</h1>
{posts ? posts.map((post) => <h1>{post.entityLable}</h1>) : <h1>no posts</h1>}
</div>
);
};
FYI - Commenting here because it was an "aha" moment for me to understand the purpose of the array [] in the useEffect utility. The array is used to house state variables which, when changed, will force execution of the function listed. So in my case actually since I wanted fetchPostsByTerm to execute when the selected term changed (not obvious since my example shows it hardcoded)
useEffect(() => {
dispatch(fetchPostsByTerm(payload));
}, [term])
... was what I eventually went with. And now it's working great! The articles for the selected term get fetch when a new term is selected.

How to get data from the route url to put inside the callback function in React router?

I'm working on my search feature. I want to trigger a callback function in the route to fetch all data before it goes into the search component.
Like this:
<Route path="/search/:query" component={QuestionSearchContainer} onChange={()=>store.dispatch(fetchData(query?)) }/>
here is the QuestionSearchContainer:
const mapStateToProps = (state,ownProps) => {
return {
questions: Object.values(state.entities.questions),
currentUser: state.entities.users[state.session.id],
query: ownProps.match.params.query,
url: ownProps.match.url
}}
But how could I get the query keyword in the search url to put inside my fetchData as a parameter? I want to fetch the data and save it to the redux store before going to the QuestionSearchContainer so that I can get all data for questions in the container.
If you don't want to do the data fetching withing your QuestionSearchContainer component, you can make a higher-order component to wrap it with that does your data fetching for you.
You can easily modify this HOC to only return the Wrapped component when the data finishes loading as well. The loading part of this is assuming that fetchData is a redux thunk action creator . useParams is a hook exported from react-router-dom that gives you access to the match params. useDispatch is a hook exported from react-redux that gives you access to your store's dispatch function.
import { useParams } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { useEffect, useState } from 'react';
const withFetchData = (Component) => ({ children, ...props }) => {
const { query } = useParams();
const [loading, setLoading] = useState(true);
const dispatch = useDispatch();
useEffect(() => {
// Assuming fetchData is a a redux thunk action creator
setLoading(true);
dispatch(fetchData(query)).then(() => {
setLoading(false);
});
}, [query]);
if(loading){
return 'loading...'
}
return <Component {...props} />;
};
const QuestionSearchContainerWithFetchData = withFetchData(
QuestionSearchContainer
);
const Parent = () => {
return (
<Route
path="/search/:query"
component={QuestionSearchContainerWithFetchData}
/>
);
};
Another option is to create a special route that does what you desire. For instance, this OnChangeRoute function would call the callback onChangeParams with the current params every time the params change. In this one, there's a loading prop that you have to pass in as the component itself doesn't care about what you are doing with the params.
import { useEffect, useRef } from "react";
function InnerOnChangeRoute({ loading, onParamsChange, Component, ...rest }) {
const onChangeRef = useRef(onParamsChange);
useEffect(()=>{
onChangeRef.current=onParamsChange;
},[onParamsChange])
useEffect(() => {
onChangeRef.current(rest.match.params);
}, [rest.match.params]);
if(loading){
return 'loading....'
}
return <Component {...rest} />;
}
// A wrapper for <Route> that redirects to the login
// screen if you're not yet authenticated.
function OnChangeRoute({ Component, onParamsChange, loading, ...rest }) {
return (
<Route
{...rest}
render={(data) => (
<InnerOnChangeRoute
Component={Component}
onParamsChange={onParamsChange}
loading={loading}
{...data}
/>
)}
/>
);
}
In general for redux, you have to use dispatch (or mapDispatchToProps in the connector HOC) to run an action that updates the store with your data.
Here are some links that will hopefully help you get redux more under control.
https://redux.js.org/advanced/async-actions
https://redux-toolkit.js.org/usage/usage-guide#asynchronous-logic-and-data-fetching
https://github.com/reduxjs/redux-thunk
Firstly, Route doesn't have an onChange handler. (onEnter was available in previous versions(3 & earlier) of react-router-dom
Since your requirement seems to be specific to a single component(QuestionSearchContainer), using custom hooks or hocs may not be an ideal solution.
You can simply use a useEffect and listen to url changes(query). You can get the query using props.match.params and pass it as an argument to your dispatch callback.
Just make sure to maintain a loading state in redux and render a fallback while data is being fetched.
code snippet
const QuestionSearchContainer = props => {
...
useEffect(() => {
const {query} = props.match.params
console.log(query);
store.dispatch(fetchData(query))
}, [query]);
...
return <div>
{!props.isLoading && <div>My actual question search component with data !</div>}
</div>;
};
export default QuestionSearchContainer;

Update React Context using a REST Api call in a functional component

I am trying to update the context of a React App using data resulted from an API call to a REST API in the back end. The problem is that I can't synchronize the function.
I've tried this solution suggested in this blog post https://medium.com/#__davidflanagan/react-hooks-context-state-and-effects-aa899d8c8014 but it doesn't work for my case.
Here is the code for the textContext.js
import React, {useEffect, useState} from "react";
import axios from "axios";
var text = "Test";
fetch(process.env.REACT_APP_TEXT_API)
.then(res => res.json())
.then(json => {
text = json;
})
const TextContext = React.createContext(text);
export const TextProvider = TextContext.Provider;
export const TextConsumer = TextContext.Consumer;
export default TextContext
And this is the functional component where I try to access the data from the context
import TextProvider, {callTextApi} from "../../../../services/textService/textContext";
function Profile()
{
const text = useContext(TextProvider);
console.log(text);
const useStyles = makeStyles(theme => ({
margin: {
margin: theme.spacing(1)
}
}));
I can see the fetch request getting the data in the network section of the browser console but the context is not getting updated.
I've tried doing this in the textContext.js.
export async function callTextApi () {
await fetch(process.env.REACT_APP_TEXT_API)
.then(res => res.json())
.then(json => {
return json;
})
}
And I was trying to get the data in the Profile.js using the useEffect function as so
const [text, setText] = useState(null);
useEffect(()=> {
setText (callTextApi())
},[])
It's my first time using React.context and it is pretty confusing. What am I doing wrong or missing?
You have a lot of problems here. fetching and changing should happen inside Provider by modifying the value property. useContext receives an entire Context object not only the Provider. Check the following
//Context.js
export const context = React.createContext()
Now inside your Provider
import { context } from './Context'
const MyProvider = ({children}) =>{
const [data, setData] = useState(null)
useEffect(() =>{
fetchData().then(res => setData(res.data))
},[])
const { Provider } = context
return(
<Provider value={data}>
{children}
</Provider>
)
}
Now you have a Provider that fetches some data and pass it down inside value prop. To consume it from inside a functional component use useContext like this
import { context } from './Context'
const Component = () =>{
const data = useContext(context)
return <SomeJSX />
}
Remember that Component must be under MyProvider
UPDATE
What is { children }?
Everything that goes inside a Component declaration is mapped to props.children.
const App = () =>{
return(
<Button>
Title
</Button>
)
}
const Button = props =>{
const { children } = props
return(
<button className='fancy-button'>
{ children /* Title */}
</button>
)
}
Declaring it like ({ children }) it's just a shortcut to const { children } = props. I'm using children so that you can use your Provider like this
<MyProvider>
<RestOfMyApp />
</MyProvider>
Here children is RestOfMyApp
How do I access the value of the Provider inside the Profile.js?
Using createContext. Let's assume the value property of your Provider is {foo: 'bar'}
const Component = () =>{
const content = useContext(context)
console.log(content) //{ foo : 'bar' }
}
How can you double declare a constant as you've done in the Provider?
That was a typo, I've changed to MyProvider
To access it from inside a class based component
class Component extends React.Component{
render(){
const { Consumer } = context
return(
<Consumer>
{
context => console.log(contxt) // { foo: 'bar' }
}
</Consumer>
)
}
}
First thing that I am seeing is that you are not returning the promise within your function which will lead to setting the state to undefined.
I added the return statement below:
export async function callTextApi () {
return await fetch(process.env.REACT_APP_TEXT_API)
.then(res => res.json())
.then(json => {
return json;
})
}
Also your last then-chain could be cleaned up a bit and I am quite sure you can remove the await statement in an async function when returning a promise. It will automatically be awaited:
export async function callTextApi () {
return fetch(process.env.REACT_APP_TEXT_API)
.then(res => res.json())
.then(json => json)
}
Second step would be to have a look at your useEffect hook. You want to setText after the promise from the api call has been resolved. So you have to make the callback function of useEffect asynchronous as well.
useEffect(async ()=> {
const newText = await callTextApi();
setText (newText);
},[])
Third step, would be to look at how to properly use the context api and the useContext hook. The useContext hook takes a context as a parameter but you passed the ContextProvider as the argument.
const text = useContext(TextContext);
The context and the context-provider are two different entities in the React world. Think of the context as state and functionality that you want to share across your application (like a global state), and think about the provider as a react component that manages one context and offers this context state to it's child components.
return(
<TextContext.Provider value={/* some value */}>
{children}
</TextContext.Provider>);
This is how a return statement of a provider component would look like and I think this code is currently missing in your application.

Resources