简体   繁体   中英

React does not update State (React-Hooks)

I have the following setup:

  const templates = [
    'dot',
    'line',
    'circle',
    'square',
  ];
  
  const [currentTemplate, setCurrentTemplate] = useState(0);

  const changeTemplate = (forward = true) => {
    let index = currentTemplate;
    index = forward ? index + 1 : index - 1;
    if (index > templates.length - 1) {
      index = 0;
    } else if (index < 0) {
      index = templates.length - 1;
    }
    setCurrentTemplate(index);
  };

  useEffect(() => {
    console.log(`Current Template is: ${templates[currentTemplate]}`);
  }, [currentTemplate]);

  useKeypress('ArrowLeft', () => {
    changeTemplate(false);
  });
  useKeypress('ArrowRight', () => {
    changeTemplate(true);
  });

This is the useKeypress-Hook is used:

  import { useEffect } from 'react';
/**
 * useKeyPress
 * @param {string} key - the name of the key to respond to, compared against event.key
 * @param {function} action - the action to perform on key press
 */
export default function useKeypress(key: string, action: () => void) {
  useEffect(() => {
    function onKeyup(e: KeyboardEvent) {
      if (e.key === key) action();
    }
    window.addEventListener('keyup', onKeyup);
    return () => window.removeEventListener('keyup', onKeyup);
  }, []);
}

Whenever I press the left or right arrow key, the function gets triggered. But the currentTemplate variable is not changing. It always stays at 0. The useEffect is only triggered when I swich the key from left to right or the other way. Clicking the same key twice, does not trigger useEffect again, but it should, When changing from right to left, the output is Current Template is: square and when changing from left to right, it is Current Template is: line . But this never changes. The value of currentTemplate always stays 0 .

What am I missing?

The issue is with your useEffect inside the useKeypress hook.

export default function useKeypress(key: string, action: () => void) {
  useEffect(() => {
    function onKeyup(e: KeyboardEvent) {
      if (e.key === key) action();
    }
    window.addEventListener('keyup', onKeyup);
    return () => window.removeEventListener('keyup', onKeyup);
  }, []); // 👈 problem
}

The hook is a single useEffect with no dependencies. Meaning, it will only fire once. This is a problem for the action you are passing into this hook. Whenever the changeTemplate -action changes in your component (ie on a rerender), the reference to this is not renewed inside of the useKeypress -hook.

To solve this, you need to add the action to the dependency array of the useEffect :

export default function useKeypress(key: string, action: () => void) {
  useEffect(() => {
    function onKeyup(e: KeyboardEvent) {
      if (e.key === key) action();
    }
    window.addEventListener('keyup', onKeyup);
    return () => window.removeEventListener('keyup', onKeyup);
  }, [action]); // 👈 whenever `action` changes, the effect will be updated
}

This is to ensure that the effect is updated whenever the given action is changed. After this change, things should be working as expected.

Optimalizing:

The useEffect will at this point be regenerated for each render, as the changeTemplate function will be instanciated for each render. To avoid this, you can wrap the changeTemplate with auseCallback , so that the function is memoized:

const changeTemplate = useCallback(
  (forward = true) => {
    let index = currentTemplate;
    index = forward ? index + 1 : index - 1;
    if (index > templates.length - 1) {
      index = 0;
    } else if (index < 0) {
      index = templates.length - 1;
    }
    setCurrentTemplate(index);
  }, 
  // Regenerate the callback whenever `currentTemplate` or `setCurrentTemplate` changes
  [currentTemplate, setCurrentTemplate]
); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM