How to use multiple setState sequentially one after another in ReactJS - javascript

I just got to know that setState is a async call.
I want to use the result updated by setState in a very next line but as it is a async call I am getting old result of state. I check lot of solutions but they are mostly class based react and I am using functional based component, class based component setState do have a second args which can be use as a callback but what about functional based?
How & how many approaches do we have to tackle this issue.
here is my code, I want to use filtersAdded state result in a next line to update another state...
const { data, setData } = useContext(defaultData);
const [pestleFilters, setPestleFilters] = useState([]);
const [filtersAdded, setFiltersAdded] = useState([]);
let flag = 0;
let availableFilters = [];
const [toggleFilter, setToggleFilter] = useState(false);
const addFilter = (val) => {
let filters = [...filtersAdded];
filters.push(val);
setFiltersAdded(filters);
let pestleFiltersAux = [...pestleFilters].filter((item) => {
if (!filters.includes(item)) {
return { item };
}
});
setPestleFilters(pestleFiltersAux, () => {
console.log(filtersAdded);
});
let temp = data.filter((d) => filtersAdded.includes(d.pestle));
console.log(temp);
};

Why don't you use filters variable if it is equal to filtersAdded ?
let temp = data.filter((d) => filters.includes(d.pestle));
console.log(temp);
You can't access a state that you just set in the same function, one way you could do this is by creating a useEffect that listen to your state that you just set, like in the example below:
const [state, setState] = useState(0)
const [otherState, setOtherState] = useState(0)
const func = () => {
setState(3)
}
useEffect(() => {
setOtherState(state + 1)
}, [state])
Once you will call the func(), the state will be updated to 3 and useEffect will throw his function because it listen to "state", so otherState will be updated to 4.

Related

Lodash throttle not working correctly in React

I have a function that filters through some state and renders out the result for a search request.
const handleSearch = (value: string) => {
const searchResultData = users.filter((userId) => user.id.startsWith(value));
setSearchResult(searchResultData);
};
I am trying to work with lodash.throttle library to cause a delay before the request is sent. So we don't have a request go out every time a user types.
const handleSearch = useCallback(throttle((value: string) => {
const searchResultData = users.filter((userId) => user.id.startsWith(value));
setSearchResult(searchResultData);
}, 2500), []);
This works in delaying input as expected but for some reason, the user.filter method doesn't run, and so the state isn't updated with the search result. I believe the problem might be from the useCallback hook, but the throttle function is dependent on it to run. Any ideas on how I can work around this problem?
If your throttled/debounced handler uses props or state, like this:
const { fetcherFunctionFromProps } = props;
const eventHandler = async () => {
const resp = await fetcherFunctionFromProps();
};
const debouncedEventHandler = useMemo(
() => throttle(eventHandler, 300)
), [fetcherFunctionFromProps]);
And it doesn't work,
you can refactor it to the following:
const { fetcherFunctionFromProps } = props;
const eventHandler = async (fetcher) => {
const resp = await fetcher();
};
const debouncedEventHandler = useMemo(() => throttle(eventHandler, 300), []);
...
<Component onClick={() => debouncedEventHandler(fetcherFunctionFromProps)}>

useEffect re-render different values making impossible build setting screen using async storage

