useState() provided state value currently cannot be used in useEffect(fn, []) - (componentDidMount-like scenario) with asynchronous functions after state has been updated following the initial [] run.
Upon trying to give Hooks a try for a real world application I was initially confused with accessing state. Here is a little example what I tried to do:
constUserList=()=>{const[users,setUsers]=useState([])useEffect(()=>{constsocket=io('/dashboard')socket.on('user:connect',(user)=>{setUsers([...users,user])})socket.on('user:update',(user)=>{letnewUsers=users.map((u)=>u.id==user.id ? user : u)setUsers(newUsers)})},[])return(users.map(({id, email})=>(<trkey={id}><td>{id}</td><td>{email}</td></tr>)))}Upon running this I instantly realised that inside the socket.on() handler the users initially obtained from useState() did not reflect the changes inflicted by setUsers() ran on socket.on('user:connect'). Passing [users] as the second argument of useEffect() wasn't an option as that would cause additional socket.on() binds. I became skeptical about Hooks for this use case and sadly thought this would be where my journey with using hooks instead of the class components would end.
Fortunately I then found a solution to this problem (with someone indirectly having helped me by accident in the reactflux channel) by using an updater function with setState() which made it all work:
socket.on('user:update',(user)=>{setUsers(users=>users.map((u)=>u.id==user.id ? user : u))})The setState() problem was solved, but I am now wondering that if I will ever need to access state outside of an updater function, i.e. to just reply to a WebSocket message with some value from the state I will be unable to do so and this will force me and other users to revert to class components for such cases.
I therefore would like to suggest that a getState() hook would be an ideal solution to this problem.
Here is another mini example demonstrating the problem in a more concise manner:
constHooksComponent=()=>{const[value,setValue]=useState({val: 0});useEffect(()=>{setTimeout(()=>setValue({val: 10}),100)setTimeout(()=>console.log('value: ',value.val),200)},[]);}//console.log output: 0 instead of 10And here is one with a proposed solution:
constHooksComponent=()=>{const[state,setState,getState]=useState({val: 0});useEffect(()=>{setTimeout(()=>setState({val: 10}),100)setTimeout(()=>{getState(state=>{console.log('value: ',state.val)})},200)},[])
useState()provided state value currently cannot be used inuseEffect(fn, [])- (componentDidMount-like scenario) with asynchronous functions after state has been updated following the initial [] run.Upon trying to give Hooks a try for a real world application I was initially confused with accessing state. Here is a little example what I tried to do:
Upon running this I instantly realised that inside the
socket.on()handler theusersinitially obtained from useState() did not reflect the changes inflicted bysetUsers()ran onsocket.on('user:connect'). Passing[users]as the second argument ofuseEffect()wasn't an option as that would cause additionalsocket.on()binds. I became skeptical about Hooks for this use case and sadly thought this would be where my journey with using hooks instead of the class components would end.Fortunately I then found a solution to this problem (with someone indirectly having helped me by accident in the reactflux channel) by using an updater function with
setState()which made it all work:The
setState()problem was solved, but I am now wondering that if I will ever need to accessstateoutside of an updater function, i.e. to just reply to a WebSocket message with some value from the state I will be unable to do so and this will force me and other users to revert to class components for such cases.I therefore would like to suggest that a
getState()hook would be an ideal solution to this problem.Here is another mini example demonstrating the problem in a more concise manner:
And here is one with a proposed solution: