How can i attach something to happen on setState const - reactjs

i have
const [items, setItems] = useState([])
how can I add one more action to happen when firing setItems(), so something like (which is not valid)
const setItems = (items) =>{
// do something first
// then call the setItems
}
it's possible to pass it to another function first then call the actual setItems() later, but I was wondering if its possible to override the original setItems function.

you can add an intermediate function as call setItems from that function.
const handleSetItems = (params) => {
setItems (prevItems) =>{
// do something first
// then call the setItems
return prevItems; // data to be set in items state
}
}
Or you can do within setter function as following
setItems (prevItems =>{
// do something first
// then call the setItems
return prevItems;
})

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.

Why is useState value null inside function block?

I know there is a scoping issue here. I just can't find it. Why is 'items' null in the searchItems() block?
export const useStore = () => {
const [items, setItems] = useState(null)
const setItemsFromApi = () => {
//call api to get data, then
setItems(data)
}
const searchItems = (query) => {
//use the local data and filter based on query
//'items' IS NULL IN THIS SCOPE
items.filter(() => {})
}
console.log(items) // 'items' HAS UPDATED VALUE AFTER setItemsFromApi() IN THIS SCOPE
return {setItemsFromApi, searchItems}
}
Use store like this. (NOTE: I left out the rendering of the items in the list because that part works fine. Just focusing on why the onClick doesn't use the already loaded items to filter with.)
export default function DataList(props) => {
const store = useStore();
useEffect(() => {
store.setItemsFromApi()
}, [])
const runSearch = (query) => {
store.searchItems(query)
}
return <button onClick={runSearch('searchTerm')}
}
I even tried passing it as a callback dependency, but it's still null
const searchItems = useCallback((query) => {
//'items' IS STILL NULL IN THIS SCOPE
items.filter(() => {})
}, [items])
From the code you posted,
const store = useStore()
store.setItemsFromApi()
...
store.searchItems(query)
the issue may be because you are doing an async operation (calling the API), but the search is not waiting for the result of the fetch call. So, when you do the
store.searchItems(query)
, the store is null and only changes its value later.
In a nutshell, the state wasn't refreshing after triggering a search because I had a "debounce" useRef function running within the component when the onChange event was fired, even though this was a local data search. I guess this interrupted the re-render event. So I removed it.

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

Why is React useState dependency empty in callback?

The dependency used in the useCallback is coming as null even though it is populated when calling it outside of the useCallback. I even tried removing the useCallback and used the data variable inside a regular function. It is still null. Any idea why this is happening?
const [data, setData] = useState(null);
useEffect(() => { //on page load
const data = fetchData();
setData(data)
}, []);
const func = useCallback(async (payload) => {
console.debug(data); //null
if (data) //call api with payload
}, [data]);
console.debug(data); //correct population of data
return <MyComponent onSubmit={func} /> //passed to and called from second child down from here
Do it like this
Pass the data as a parameter in useState to the function.Never mind i don't have data.So i used 'abc'.
useEffect(() => { //on page load
const data = 'abc';
setData(data)
func(data)
}, []);
Next step take the data as a parameter in function.
const func = (data) => {
console.log(data); //now it will have abc
};
The only way I could get this to work in all cases was to move func() down to the child...where it's used. And because data is used in both parent and children, I passed it down to the children and pushed any modifications to it back up to the parent after running func() so the modified data could be set() in the parent.

React how to use state in passed function

I am using React context to pass down state. When my state changes, it also changes in the child Component (console logs shows new value), but when this state is used in a function, it doesnt update there (console.log shows old value).
Do I need to rerender the function? How?
const {user, userInfo, ref} = useSession(); <-- wrapper for useContext
console.log(userInfo); <--- correct, updated value
const haalDataOp = async () => {
console.log(userInfo.enelogic); <--- old value displaying
...
}
I am using the function haalDataOp from a button (onClick)
As someone already mentioned, I could use useRef, but I dont understand why. Why does this simple example work (extracted from https://dev.to/anpos231/react-hooks-the-closure-hell-71m), and my code doesnt:
const [value, setValue] = useState(1);
const handleClick = useCallback(
() => {
setValue(value + 1)
},
[value],
);
I also tried using useCallback (with userInfo in the dep array) in my example but that doesnt do the trick.
const ... userInfo ... is a constant, so in a Component like following:
console.log('render', userInfo.enelogic) // different value in each render
const haalDataOp = async () => {
console.log('before', userInfo.enelogic) // correct old value
await update()
console.log('after', userInfo.enelogic) // still the same old value
}
return <button onClick={haalDataOp} />
...it would log:
render old
before old
after old
render new
...because the userInfo inside haalDataOp is a closure referencing to the value from the original render. If you need to access a mutable reference that would point to the up-to-date value from a future render instead, you can useRef:
const userInfoRef = useRef()
userInfoRef.current = userInfo
console.log('render', userInfo.enelogic) // different value in each render
const haalDataOp = async () => {
console.log('before', userInfoRef.current.enelogic) // old value
await update()
console.log('after', userInfoRef.current.enelogic) // should be new value
}
return <button onClick={haalDataOp} />
However, there might be a race condition and/or the execution of the 'after' code happens deterministically BEFORE the next render, in which case you will need to use some other trick...
I suspect that the const {ref} = useSession() is needed for exactly this situation, so please read the documentation.

Resources