使用某些道具触发useEffect()React钩子,但在效果中引用其他道具

时间:2019-07-07 22:35:41

标签: reactjs react-hooks

我有一个React用例,其中一个组件获得了一个category属性的传递,并且还具有一个局部状态值limit。我想要的行为是:

  1. 选择category后,如果limit当前高于10,则会重置为10。

  2. 清除category时,如果limit当前低于50,则会重置为50。

  3. 虽然category不变,但是用户可以选择他们想要的任何limit

我在效果挂钩中设置了此行为,如下所示(底部更完整的示例):

  useEffect(() => {
    if (category && limit > 10) {
      setLimit(10);
    } else if (!category && limit < 50) {
      setLimit(50);
    }
  }, [category]);

这很好用,但是与react-hooks/exhaustive-deps linter rule冲突,后者表明我应该在依赖项数组中包含limit。我实际上并不想这样做,因为我不希望用户启动的limit更改会触发任何事情,并且我希望用户在类别为'改变。

是否有一种“正确”的方式来做到这一点,既尊重钩子的规则,又不引入更多的钩子来处理所有不同的道具/状态转换?

更完整的示例:

function App() {
  const [category, setCategory] = useState(true);
  return (
    <div className="App">
      <button onClick={() => setCategory(!category)}>Toggle category</button>
      <List category={category} />
    </div>
  );
}

function List({ category }) {
  const [limit, setLimit] = useState(10);

  // Change the limit when category changes
  // BUT only if limit is above/below a threshold!
  useEffect(() => {
    if (category && limit > 10) {
      setLimit(10);
    } else if (!category && limit < 50) {
      setLimit(50);
    }
  }, [category]);

  return (
    <div>
      <select value={limit} onChange={e => setLimit(e.target.value)}>
        {[5, 10, 25, 50, 100].map(d => (
          <option key={d}>{d}</option>
        ))}
      </select>
      <div>Category is {category ? "on" : "off"}</div>
      <div>Would show {limit} items</div>
    </div>
  );
}

3 个答案:

答案 0 :(得分:1)

如何使用ref来跟踪上一个类别?它们相当于功能组件中的instance variables

这样,您可以进行比较,并且仅在类别发生更改时才运行效果代码。

const prevCat = useRef(null);
useEffect(() => {
  if (prevCat.current === category) return;

  prevCat.current = category;
  ...
}, [category, limit]);

答案 1 :(得分:0)

切换到useReducer()来与limit一起管理category,并将两者都传递给您的组件,而不是使用本地状态。这样,仅当一个值更改(或两个值都更改)时才呈现组件。

const initialState = {
    category: true,
    limit: 10
};

function reducer(state, action) {
    switch (action.type) {
    case 'toggleCategory':
        if (state.category) {
            return {category: false, limit: Math.min(50, state.limit)};
        }
        else {
            return {category: true, limit: Math.max(10, state.limit)};
        }
    case 'setLimit':
        return {...state, limit: action.limit};
    default:
        throw new Error();
    }
}

function App() {
    const [state, dispatch] = useReducer(reducer, initialState);
    // React guarantees that dispatch function identity is stable and won’t change on re-renders.
    // This is why it’s safe to omit from the useEffect or useCallback dependency list.
    const toggleCategory = useCallback(() => dispatch({type: 'toggleCategory'}));
    const setLimit = useCallback(limit => dispatch({type: 'setLimit', limit}));
    return (
        <div className="App">
            <button onClick={toggleCategory}>Toggle category</button>
            <List {...state} setLimit={setLimit} />
        </div>
    );
}

function List({ category, limit, setLimit }) {
    return (
        <div>
            <select value={limit} onChange={e => setLimit(e.target.value)}>
                {[5, 10, 25, 50, 100].map(d => (
                    <option key={d}>{d}</option>
                ))}
            </select>
            <div>Category is {category ? "on" : "off"}</div>
            <div>Would show {limit} items</div>
        </div>
    );
}

答案 2 :(得分:0)

您可以使用setter的功能版本:

useEffect(() => {
    setLimit((currentLimit) => {
        if (category && currentLimit > 10) {
          return 10;
        } else if (!category && currentLimit < 50) {
          return 50;
        } else return currentLimit;
    });
  }, [category]);