React useState setter не будет обновлять значение внутри контекста - PullRequest
0 голосов
/ 05 августа 2020

Итак, я использую 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>
  );
};

Затем я использую функцию 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. ?‍♂️

1 Ответ

1 голос
/ 05 августа 2020

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 icon, selected будет установлено в -1 и будет использовать журналы эффектов DEBUG ==> -1.

Но теперь у вас есть другая проблема: если вы удалите setSelected(index); из select и попытаетесь выбрать один чертеж из left treeview, чертеж не будет выбран. Поэтому я повторно добавил setSelected(index); в функцию select. Удалено setSelected(-1); из функции remove, и теперь useEffect ничего не регистрирует!

Я думаю, это происходит потому, что вы пытаетесь установить selected на несуществующий индекс (потому что вы удалили blueprint при нажатии на значок). Чтобы проверить это, я изменил setSelected(index); в функции select на setSelected(0); и теперь, если я удалю один чертеж, useEffect будет срабатывать и записывать DEBUG ==> 0.

Если идея, лежащая в основе 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...