Skip to content

getState() hook proposal #14092

@denysonique

Description

@denysonique

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)
    }, [])

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions