title: How to get current state inside useCallback when using useReducer?
body: <p>Using react hooks with TypeScript and here is a minimal representation of what I am trying to do: Have a list of buttons on screen and when the user clicks on a button, I want to change the text of the button to "Button clicked" and then <strong>only re-render the button which was clicked</strong>.</p>
  
  <p>I am using useCallback for wrapping the button click event to avoid the click handler getting re-created on every render. </p>
  
  <p><strong>This code works the way I want:</strong> If I use useState and maintain my state in an array, then I can use the <a href="https://reactjs.org/docs/hooks-reference.html#functional-updates" rel="noreferrer">Functional update</a> in useState and get the exact behaviour I want:</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>import * as React from 'react';
  import { IHelloWorldProps } from './IHelloWorldProps';
  import { useEffect, useCallback, useState } from 'react';
  import { PrimaryButton } from 'office-ui-fabric-react';
  
  interface IMyButtonProps {
    title: string;
    id: string;
    onClick: (clickedDeviceId: string) =&gt; (event: any) =&gt; void;
  }
  
  const MyButton: React.FunctionComponent&lt;IMyButtonProps&gt; = React.memo((props: IMyButtonProps) =&gt; {
    console.log(`Button rendered for ${props.title}`);
    return &lt;PrimaryButton text={props.title} onClick={props.onClick(props.id)} /&gt;;
  });
  
  interface IDevice {
    Name: string;
    Id: string;
  }
  
  const HelloWorld: React.FunctionComponent&lt;IHelloWorldProps&gt; = (props: IHelloWorldProps) =&gt; {
  
    //If I use an array for state instead of object and then use useState with Functional update, I get the result I want. 
    const initialState: IDevice[] = [];
    const [deviceState, setDeviceState] = useState&lt;IDevice[]&gt;(initialState);
  
    useEffect(() =&gt; {
  
      //Simulate network call to load data.
      setTimeout(() =&gt; {
        setDeviceState([{ Name: "Apple", Id: "appl01" }, { Name: "Android", Id: "andr02" }, { Name: "Windows Phone", Id: "wp03" }]);
      }, 500);
  
    }, []);
  
    const _deviceClicked = useCallback((clickedDeviceId: string) =&gt; ((event: any): void =&gt; {
  
      setDeviceState(prevState =&gt; prevState.map((device: IDevice) =&gt; {
        if (device.Id === clickedDeviceId) {
          device.Name = `${device.Name} clicked`;
        }
  
        return device;
      }));
  
    }), []);
  
    return (
      &lt;React.Fragment&gt;
        {deviceState.map((device: IDevice) =&gt; {
          return &lt;MyButton key={device.Id} title={device.Name} onClick={_deviceClicked} id={device.Id} /&gt;;
        })}
      &lt;/React.Fragment&gt;
    );
  };
  
  export default HelloWorld;</code></pre>
  </div>
  </div>
  </p>
  
  <p>Here is the desired result:
  <a href="https://i.stack.imgur.com/6RJUu.gif" rel="noreferrer"><img src="https://i.stack.imgur.com/6RJUu.gif" alt="enter image description here"></a></p>
  
  <p><strong>But here is my problem:</strong> In my production app, the state is maintained in an object and we are using the useReducer hook to simulate a class component style setState where we only need to pass in the changed properties. So we don't have to keep replacing the entire state for every action. </p>
  
  <p>When trying to do the same thing as before with useReducer, the state is always stale as the cached version of useCallback is from the first load when the device list was empty. </p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>import * as React from 'react';
  import { IHelloWorldProps } from './IHelloWorldProps';
  import { useEffect, useCallback, useReducer, useState } from 'react';
  import { PrimaryButton } from 'office-ui-fabric-react';
  
  interface IMyButtonProps {
    title: string;
    id: string;
    onClick: (clickedDeviceId: string) =&gt; (event: any) =&gt; void;
  }
  
  const MyButton: React.FunctionComponent&lt;IMyButtonProps&gt; = React.memo((props: IMyButtonProps) =&gt; {
    console.log(`Button rendered for ${props.title}`);
    return &lt;PrimaryButton text={props.title} onClick={props.onClick(props.id)} /&gt;;
  });
  
  interface IDevice {
    Name: string;
    Id: string;
  }
  
  interface IDeviceState {
    devices: IDevice[];
  }
  
  const HelloWorld: React.FunctionComponent&lt;IHelloWorldProps&gt; = (props: IHelloWorldProps) =&gt; {
  
    const initialState: IDeviceState = { devices: [] };
    
    //Using useReducer to mimic class component's this.setState functionality where only the updated state needs to be sent to the reducer instead of the entire state.
    const [deviceState, setDeviceState] = useReducer((previousState: IDeviceState, updatedProperties: Partial&lt;IDeviceState&gt;) =&gt; ({ ...previousState, ...updatedProperties }), initialState);
  
    useEffect(() =&gt; {
    
      //Simulate network call to load data.
      setTimeout(() =&gt; {
        setDeviceState({ devices: [{ Name: "Apple", Id: "appl01" }, { Name: "Android", Id: "andr02" }, { Name: "Windows Phone", Id: "wp03" }] });
      }, 500);
    
    }, []);
  
    //Have to wrap in useCallback otherwise the "MyButton" component will get a new version of _deviceClicked for each time.
    //If the useCallback wrapper is removed from here, I see the behavior I want but then the entire device list is re-rendered everytime I click on a device.
    const _deviceClicked = useCallback((clickedDeviceId: string) =&gt; ((event: any): void =&gt; {
  
      //Since useCallback contains the cached version of the function before the useEffect runs, deviceState.devices is always an empty array [] here. 
      const updatedDeviceList = deviceState.devices.map((device: IDevice) =&gt; {
        if (device.Id === clickedDeviceId) {
          device.Name = `${device.Name} clicked`;
        }
  
        return device;
      });
      setDeviceState({ devices: updatedDeviceList });
  
    //Cannot add the deviceState.devices dependency here because we are updating deviceState.devices inside the function. This would mean useCallback would be useless. 
    }), []);
  
    return (
      &lt;React.Fragment&gt;
        {deviceState.devices.map((device: IDevice) =&gt; {
          return &lt;MyButton key={device.Id} title={device.Name} onClick={_deviceClicked} id={device.Id} /&gt;;
        })}
      &lt;/React.Fragment&gt;
    );
  };
  
  export default HelloWorld;</code></pre>
  </div>
  </div>
  </p>
  
  <p>This is how it looks:
  <a href="https://i.stack.imgur.com/zO1h6.gif" rel="noreferrer"><img src="https://i.stack.imgur.com/zO1h6.gif" alt="enter image description here"></a></p>
  
  <p><strong>So my question boils down to this:</strong> When using useState inside useCallback, we can use the functional update pattern and capture the current state (instead of from when useCallback was cached)
  This is possible without specifying dependencies to useCallback.</p>
  
  <p><strong>How can we do the same thing when using useReducer? Is there a way to get the current state inside useCallback when using useReducer and without specifying dependencies to useCallback?</strong></p>