question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

getState() hook proposal

See original GitHub issue

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:

const UserList = () => {
    const [users, setUsers] = useState([])
    useEffect(() => {
        const socket = io('/dashboard')
        socket.on('user:connect', (user) => {
            setUsers([...users, user])
        })
        socket.on('user:update', (user) => {
            let newUsers = users.map((u) => u.id == user.id ? user : u)
            setUsers(newUsers)
        }) 
    }, [])

    return (
        users.map(({id, email}) => (
            <tr key={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:

const HooksComponent = () => {
    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 10

And here is one with a proposed solution:

const HooksComponent = () => {
    const [state, setState, getState] = useState({ val: 0 });

    useEffect(() => {
        setTimeout(() => setState({ val: 10 }), 100)
        setTimeout(() => {
            getState(state => {
                console.log('value: ', state.val)
            })
        }, 200)
    }, [])

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:10
  • Comments:16 (3 by maintainers)

github_iconTop GitHub Comments

7reactions
pzhinecommented, Aug 15, 2019

You can useReducer or setState(updaterFn) to avoid closing over the variables. This doesn’t solve all use cases (especially when you also need to perform side effects) but gets you pretty close.

@gaearon, it would be really useful to add the updaterFn argument to the dispatch method returned by useReducer, so you could do something like:

const [, dispatch] = useReducer(reducer, initialState)
dispatch(state => state.isLoading || doSomething())

In that case, state would always point to the freshest state.

6reactions
gaearoncommented, Nov 5, 2018

This is a known limitation. We might make useCallback handle these cases better in the future. We know that in practice it invalidates too often. The current recommended workaround is one of three options:

  1. Re-subscribe if it’s cheap enough.
  2. If re-subscribing is expensive, you could wrap the API you’re using to make it cheap.
  3. You can useReducer or setState(updaterFn) to avoid closing over the variables. This doesn’t solve all use cases (especially when you also need to perform side effects) but gets you pretty close.

There is also a more hacky workaround using refs that essentially emulates what classes do. I only recommend it as last resort since it may cause some issues in the future, but at least it’s no worse than classes.

We want to provide a better solution but it will take some time.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Use React Hooks to Connect to Redux Store | by Daw-Chih Liou
React Hooks are officially out with React 16.18 for a little bit now. How do you like it so far? ... const state...
Read more >
Give an Upgrade to Your useReducer Hook | by Nitsan Cohen
The useReducer hook does not offer us a way to apply a middleware and getState with its default behavior, but we are going...
Read more >
React hooks: accessing up-to-date state from within a callback
In React hooks, due to the way state is encapsulated in the functions of React.useState() , if a callback gets the state through...
Read more >
Hooks - React Redux
When the function component renders, the provided selector function will be called and its result will be returned from the useSelector() hook.
Read more >
Testing React Hook State Changes - DEV Community ‍ ‍
How to test changes to your React Hook useState using Jest and Enzyme. ... our enzyme shallow render object will not have a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found