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

时间:2020-08-05 10:47:53

标签: javascript reactjs react-hooks react-context

因此,我正在使用React Context 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>
  );
};

然后,我在这样的组件内部使用删除功能。

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。 ?‍♂️

1 个答案:

答案 0 :(得分:1)

Ciao,我发现了您的问题:在函数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]);

不记录任何内容。

我尝试从setSelected(index);函数中删除select,当我单击IoMdClose图标时,selected将设置为-1,并使用效果日志DEBUG ==> -1

但是现在您遇到另一个问题:如果从setSelected(index);中删除select,并且尝试从左侧树视图中选择一个蓝图,则不会选择该蓝图。因此,我在setSelected(index);函数中重新添加了select。从setSelected(-1);函数中删除了remove,现在useEffect不会记录任何内容!

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

如果setSelected(-1);背后的想法是取消选择树状视图中的所有蓝图,则可以执行以下操作:

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]);

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