Background
I am new to ReactJS and needs to create a search field with autocomplete that:
Allows free type
Each user input trigger API request to return a list of matching array
The auto-suggestions should show the latest result
Problem
API response returns in a non-linear order; 1st request could respond later than 2nd request, this caused the State to store not the latest request.
export const SearchBar = () => {
const [state, setState] = useState({list: []})
const apiHandler = (term) => {
axois.post('www.abc.com',term)
.then((r)=> {setState({list: r.data})})
}
return(
...
)}
What would be the ways to resolve this?
Thank you.
as already suggested you should avoid hitting an api for every keystroke with a debouncing strategy.
Anyway if you don't care about stressing your server with too many requests you can make use of a ref hook to make sure that the response from the api is still relevant.
I have created a working example with plenty of comments here:
https://codesandbox.io/s/awesome-leakey-kil2e?file=/src/App.js
You should not hit an api on every user input. You have to debounce that function so it delays the call and you can minimize the number of request made to api with good UX.
Lodash Debounce
Also if you are using react you can use this module
use-debounced-effect
you can pass your input variable as dependency.
Related
According to the thread below,
useCustomHook being called on every render - is something wrong with this
It says it is completely normal to keep calling the custom hook function every time React re-renders.
My questions are, if it affects on a performance side when returning an array from this Custom Hook function( Not when fetching API and receiving data ) which contains a lot of values.
If so, how to prevent it ( How to let this Custom Hook function run only once )?
Here is my Custom Hook code, it returns an array which contains around 5000 string values.
function FetchWords(url: string) {
const [data, setData] = useState<string[]>([]);
useEffect(() => {
fetch(url)
.then((words) => words.text())
.then((textedWords) => {
setData(textedWords.replace(/\r\n/g, "\n").split("\n"));
});
}, []);
const expensiveData = useMemo(() => data, [data]);
return expensiveData;
}
export default FetchWords;
My Main js
const wordLists: any[] = useFetch(
"https://raw.githubusercontent.com/charlesreid1/five-letter-words/master/sgb-words.txt"
);
CustomHooks should start with word use...
You don't need useMemo in your hook, simply return data state.
Your hook makes the fetch call only once, so no problem there as the effect has empty dependency, so it runs once after first render.
The hook stores the array of 5000 entries once in data state and returns the same reference each time your custom hook is called during component re-renders. There is no copy operation, so you don't need to worry about that.
If you only want to fetch 100 entries for example, then your backend needs to provide that api.
Hope this resolves your queries as it is not very clear what is your doubt.
If you are worried about bringing all this data at the same time, you can indicate from the backend that they send you a certain number of records and from the frontend you can manage them with the pagination.
the use of useMemo is superfluous.
the useEffect that you are using will only be rendered ONCE, that is, it will only call the 5,000 registers that you mention only once
If I want to call API after the first rendering of component, I know we have useEffect hook to call the API method. (I am talking about functional components only. No class component).
Is there any way, I can call the API before my component renders the first time.
The reason for this question is, If some UI part is dependent on API, I do not want to show any incomplete information to the user on the first render also, which will be changed once I get the data from API.
This seems to be a bad experience with UI.
Edit: I got a couple of advice to use useLayoutEffect or any consumable flag to check if it is rendered or not. I have checked useLayoutEffect does not work, and by using the consumable flag, we are increasing the complexity only.
Do we have any better way for this?
I think useLayoutEffect can be used for something like this, by passing in an empty array as second argument. useLayoutEffect(() => {...}, []);
Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
Although you can always fetch the data in the parent component and pass it as props. Or - if you don't mind it being an experimental feature for now - React Suspense is trying to solve this exact problem.
There are no correct ways to make API call before component rendered from the same component.
You may preferred make API call in parent component and render presentation component when and only when have consumable data.
Another workaround for such case is keep consumable flag inside component, make request inside useEffect, render nothing or some kind loader and render something only when request completed with success.
on calling api it is not responding exact on its first render but giving exact response when it's being hit second time
You can have a spinner or loading component be rendered first conditionally (isLoading for example):
if(isLoading) return <Spinner />
and have the api call set (isLoading) to false on status 200 for example.
Just came across something, which may help someone in future. So we can use some library but the specific one I would mention here is React Query
React query does exactly what we are trying to achieve, the hooks like useQuery fetch data as soon as rendering starts so you don’t have to wait until react loads the entire component as follows
// with react query
const { status, data, error, isFetching } = useQuery(
['data'],
async () => {
const data = await (
await fetch(`${API_BASE_URL}/data`)
).json()
return data
}
)
// without react query
useEffect(() => {
try {
setLoading(true)(async () => {
const data = await (await fetch(`${API_BASE_URL}/data`)).json();
setData(data);
})();
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}, []);
Here is the article link if you want to read
First off some description of what I need to achieve. I show information in front-end (React) that mostly corresponds to database rows and the user can do regular CRUD operations on those objects. However, I also add some dummy rows into the JSON that I send to front-end because there are some objects that are "defaults" and should not be inserted into the database unless the user actually wants to modify them. So what I need to do is once a user wants to modify a "default" object in front-end, I first have to POST that object to a specific endpoint that copies it from constants to the database and only then follow that up with the request to modify that row.
Secondly, the architecture around this. For storing the state of the rows in front-end I'm using Redux via easy-peasy and I have a thunk for doing the first saving before modifying. Once a user wants to edit a "default" object anywhere in the UI (there are about 20 different ways of editing an object), the flow of the program looks something like this:
User edits something and presses "save"
Thunk is called in the save function and awaited
Thunk POSTs to backend to insert the object into database and return the corresponding row
Backend responds with the ID-s of the rows
Thunk calls action and updates these objects in store with correct ID-s
Thunk returns and the function pointer moves back to the modifying function
The modifying function makes another request with the correct ID-s
The modifying function updates the store with the modified values
Now, the problem I run into is from step 5 to 7, because the component looks basically like this:
const Foo = () => {
const insertToDatabaseIfNecessary = useStoreActions((actions) => actions.baz.insertIfNecessary)
const items = useStoreState((state) => state.baz.items);
const onSave = async () => {
await insertToDatabaseIfNecessary();
// do the actual modifying thing here
axios.post(...items);
}
return (
<button onClick={onSave}>Save!</button>
);
}
If you know functional components better than I do, then you know that in onSave() the insertToDatabaseIfNecessary() will update the values in Redux store, but when we get to the actual modifying and post(...items) then the values that are POSTed are not updated because they will be updated in the next time the component is called. They would be updated if this was a class-based component, but easy-peasy has no support for class-based components. I guess one way would be to use class-based components and Redux directly but I have feeling there might be a different pattern that I could use to solve my issue without resorting to class-based components.
The question: Is there a sane way of doing this with functional components?
Thunks in easy-peasy can handle asynchronous events, so you should put your axios post in there e.g.
insertToDatabaseIfNecessary : thunk(async (actions, payload) => {
// First update the data on the server
await axios.post(payload.items);
// Assuming that the post succeeds, now dispatch and action to update your store.
// (You'd want to check your post succeeded before doing this...)
actions.updateYourStoreData(payload);
})
This easy-peasy thunk will wait for the async post to finish, so you can use the action as follows in your Foo component:
insertToDatabaseIfNecessary();
You will not need to await it or use the onSave function in your Foo component.
I am using graphql subscriptions to get a stream of live messages. The question I have is how to combine this with a query as there are messages from the past.
My example is I have a MessagesQuery component that fetches all previous messages, but to display newer messages I am using a MessageSubscription component.
Can anyone explain the best approach for this? I found this GitHub issue but unfortunately no feedback has been. given.
That issue describes the typical approach -- use the Query component or useQuery hook and call subscribeToMore to tack on a subscription. That user's problem seemed to be that the subscription unsubscribes on unmount but that is appropriate and expected behavior.
Example usage:
const { subscribeToMore, ...result } = useQuery(MESSAGES_QUERY, otherOptions)
subscribeToMore({
document: MESSAGES_SUBSCRIPTION,
updateQuery: (prev, { subscriptionData }) => {
// merge the subscription data into the query result
},
})
See the docs for additional details.
when you click advanced sports search button I need to display drawer with my api values.
but right now when I map my redux state with component state I am getting an error.
Actions must be plain objects. Use custom middleware for async actions.
can you tell me how to map my state.
so that in future I can fix all my redux issues by myself.
providing code snippet and sandbox below.
all my map state is done in tab-demo.js
https://codesandbox.io/s/rlpv50q8qo
getSportsPlayerHistory = values => {
this.props.fetchHistorySportsDatafromURL();
};
toggleDrawer = (side, open) => () => {
if (open === true) {
this.getSportsPlayerHistory();
}
this.setState({
[side]: open
});
};
const mapDispatchToProps = dispatch => {
return {
onDeleteAllSPORTS: () => {
// console.log("called");
dispatch(deleteAllPosts());
},
addFavoriteSPORTSs: data => {
dispatch(addFavoriteSPORTSs(data));
},
fetchHistorySportsDatafromURL: () => {
dispatch(fetchHistorySportsDatafromURL());
}
};
};
Actions need to return plain objects, your fetchHistorySportsDatafromURL action returns a function. If you make your history reducer function async then you can make an async function to make your API call there and return the result to state.
API call in reducer
This works, but isn't ideal as you want your reducers to be pure functions, as-in, no side-effects, same input always produces the same output
You can also make the API request in the component's callback handler asynchronously and pass the result to the dispatched action.
API call in component then dispatched in action
This is a good solution and works great for small projects, but couples network business logic into your UI display components, which also isn't as ideal since it reduces code re-usability.
If you still want to keep your API logic separate from your component (which is a good thing), redux-thunk is a way to create asynchronous action creators, which is very similar to the pattern of your original code.
API call in action using redux-thunk
This is the most ideal as it completely de-couples business logic from your UI, meaning you can change back-end requests without touching front-end UI, and other components can now also use the same action. Good DRY principal.
Not really sure what you wanted to do with the new state, but this should get you to a good spot to handle that in your mapStateToProps function.