I am attempting to setState in a React component using a function callback which is now the recommended way.
It is in the componentDidMount and when I get my jobs data back I need to update the state.
It works when I set it directly but I have attempted may functions callbacks and cannot get it to work.
Sample code provided below with one of my many attempts.
async componentDidMount(){
const jobs = await loadJobs();
this.setState({jobs});
//this.setState((prevState, jobs) => {return {jobs: [prevState,...jobs]}})
}
What is the correct syntax?
You only need to make use of functional setState when you want to update current state based on prevState, in your case it seems like you just want to set the state jobs, you would simply write
this.setState({jobs});
However if you want to use functional setState, you would still write
this.setState((prevState) => {
return {jobs};
})
You need to update the jobs by getting value from prevState and appending the result to the previous state, you would do it like
async componentDidMount(){
const jobs = await loadJobs();
this.setState((prevState) => {
return {jobs: [...prevState.jobs, ...jobs]}
})
}
Related
Can we pass an asynchronous function (example fetchData() ) in a dependancy array of useEffect of a react component ? example :
useEffect(()=>{fetchData().then(data=>{ //do something with data like setting state
)}},[fetchData])
Yes this is fine. Just be aware that a new, potentially overlapping, request will be made every time fetchData changes. If you only want to make this request once use an empty dependency array.
yes for sure but you need to pass empty dependency to avoid overlapping or pass an state array dependency:
useEffect(() => {
async function fetchMyAPI() {
let response = await fetch('api/data')
response = await response.json()
dataSet(response)
}
fetchMyAPI()
}, [])
I am using a hook component and several state variables. I have read about using useEffect() with params to get a kind of callback after updating a state. Example:
export const hookComponent = () => {
const [var, setVar] = useState(null);
useEffect(() => {
//do things
}, [var])
}
In this example, useEffect() would be executed on every setVar() call. In my case, I do not want to execute useEffect() everytime, but only on specific occasions.
I would like to give the setVar() some kind of information which I can use in useEffect() like setVar(newValue, true).
Note: I do not want to store this information in var.
Is there a way to do this?
Like Nizar said, simple conditional check on 'var' in useEffect
If expensive calc you can
const expensiveValue = useMemo(() => {
// other logic here if needed
// could even be simple return var=='x'?true:false, although this would be easier to do in the useEffect hook?
return computeExpensiveValue(var);
},[var]);
useEffect(() => {
//do things
//expensiveValue only changes when you want it to from the memo
}, [expensiveValue])
Thank you sambomartin and Nizar for your input.
For everyone looking for an answer:
After some further research I found 3 possible solutions:
Use a class component. If you really are dependent on that state update to be completed switch to a class component, which allows you to give the setState() a callback as a second param.
Use the useRef hook to determine where your state update is comming from. You can use this information in the useEffect() method.
Get independent from the state. I used this solution and externalized my callback function with the drawback of giving it every parameter on every call, although they are present in the component the states are saved.
As far as I know, the useEffect only triggers if the dependency value changes, not simply by executing setValue.
I offer you three solutions, the first one, close to what you want but without using useEffect hook, the second one is an extension of the first one, that may be required if you need control over the previous state, and the third, more general, like comments say, though it won't be triggered if the state is the same, even if you execute setValue.
First solution: Wrap your set value with another function that definitely controls what may happen after or before the new state:
export default function MyComponent() {
const [state, setState] = useState(null);
const handleChangeSetState = (nextState, flag) => {
if (flag) {
specialUseCaseCb();
}
setState(nextState);
};
return <div>{/* ... */}</div>;
}
Second solution: Wrap your set value with another function, like in the solution 1, and ask for the previous or next state within setState inner callback:
export default function MyComponent2() {
const [state, setState] = useState(0);
const handleChangeSetState = (increment, flag) =>
setState((prevState) => {
const nextState = prevState + increment;
// you may need prevState or nextState for checking your use case
if (flag) {
specialUseCaseCb();
}
return nextState;
});
return <div>{/* ... */}</div>;
}
Third solution: use useEffect hook to follow changes, remember though that setState won't re-trigger useEffect hook if the state is the same:
export default function MyComponent3() {
const [state, setState] = useState("");
// notice that this will only be triggered if state changes
useEffect(() => {
if (state !== "my-special-use-case") return;
specialUseCaseCb();
}, [state]);
return <div>{/* ... */}</div>;
}
So as you probably know, in normal mode, we use update dependencies to get notice when the state updated, like this:
const [val, setVal] = useState();
useEffect(() => {}, [val]]);
But in my case, I have an array in my state and I'm trying to update it in a loop in my useEffect like this:
const [val, setVal ] = useState([...]);
useEffect(() => {
anotherArr.forEach(i => {
// get val and modify some indexes
setVal(modifiedValuesArray);
}
}, []);
In this case, every time forEach loop runs, I'm getting the initial val (I know because val is not a
dependency of useEffect) but if I put it as a dependency, it will update twice. what is the solution for this?
EDIT: Basically, I mean when I update state in a round of loop in useEffect, on the next round, I'm not getting the updated state but the initial state before entering the loop. And I know, that is because of the nature of useEffect which gives us a memorized value of state (since we didn't pass it as a dependency to avoid the additional execution), but what is the solution in these types of scenarios.
I came across this answer:(https://stackoverflow.com/a/59422750/2728431) and it solved my problem.
for getting updated state, (as #usafder said in a comment), we need to pass state as a value in an arrow function just like this:
const [val, setVal ] = useState([...]);
useEffect(() => {
anotherArr.forEach(i => {
setVal(val => {
// modify based on provided val on arrow function
return modifiedValuesArray
});
}
}, []);
Whenever you need to update the state using its current value, you need to send in a function instead to the state setter which would give you the updated current value of the state as a param. So in your case it would be something like below:
const [val, setVal ] = useState([...]);
useEffect(() => {
anotherArr.forEach(i => {
setVal((currVal) => {
let modifiedValuesArray = [];
// your update logic here (use currVal instead of val)
return modifiedValuesArray;
});
});
}, []);
Use setVal only once, not during an forEach loop. setState is async so you can't depend on it like its synchronous. In your example setVal will actually be executed some time in the future.. Do you maybe have a codesandbox example?
EDIT: You don't get updated state on "next round". setState will be executed N times, and will put it in an update queue, and React will probably only update the last setState value for optimisation. Also, your example useEffect will run only once..
I'm using redux-thunk to manage async functions, and I want to use setInterval within an action creator, this is my code:
export const startLobbyPolling = () => dispatch => {
const pollTimer = setInterval(() => {
dispatch(fetchLobby);
}, POLL_TIME);
dispatch({ type: START_LOBBY_POLLING, payload: pollTimer });
};
fetchLobby is another action creator that simply fetch a request and store its data.
but surprisingly it's not working as it only shows START_LOBBY_POLLING action in redux debugger tool and nothing happens afterward. I would appreciate it to know how to use setInterval with redux.
I would suggest to not use redux as a polling manager.
Instead you should have a component / container that does that for you. The reason being is, that when the component will get unmounted, also your polling will be properly canceled
const PollContainer = ({ fetchLoby, children }) => {
useEffect(() => {
const ptr = setInterval(fetchLoby, POLL_TIME)
return () => clearInterval(ptr)
}, [fetchLoby])
return children
}
you can now use your PollContainer and as long as it is mounted, it will keep fetchLoby.
Seems like i should had called the function not just pass it as an argument to dispatch
dispatch(fetchLobby); -->
dispatch(fetchLobby()); and it works now
There's nothing particular about using setInterval with redux-thunk. It should work just as it would anywhere else. You need to see the callback function which has been used in the setInterval.
I need to get data from my SQLite database and then save it in my component's state. The database part is working fine, the problem is that it doesn't get saved to state quite quickly enough. If I add some artificial delay with setTimeout then it works fine. How could I better tie these things together so that it all works with the correct order and timing without a million callbacks?
This doesn't work:
let returnedData;
// This function works, the data comes back correctly eventually
returnedData = getDataFromDB();
this.setState({
dbData: returnedData //but returnedData is still empty at this point
})
// Data is not back in time to see in the variable or in state:
console.log(returnedData); // Undefined
console.log(this.state.dbData); // Undefined
But this does work:
let returnedData;
returnedData = getDataFromDB();
// If I add this tiny delay, it all works
setTimeout(function(){
this.setState({
dbData: returnedData
})
console.log(returnedData); // Shows correct data
console.log(this.state.dbData); // Shows correct data
},100);
I would like to try to find a way for this to work without the artificial delay. I will need to do about 3 of these database queries in componentWillMount as my component is loading and will need to know that I have the data back before I render the component.
Thanks!
Use the componentDidMount lifecycle hook to obtain async data when a component is initialized. This should be done in the component that is using the asynchronously obtained data, or the closest common ancestor for data that is used by multiple components. The reason for this is to reduce the amount of re-rendered components once the async retrieval has completed.
Keep in mind you will also need to account for a loading state, before your async data is available.
Below is a basic example of the principles.
class ComponentThatRequiresAsyncData extends PureComponent {
constructor( props ) {
super( props );
// initialize state
this.state = {
data: null
}
}
// handle async operation in this lifecycle method to ensure
// component has mounted properly
componentDidMount() {
axios.get( "some_url" )
.then( ( response ) => {
// once you have your data use setState to udpate state
this.setState( ( prevState, props ) => {
return { data: response.data };
})
})
.catch( ( error ) => {
// handle errors
});
}
render() {
const { data } = this.state;
return (
{
data ?
<WhatEverIWantToDoWithData data={ data } /> :
<p>Loading...</p>
}
);
}
}
Use the same idea for data that needs to be loaded because of an event, such as a button click. Instead of using componentDidMount you would make your own method, make your async call and update state via setState in the then method of your http call. Just make sure you bind the this context of your method in your constructor if you're using it as an event handler.
Hope this helps. If you have any questions please ask!