How to use Async / Await with React hooks? - reactjs

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

Related

The setItem function is working fine in Reactjs but localStorage.getItem is not working

In the code I change some recipe content and it saves but when I refresh my page it resets everything and the changes are gone
Here is my code
const LOCAL_STORAGE_KEY = "cookingWithKyle.recipes"
function App() {
const [selectedRecipeId, setSelectedRecipeId] = useState()
const [recipes, setRecipes] = useState(sampleRecipe)
const selectedRecipe = recipes.find(
(recipe) => recipe.id === selectedRecipeId
)
useEffect(() => {
const recipeJSON = localStorage.getItem(LOCAL_STORAGE_KEY)
if (recipeJSON) setRecipes(JSON.parse(recipeJSON))
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(recipes))
}, [recipes])
AFAIK, in React useEffect hook's callback is run asynchronously. So, you cannot control which callback is processed first and end before another.
But here, the main reason of your issue is, even if you have recipes as a dependency in your second useEffect, second useEffect's callback will also be fired on initial mount (on page reload), thus in your code you set a value (probably undefined) to your localStorage and then try to get a value, which was already gone.
So, try to run your second useEffect's callback with a condition:
useEffect(() => {
if (recipes) {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(recipes))
}
}, [recipes])
For information, React fires the useEffect hooks callback when it compares dependency list in an array with a previous render (so, try not to use a value of object type as a dependency, otherwise your callback will be fired in every render) and if it sees the difference, it fires useEffect's callback, including the initial render.

How to use function containing setState synchronously

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.

How do I avoid trigger useEffect when I set the same state value?

I use react hook call an API and set the data to state and I still have some control view can change the value, but when I change it, my API function trigger again, it cause my view re render multiple times.
How do I use my fetchData function just like in class component componentDidMount function ?
const [brightness, setBrightness] = useState(0);
useEffect(() => {
fetchData();
});
const fetchData = async () => {
const value = await something(); // call API get the first value
setBrightness(value);
};
return (
<View>
<SomeView value={brightness} onComplete={(value) => setBrightness(value)}
</View>
);
Your useEffect will be triggered on every render because you haven't provided the 2nd argument.
The 2nd argument is what the effect is conditional upon. In your case you only want it to run once so you can provide an empty array.
useEffect(() => {
// Run once
}, [])
Lets say you wanted it to fetch anytime some prop changed, you could write
useEffect(() => {
// run every time props.myValue changes
}, [props.myValue])
See https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

Custom hooks not working properly with useEffect

I wasn't sure on a title for this issue, but I can better explain it here. I have a custom hook that is relying on some information. Part of the information must rely on an async call.
I have three situations happening.
Tried conditionally rendering the custom hook, but react does not like that due to rendering more hooks on a different render.
The custom hook is only mounting once and not passing in the updated information it needs.
I tried passing the dependency to the custom hook and it causes an infinite loop.
Here is a small example of what I'm doing.
Custom Hook:
export function useProducts(options){
const [products, setProducts] = useContext(MyContext)
useEffect(() => {
// only gets called once with `options.asyncValue` === null
// needs to be recalled once the new value is passed in
const loadProducts = async () => {
const data = await asyncProductReq(options)
setProducts(data)
}
loadProducts()
}, []) // if I pass options here it causes the infinite loop
return [products, setProducts]
}
Inside function calling:
export function(props){
const [asyncValue, setValue] = useState(null)
useEffect(() => {
const loadValue = async () => {
const data = await asyncFunc()
setValue(data)
}
loadValue()
}, []}
const options = {...staticValues, asyncValue}
const [products] = useProducts(options)
return (
<h2>Hello</h2>
)
}
I know that I need to pass the options to be a dependency, but I can't figure out why it's causing an infinite reload if the object isn't changing once the async call has been made inside the func.
You were correct in adding options in the dependencies list for your custom hook.
The reason it is infinitely looping is because options IS constantly changing.
The problem is you need to take it one step further in the implementation and make use of the useMemo hook so options only changes when the async value changes, instead of the whole component changing.
So do this:
const options = React.useMemo(() => ({...staticValues, asyncValue}), [asyncValue])

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