How to use function containing setState synchronously - reactjs

I want to use a function containing setState() synchronously
but, it's not working
here is my code
const [state, setState] = useState(false)
function handlerChange(someValue) {
//validate value and that result set state
const isValid = REGEXP.test(someValue)
setState(isValid)
}
async function checkValue() {
await handlerChange()
}
function handlerClick() {
checkValue().then(()=>{
if(state){
//some function
}})
}
above code is ommitted in many parts,
but, the gist is that i want to execute some function according to the state in handlerClick().
state is processed asynchronously.
please any advice/help

You can use useEffect with the state parameter like because useState doesn't provide any callback like setState
Ex:
useEffect(() => {
console.log('Do something after state has changed', state);
}, [state])
or You may use a custom hook just for that.

Related

How to get current state value inside an async function

I have created some state at the top level component App() and created a getter method for this state so I could pass it on to any function to be able to get its current state.
App.js
const [searchState, changeScreen] = useState("");
const getSearchState = () => {
console.log("searchState is", searchState);
return searchState;
}
scripts/search.js
export const performSearch = async (searchText, changeScreen, getSearchState) => {
if(searchText) {
console.log("1", getSearchState())
let res = await doSearchQuery(searchText);
console.log("2", getSearchState())
if(res.status) {
// *** getSearchState() should have a value of "loading" here
if(getSearchState() !== "expanded") {
changeScreen("results");
}
}
else {
//
}
}
}
components/SearchComponent.js
import { performSearch } from '../scripts/search';
function SearchHistoryComponent({changeScreen, getSearchState}) {
...
// This method is fired from an onPress()
const performHistorySearch = async (text) => {
changeScreen('loading');
await performSearch(text, changeScreen, getSearchState);
}
...
}
I then pass getSearchState() as a parameter to a standalone asynchronous function in a different script to be able to look up the searchState value but it doesn't seem to be working as intended.
The value I'm getting seems to be the previous value and not the current value at the time getSearchState() is called - as can be seen from the console outputs I have setup:
searchState is expanded
searchState is loading
1 expanded
2 expanded
searchState is results
Am I doing something wrong?
This is exactly how React callback functions work.
That is, every line in your performSearch function is engaged to one searchState value, i.e, "expanded".
If you want to get "loading" from getSearchState(), you need to call getSearchState() again in useEffect by passing searchState or getSearchState in the dependency array.
To be more clear, setting a state value after awaiting a promise works fine, but if you want to pull the newly set state value inside the same function body, it won't work.
That said, you need to listen to the newly set state value in useEffect or just make your code declarative so it behaves according to state changes.
Just to help you understand this better, I've written a quick snack here to show the difference between pulling the state from a function body and a useEffect.
https://snack.expo.dev/h65-cPvIb
Thanks.

useEffect on infinite loop using async fetch function

