React: [Feature] useState hook should return getState function

Created on 5 Apr 2019  路  3Comments  路  Source: facebook/react

Do you want to request a feature or report a bug?
Feature. useState should return getState.

What is the current behavior?
useState returns an initial state value, and a setter.

Consider:

import React, { useState, useEffect } from 'react'
import axios from 'axios'
import { Generate } from 'genyaj'

const ItemDisplay = () => {
    const [items, setItems] = useState([])
    const [stopUpdates, setStopUpdates] = useState(false)

    const getItemUpdates = async () => {
        const { data } = await axios.get(API)
        setItems(data)
    }

    useEffect(() => {
        Generate({
            task: getItemUpdates,
            stop: () => {
                let temp
                // React doesn't have a getState hook,
                // setStopUpdates can read the most current value of stopUpdates
                setStopUpdates(s => (temp = s))
                return temp
            },
            interval: 5000
        })
    }, [])

    return (
        <div>
             <button onClick={() => setStopUpdates(true)}>
            {items.map(item => <p>{item.name}</p>)}
        </div>
    )
}

What is the expected behavior?

const [stopUpdates, setStopUpdates, getStopUpdates] = useState(false)

Generate({
    task: getItemUpdates,
    // Rather than wrapping setStopUpdates with a temp variable assignment, just cut to the chase. 
    stop: getStopUpdates,
    interval: 5000
})

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React 16.8.4 | Chrome 73.0.3683.86

Most helpful comment

Won't setStopUpdates(s => (temp = s)) set stopUpdates to undefined?

Shoud be

setStopUpdates(s => {
  temp = s;
  return s;
})

Edit: I realised this wouldn't work because setState functions are asynchronous

Instead you'll have to use a ref, i.e

const [stopUpdates, setStopUpdates] = useState(false);
const stopUpdatesRef = useRef(stopUpdates);

useEffect(()=>{
  stopUpdatesRef.current = stopUpdates;
});

    useEffect(() => {
        Generate({
            task: getItemUpdates,
            stop: () => stopUpdatesRef.current,  // <- always up to date
            interval: 5000
        })
    }, [])

you can also write a custom state hook wrapper eh... hook?

const useStateWithGetter = (initialState) => {
  const [state, setState] = useState(initialState);

  const stateRef = useRef(state);

  useEffect(() => {
    stateRef.current = state;
  });

  const getState = useCallback(() => stateRef.current, []);

  return [state, setState, getState];
}

// in your component

const [stopUpdates, setStopUpdates, getStopUpdates] = useStateWithGetter(false); 

    useEffect(() => {
        Generate({
            task: getItemUpdates,
            stop: getStopUpdates,  // <- always up to date
            interval: 5000
        })
    }, [])

All 3 comments

Won't setStopUpdates(s => (temp = s)) set stopUpdates to undefined?

Shoud be

setStopUpdates(s => {
  temp = s;
  return s;
})

Edit: I realised this wouldn't work because setState functions are asynchronous

Instead you'll have to use a ref, i.e

const [stopUpdates, setStopUpdates] = useState(false);
const stopUpdatesRef = useRef(stopUpdates);

useEffect(()=>{
  stopUpdatesRef.current = stopUpdates;
});

    useEffect(() => {
        Generate({
            task: getItemUpdates,
            stop: () => stopUpdatesRef.current,  // <- always up to date
            interval: 5000
        })
    }, [])

you can also write a custom state hook wrapper eh... hook?

const useStateWithGetter = (initialState) => {
  const [state, setState] = useState(initialState);

  const stateRef = useRef(state);

  useEffect(() => {
    stateRef.current = state;
  });

  const getState = useCallback(() => stateRef.current, []);

  return [state, setState, getState];
}

// in your component

const [stopUpdates, setStopUpdates, getStopUpdates] = useStateWithGetter(false); 

    useEffect(() => {
        Generate({
            task: getItemUpdates,
            stop: getStopUpdates,  // <- always up to date
            interval: 5000
        })
    }, [])

Won't setStopUpdates(s => (temp = s)) set stopUpdates to undefined?

The (temp = s) expression returns the value assigned to the left-side operand.

you can also write a custom state hook wrapper eh...

This is awesome. I didn't think about using refs this way! thank you very much 馃槃

Although this custom hook addresses the issue I was having, I'm wondering if other people would prefer something like this being a part of the core useState api. If no one else thinks this, then I'd be fine closing this issue.

This is a great idea. It means in useCallbacks and useMemos etc. you won't need to subscribe to the state variable so your references will not change.

Was this page helpful?
0 / 5 - 0 ratings