i'm quite new to react-native, i'm trying to implementing a setting screen in my recipe search app. basically the user can choose different filter to avoid some kind of food (like vegan or no-milk ecc.), i thought to make an array with a number for each filter and then in the search page passing the array and apply the filter adding piece of strings for each filter. the thing is: useEffect render the array i'm passing with async-storage empty on the first render, it fulfill only on the second render, how can i take the filled array instead of the empty one?
const [richiesta, setRichiesta] = React.useState('');
const [data, setData] = React.useState([]);
const [ricerca, setRicerca] = React.useState("");
const [preferenza, setPreferenza] = React.useState([]);
let searchString = `https://api.edamam.com/search?q=${ricerca}&app_id=${APP_ID}&app_key=${APP_KEY}`;
useEffect(() => {
getMyValue();
getPreferences(preferenza);
},[])
const getMyValue = async () => {
try{
const x = await AsyncStorage.getItem('preferenza')
setPreferenza(JSON.parse(x));
} catch(err){console.log(err)}
}
const getPreferences = (preferenza) => {
if(preferenza === 1){
searchString = searchString.concat('&health=vegan')
}
else { console.log("error")}
}
//useEffect
useEffect(() => {
getRecipes();
}, [ricerca])
//fetching data
const getRecipes = async () => {
const response = await fetch(searchString);
const data = await response.json();
setData(data.hits);
}
//funzione ricerca (solo scrittura)
const onChangeSearch = query => setRichiesta(query);
//funzione modifica stato di ricerca
const getSearch = () => {
setRicerca(richiesta);
}
//barra ricerca e mapping data
return(
<SafeAreaView style={styles.container}>
<Searchbar
placeholder="Cerca"
onChangeText={onChangeSearch}
value={richiesta}
onIconPress={getSearch}
/>
this is the code, it returns "error" because on the first render the array is empty, but on the second render it fills with the value 1. can anyone help me out please?
By listening to the state of the preferenza. You need to exclude the getPreferences(preferenza); out of the useEffect for the first render and put it in it's own useEffect like this:
...
useEffect(() => {
getMyValue();
}, [])
useEffect(() => {
if( !preferenza.length ) return;
getPreferences(preferenza);
}, [preferenza])
i forgot to put the index of the array in
if(preferenza === 1){
searchString = searchString.concat('&health=vegan')
}
else { console.log("error")}
}
thanks for the answer tho, have a nice day!

React useState how it works? How can It be with vanila js?

Want to understand, how it can be built with pure js.
I expect it something like that:
const useStatePureJs = () => {
let state = null;
const setState = (value) => {
state = value
};
return [
state,
setState
];
};
const [state, setState] = useStatePureJs();
setState(5);
console.log(state);
But ofc my console.log will return null, because of scope initialization.
How I can make it be updated with pure js implemetation? Value I mean.
Do I need a watcher or something?
What about this? 😊
Seems to work as you would expect.
const useStatePureJs = () => {
let state;
const getState = () => {
return state
}
const setState = (x) => {
state = x
}
return [ getState, setState ]
}
const [ getState, setState ] = useStatePureJs()
setState(1)
console.log(getState()) // 1
setState(2)
console.log(getState()) // 2

React w/hooks: prevent re-rendering component with a function as prop

Let's say I have:
const AddItemButton = React.memo(({ onClick }) => {
// Goal is to make sure this gets printed only once
console.error('Button Rendered!');
return <button onClick={onClick}>Add Item</button>;
});
const App = () => {
const [items, setItems] = useState([]);
const addItem = () => {
setItems(items.concat(Math.random()));
}
return (
<div>
<AddItemButton onClick={addItem} />
<ul>
{items.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
};
Any time I add an item, the <AddItemButton /> gets re-rendered because addItem is a new instance. I tried memoizing addItem:
const addItemMemoized = React.memo(() => addItem, [setItems])
But this is reusing the setItems from the first render, while
const addItemMemoized = React.memo(() => addItem, [items])
Doesn't memoize since items reference changes.
I can'd do
const addItem = () => {
items.push(Math.random());
setItems(items);
}
Since that doesn't change the reference of items and nothing gets updated.
One weird way to do it is:
const [, frobState] = useState();
const addItemMemoized = useMemo(() => () => {
items.push(Math.random());
frobState(Symbol())
}, [items]);
But I'm wondering if there's a better way that doesn't require extra state references.
The current preferred route is useCallback, which is the same as your useMemo solution, but with additional possible optimizations in the future. Pass an empty array [] to make sure the function will always have the same reference for the lifetime of the component.
Here, you also want to use the functional state update form, to make sure the item is always being added based on the current state.
const addItem = useCallback(() => {
setItems(items => [...items, Math.random()]);
}, []);

react useEffect comparing objects

I am using react useEffect hooks and checking if an object has changed and only then run the hook again.
My code looks like this.
const useExample = (apiOptions) => {
const [data, updateData] = useState([]);
useEffect(() => {
const [data, updateData] = useState<any>([]);
doSomethingCool(apiOptions).then(res => {
updateData(response.data);
})
}, [apiOptions]);
return {
data
};
};
Unfortunately it keeps running as the objects are not being recognised as being the same.
I believe the following is an example of why.
const objA = {
method: 'GET'
}
const objB = {
method: 'GET'
}
console.log(objA === objB)
Perhaps running JSON.stringify(apiOptions) works?
Use apiOptions as state value
I'm not sure how you are consuming the custom hook but making apiOptions a state value by using useState should work just fine. This way you can serve it to your custom hook as a state value like so:
const [apiOptions, setApiOptions] = useState({ a: 1 })
const { data } = useExample(apiOptions)
This way it's going to change only when you use setApiOptions.
Example #1
import { useState, useEffect } from 'react';
const useExample = (apiOptions) => {
const [data, updateData] = useState([]);
useEffect(() => {
console.log('effect triggered')
}, [apiOptions]);
return {
data
};
}
export default function App() {
const [apiOptions, setApiOptions] = useState({ a: 1 })
const { data } = useExample(apiOptions);
const [somethingElse, setSomethingElse] = useState('default state')
return <div>
<button onClick={() => { setApiOptions({ a: 1 }) }}>change apiOptions</button>
<button onClick={() => { setSomethingElse('state') }}>
change something else to force rerender
</button>
</div>;
}
Alternatively
You could write a deep comparable useEffect as described here:
function deepCompareEquals(a, b){
// TODO: implement deep comparison here
// something like lodash
// return _.isEqual(a, b);
}
function useDeepCompareMemoize(value) {
const ref = useRef()
// it can be done by using useMemo as well
// but useRef is rather cleaner and easier
if (!deepCompareEquals(value, ref.current)) {
ref.current = value
}
return ref.current
}
function useDeepCompareEffect(callback, dependencies) {
useEffect(
callback,
dependencies.map(useDeepCompareMemoize)
)
}
You can use it like you'd use useEffect.
I just found a solution which works for me.
You have to use usePrevious() and _.isEqual() from Lodash.
Inside the useEffect(), you put a condition if the previous apiOptions equals to the current apiOptions. If true, do nothing. If false updateData().
Example :
const useExample = (apiOptions) => {
const myPreviousState = usePrevious(apiOptions);
const [data, updateData] = useState([]);
useEffect(() => {
if (myPreviousState && !_.isEqual(myPreviousState, apiOptions)) {
updateData(apiOptions);
}
}, [apiOptions])
}
usePrevious(value) is a custom hook which create a ref with useRef().
You can found it from the Official React Hook documentation.
const usePrevious = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
If the input is shallow enough that you think deep equality would still be fast, consider using JSON.stringify:
const useExample = (apiOptions) => {
const [data, updateData] = useState([]);
const apiOptionsJsonString = JSON.stringify(apiOptions);
useEffect(() => {
const apiOptionsObject = JSON.parse(apiOptionsJsonString);
doSomethingCool(apiOptionsObject).then(response => {
updateData(response.data);
})
}, [apiOptionsJsonString]);
return {
data
};
};
Note it won’t compare functions.
If you're real sure that you cannot control apiOptions then just replace native useEffect with https://github.com/kentcdodds/use-deep-compare-effect.
It's reallllly so simple in some case!
const objA = {
method: 'GET'
}
const objB = {
method: 'GET'
}
console.log(objA === objB) // false
Why objA not equal with objB? Coz JS just compare their address right? They are two diffirent obj. That's we all know!
The same as React hooks does!
So, also as we all know, objA.method === objB.method right? Coz they are literal.
The answer comes out:
React.useEffect(() => {
// do your facy work
}, [obj.method])
You can use useDeepCompareEffect, useCustomCompareEffect or write your own hook.
https://github.com/kentcdodds/use-deep-compare-effect
https://github.com/sanjagh/use-custom-compare-effect
One other option, if you have the ability to modify doSomethingCool:
If know exactly which non-Object properties you need, you can limit the list of dependencies to properties that useEffect will correctly interpret with ===, e.g.:
const useExample = (apiOptions) => {
const [data, updateData] = useState([]);
useEffect(() => {
const [data, updateData] = useState<any>([]);
doSomethingCool(apiOptions.method).then(res => {
updateData(response.data);
})
}, [apiOptions.method]);
return {
data
};
};

Categories