I am trying to understand why the following useEffect is running in an infinite loop. I made the fetchSchedule helper function to call the getSchedule service (using Axios to query the API endpoint). Reason I did not define this function inside the useEffect hook is because I would like to alternatively also call it whenever the onStatus function is invoked (which toggles a Boolean PUT request on a separate endpoint).
The eslinter is requiring fetchSchedule be added to the array of dependencies, which seems to be triggering the infinite loop.
The way it should work is fetching the data from the database on first render, and then only each time either the value prop is updated or the onStatus button is toggled.
So far my research seems to point that this may have something to do with the way useEffect behaves with async functions and closures. I’m still trying to understand Hooks and evidently there’s something I’m not getting in my code…
import React, { useEffect, useCallback } from 'react';
import useStateRef from 'react-usestateref';
import { NavLink } from 'react-router-dom';
import { getSchedule, updateStatus } from '../../services/scheduleService';
import Status from './status';
// import Pagination from './pagination';
const List = ({ value }) => {
// eslint-disable-next-line
const [schedule, setSchedule, ref] = useStateRef([]);
// const [schedule, setSchedule] = useState([]);
const fetchSchedule = useCallback(async () => {
const { data } = await getSchedule(value);
setSchedule(data);
}, [value, setSchedule]);
const onStatus = (id) => {
updateStatus(id);
fetchSchedule();
console.log('fetch', ref.current[0].completed);
};
useEffect(() => {
fetchSchedule();
}, [fetchSchedule]);
return (...)
Update March 2021
After working with the repo owner for react-usestateref, the package now functions as originally intended and is safe to use as a replacement for useState as of version 1.0.5. The current implementation looks like this:
function useStateRef(defaultValue) {
var [state, setState] = React.useState(defaultValue);
var ref = React.useRef(state);
var dispatch = React.useCallback(function(val) {
ref.current = typeof val === "function" ?
val(ref.current) : val;
setState(ref.current);
}, []);
return [state, dispatch, ref];
};
You would be fine if it weren't for this react-usestateref import.
The hook returns a plain anonymous function for setting state which means that it will be recreated on every render - you cannot usefully include it in any dependency array as that too will be updated on every render. However, since the function is being returned from an unknown custom hook (and regardless, ESLint would correctly identify that it is not a proper setter function) you'll get warnings when you don't.
The 'problem' which it tries to solve is also going to introduce bad practice into your code - it's a pretty way to avoid properly handling dependencies which are there to make your code safer.
If you go back to a standard state hook I believe this code will work fine. Instead of trying to get a ref of the state in onStatus, make it async as well and return the data from fetchSchedule as well as setting it.
const [schedule, setSchedule] = useState([]);
const fetchSchedule = useCallback(async () => {
const { data } = await getSchedule(value);
setSchedule(data);
return data;
}, [value]);
const onStatus = async (id) => {
updateStatus(id);
const data = await fetchSchedule();
};
useEffect(() => {
fetchSchedule();
}, [fetchSchedule]);
Alternatively, although again I wouldn't really recommend using this, we could actually write a safe version of the useStateRef hook instead:
function useStateRef(defaultValue) {
var [state, setState] = React.useState(defaultValue);
var ref = React.useRef(defaultValue);
ref.current = state;
return [state, setState, ref];
}
A state setter function is always referentially identical throughout the lifespan of a component so this can be included in a dependency array without causing the effect/callback to be recreated.

useState function is not working in functional component

I want to change a state (filterConsultantId) with a useState function (setFilterConsultantId) after I trigger a normal function (handleConsultantChange) and I expect the state value is changed when I use the state in another normal function (getActiveLeads). But the state didn't change. Please see my React functional component below:
const TabActiveLeads = ({
...
}) => {
const [filterConsultantId, setFilterConsultantId] = useState('');
//after this arrow function triggered,
const handleConsultantChange = (event) => {
setFilterConsultantId(event.target.value); //and the filterConsultantId should be changed here
//this getActiveLeads function is called
getActiveLeads();
};
const getActiveLeads = () => {
// but the filterConsultantId here is not changed after it should be changed inside handleConsultantChange function
console.log(filterConsultantId);
};
};
export default TabActiveLeads;
I don't understand, why filterConsultantId is not changed inside the getActiveLeads function? Previously it should be changed by calling setFilterConsultantId inside the handleConsultantChange function.
Thanks for any help..
setFilterConsultantId is the asynchronous method, so you can't get the updated value of filterConsultantId right after setFilterConsultantId.
You should get it inside useEffect with adding a dependency filterConsultantId.
useEffect(() => {
console.log(filterConsultantId);
}, [filterConsultantId]);

How to use Async / Await with React hooks?

I am learning React and following a video tutorial. The instructor used class components and I'm using functional components to brush up on hooks concept.
I want to convert this code into a functional one:
return(
<div className={classes.editorContainer}>
<ReactQuill
value={this.state.text}
onChange={this.updateBody}>
</ReactQuill>
</div>
);
}
updateBody = async (val) => {
await this.setState({ text: val });
this.update();
};
I have tried to do this but async doesn't seem to work as I expected. Await is not working for setText.
const [text, setText] = useState('')
const updateBody = async(val) => {
await setText(val)
update()
}
First of all, setText function does not return a promise that you can await and make sure the data is set.
If you want to set the state and make sure you are calling the update function after the value is set, you need to use another hook called useEffect.
By using useEffect you can basically get the latest state when it is updated.
If you don't care about whether the state is set or not you can convert your async function to the following,
const updateBody = (val) => {
setTitle(val)
update()
}
You can use useEffect hook to trigger a function which you want to get triggered after the state has been updated.
const [text, setText] = useState('')
useEffect(() => {
update()
}, [text]); // This useEffect hook will only trigger whenever 'text' state changes
const updateBody = (val) => {
setText(val)
}
Basically you useEffect() hook accepts two arguments useEffect(callback, [dependencies]);
callback is the callback function containing side-effect logic. useEffect() executes the callback function after React has committed the changes to the screen.
Dependencies is an optional array of dependencies. useEffect() executes callback only if the dependencies have changed between renderings.
Put your side-effect logic into the callback function, then use the dependencies argument to control when you want the side-effect to run
you can find more info about useEffect() hook in this article

