React useState setter 不会更新上下文中的值

React useState setter wont update the value inside a context

所以,我正在使用 React 上下文 API 来管理我的应用程序的一些全局状态

const blueprintList = [
  {
    id: new Date().toISOString(),
    name: 'Getting started',
    color: '#c04af6',
    code: '<h1>Hello World!</h1>,
  },
];

export const BlueprintsContext = createContext();

export const BlueprintsProvider = ({ children }) => {
  const [blueprints, setBlueprints] = useState(blueprintList);
  let [selected, setSelected] = useState(0);
  const [count, setCount] = useState(blueprints.length);

  useEffect(() => {
    setCount(blueprints.length);
  }, [blueprints]);

  // just for debugging
  useEffect(() => {
    console.log(`DEBUG ==> ${selected}`);
  }, [selected]);

  const create = blueprint => {
    blueprint.id = new Date().toISOString();
    setBlueprints(blueprints => [...blueprints, blueprint]);
    setSelected(count);
  };

  const remove = blueprint => {
    if (count === 1) return;
    setBlueprints(blueprints => blueprints.filter(b => b.id !== blueprint.id));
    setSelected(-1);
  };

  const select = blueprint => {
    const index = blueprints.indexOf(blueprint);
    if (index !== -1) {
      setSelected(index);
    }
  };

  const value = {
    blueprints,
    count,
    selected,
    select,
    create,
    remove,
  };

  return (
    <BlueprintsContext.Provider value={value}>
      {children}
    </BlueprintsContext.Provider>
  );
};

然后我像这样在组件内部使用 remove 函数。

const Blueprint = ({ blueprint, isSelected }) => {
  const { select, remove } = useContext(BlueprintsContext);
  return (
    <div
      className={classnames(`${CSS.blueprint}`, {
        [`${CSS.blueprintActive}`]: isSelected,
      })}
      key={blueprint.id}
      onClick={() => select(blueprint)}
    >
      <div
        className={CSS.blueprintDot}
        style={{ backgroundColor: blueprint.color }}
      />
      <span
        className={classnames(`${CSS.blueprintText}`, {
          ['text-gray-300']: isSelected,
          ['text-gray-600']: !isSelected,
        })}
      >
        {blueprint.name}
      </span>
      {isSelected && (
        <IoMdClose className={CSS.close} onClick={() => remove(blueprint)} />
      )}
    </div>
  );
};

父组件接收项目列表并为每个项目呈现一个 Blueprint 组件。问题是当按下 IoMdClose 图标时,该项目将从列表中删除,但 selected 值不会更新为 -1。 ‍♂️

再见,我发现了你的问题:在函数 select 中你已经设置了 selected:

const select = blueprint => {
   const index = blueprints.indexOf(blueprint);
   if (index !== -1) {
     setSelected(index);  // here you set selected
   }
};

当您点击 IoMdClose 图标时,也会触发 select。所以结果是这个 useEffect:

useEffect(() => {
   console.log(`DEBUG ==> ${selected}`);
}, [selected]);

不记录任何内容。

我试图从 select 函数中删除 setSelected(index);,当我点击 IoMdClose 图标时,selected 将被设置为 -1 并且 useEffect 日志 DEBUG ==> -1.

但现在您遇到了另一个问题:如果您从 select 中删除 setSelected(index); 并尝试从左侧树视图中 select 一个蓝图,蓝图将不会 select编辑。所以我在select函数中re-addedsetSelected(index);。从 remove 函数中删除了 setSelected(-1);,现在 useEffect 不记录任何内容!

我认为发生这种情况是因为您试图将 selected 设置为不存在的索引(因为您在单击图标时删除了蓝图)。为了验证这一点,我将 select 函数中的 setSelected(index); 修改为 setSelected(0); 并且现在如果我删除一个蓝图,useEffect 将被触发并记录 DEBUG ==> 0.

如果 setSelected(-1); 背后的想法是取消 select 树视图中的所有蓝图,您可以这样做:

export const BlueprintsProvider = ({ children }) => {
....
const removeSelection = useRef(0);

useEffect(() => {
    if (blueprints.length < removeSelection.current) setSelected(-1); // if I removed a blueprint, then fire setSelected(-1);
    setCount(blueprints.length);
    removeSelection.current = blueprints.length; //removeSelection.current setted as blueprints.length
  }, [blueprints]);

当然还有从 remove 函数中删除 setSelected(-1);