最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Does React enforces the same number and order of hooks across all instances of a component? - Stack Overflow

programmeradmin3浏览0评论

Suppose I have two instances of the same component. One uses a custom hook with two useState calls, and another uses a different custom hook with three useState calls. Intuitively, since they're separate instances, their state should be independent.

I came up with a sample implementation which works fine having different number of hooks for 2 different instances of the same component (MyComponent). This is achieved via conditionally plugging a custom hook to the component body when mounting with the help of a useState init function that guarantees picking a custom hook occurs only one time.

const [conditionalHook] = useState(() => (count % 2 === 1) ? useCustomB : useCustomA);

Is there any drawbacks?

Note: The following logic can also be used to set custom hook conditionally without a useState init function

const conditionalHook = (count % 2 === 1) ? useCustomB : useCustomA;

But this is highly error prone as number and order of hooks can change even for a single component instance as prop values are modified

Please refer to the working sample implementation below

import { useEffect, useRef, useState } from 'react'

const useCustomA = (count) => {
  const [val, setVal] = useState('');

  useEffect(() => {
    console.log("A Effect called");
    setVal('State A' + count);
  }, [count]);
  
  return `${val}`;
};

const useCustomB = (count) => {
  const [val, setVal] = useState('');
  const [rnNum, setRnNum] = useState(0);

  useEffect(() => {
    console.log("B Effect called");
    setVal('State B' + count)
    setRnNum(Math.random())
  }, [count]);

  return `${val}-${rnNum}`;
};

const MyComponent = ({count}) => {

  //Please note that conditional hook won't change between re-renders for a single component instance
  //since it is determined using a useState init function, 
  //but can change for different instances on mounting
  const [conditionalHook] = useState(() => (count % 2 === 1) ? useCustomB : useCustomA);
  const value = conditionalHook(count)

  return (
    <>
      <div>Value: {value}</div>
    </>
  )
};


const App = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  }

  return (
    <div>
      <MyComponent count={count}/>
      <MyComponent count={count+ 1}/>
      <div>Count: {count}</div>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default App

Suppose I have two instances of the same component. One uses a custom hook with two useState calls, and another uses a different custom hook with three useState calls. Intuitively, since they're separate instances, their state should be independent.

I came up with a sample implementation which works fine having different number of hooks for 2 different instances of the same component (MyComponent). This is achieved via conditionally plugging a custom hook to the component body when mounting with the help of a useState init function that guarantees picking a custom hook occurs only one time.

const [conditionalHook] = useState(() => (count % 2 === 1) ? useCustomB : useCustomA);

Is there any drawbacks?

Note: The following logic can also be used to set custom hook conditionally without a useState init function

const conditionalHook = (count % 2 === 1) ? useCustomB : useCustomA;

But this is highly error prone as number and order of hooks can change even for a single component instance as prop values are modified

Please refer to the working sample implementation below

import { useEffect, useRef, useState } from 'react'

const useCustomA = (count) => {
  const [val, setVal] = useState('');

  useEffect(() => {
    console.log("A Effect called");
    setVal('State A' + count);
  }, [count]);
  
  return `${val}`;
};

const useCustomB = (count) => {
  const [val, setVal] = useState('');
  const [rnNum, setRnNum] = useState(0);

  useEffect(() => {
    console.log("B Effect called");
    setVal('State B' + count)
    setRnNum(Math.random())
  }, [count]);

  return `${val}-${rnNum}`;
};

const MyComponent = ({count}) => {

  //Please note that conditional hook won't change between re-renders for a single component instance
  //since it is determined using a useState init function, 
  //but can change for different instances on mounting
  const [conditionalHook] = useState(() => (count % 2 === 1) ? useCustomB : useCustomA);
  const value = conditionalHook(count)

  return (
    <>
      <div>Value: {value}</div>
    </>
  )
};


const App = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  }

  return (
    <div>
      <MyComponent count={count}/>
      <MyComponent count={count+ 1}/>
      <div>Count: {count}</div>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default App

Please see the following React dev tools output for these 2 instances

Share Improve this question edited Mar 8 at 19:35 Asanka Siriwardena asked Mar 8 at 17:32 Asanka SiriwardenaAsanka Siriwardena 1,0011 gold badge14 silver badges19 bronze badges 7
  • 1 "number and order of hooks can change even for a single component instance" - and so you've violated the rules of hooks. But it seems you already know that this is a problem, so what exactly is your question? – Bergi Commented Mar 8 at 17:56
  • @Bergi In my approach number of hooks can be changed only for different component instances, not for the same component instance. Therefore I believe it does not violate react hook rules – Asanka Siriwardena Commented Mar 8 at 18:12
  • @Bergi See the snippet and it won't. I am using a useState init function that guarantees switching occurs only for one time when component is mounted const [conditionalHook] = useState(() => (count % 2 === 1) ? useCustomB : useCustomA); – Asanka Siriwardena Commented Mar 8 at 18:26
  • The idea here is to plug different business logic encapsulated within custom hooks dynamically for a component instance, by passing enum like props for different component instances improving reusability Ex: <MyComponent order={Food.PIZZA} /> <MyComponent order={Food.BURGER} /> – Asanka Siriwardena Commented Mar 8 at 18:36
  • 1 Generally you can always reduce code duplication by introducing helper functions for the similar parts, with (callback) parameters for the sub-parts that differ. These helpers may be hooks, they may be components, or they may be plain old functions; it doesn't really matter (and React won't care). – Bergi Commented Mar 8 at 19:59
 |  Show 2 more comments

1 Answer 1

Reset to default 0

Normally React expects all the instances of a particular component should use same hooks in same order but your approach violates the assumption even though it didnt throw any react error. One of the drawback is:

Inconsistent behavior when component is re-mounted: The conditionalHook is determined on mount so if same instance of MyComponent mounts and then re-mounts with the different value of count then it might use a different hook.

Example: count = 1 then useCustomB is choosen. count then changes to 2 but the component does not re-mount so it still uses useCustomB although it should use useCustomA.

This might cause issues with further react updates that optimise rendering.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论