Invalid hook call when trying to fetch data using useCallback

I'm trying to call useState inside an async function like:
const [searchParams, setSearchParams] = useState({});
const fetchData = () => useCallback(
() => {
if (!isEmpty(searchParams)) {
setIsLoading(true); // this is a state hook
fetchData(searchParams)
.then((ids) => {
setIds(ids); // Setting the id state here
}).catch(() => setIsLoading(false));
}
},
[],
);
There are two states I am trying to set inside this fetchData function (setIsLoading and setIds), but whenever this function is executed am getting the error:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
What is this Rule of hooks I am breaking here?
Is there any way around to set these states from the function?
PS: I only used the useCallback hook here for calling this function with lodash/debounce
Edit: The function is called inside useEffect like:
const debouncedSearch = debounce(fetchSearchData, 1000); // Is this the right way to use debounce? I think this is created every render.
const handleFilter = (filterParams) => {
setSearchParams(filterParams);
};
useEffect(() => {
console.log('effect', searchParams); // {name: 'asd'}
debouncedSearch(searchParams); // Tried without passing arguments here as it is available in state.
// But new searchParams are not showing in the `fetchData`. so had to pass from here.
}, [searchParams]);
The hook rule you are breaking concerns useCallback because you are returning it as the result of your fetchData;
useCallback should be called on top level; not in a callback, like this:
const fetchData = useCallback(
() => {
if (!isEmpty(searchParams)) {
setIsLoading(true); // this is a state hook
fetchData(searchParams)
.then((ids) => {
setIds(ids); // Setting the id state here
}).catch(() => setIsLoading(false));
}
},
[],
);
The code you wrote is equivalent to
const fetchData = () => { return React.useCallback(...
or even
function fetchData() { return React.useCallback(...
To read more about why you can't do this, I highly recommend this blog post.
edit:
To use the debounced searchParams, you don't need to debounce the function that does the call, but rather debounce the searched value. (and you don't actually the fetchData function that calls React.useCallback at all, just use it directly in your useEffect)
I recommend using this useDebounce hook to debounce your search query
const [searchParams, setSearchParams] = React.useState('');
const debouncedSearchParams = useDebounce(searchParams, 300);// let's say you debounce using a delay of 300ms
React.useEffect(() => {
if (!isEmpty(debouncedSearchQuery)) {
setIsLoading(true); // this is a state hook
fetchData(debouncedSearchParams)
.then((ids) => {
setIds(ids); // Setting the id state here
}).catch(() => setIsLoading(false));
}
}, [debouncedSearchParams]); // only call this effect again if the debounced value changes

Resources