Как подключить настраиваемый флажок / ввод к LocalStorage в React с помощью компонента более высокого порядка?

Вступление

Вчера я следовал расширенному руководству от Кента С. Доддса, в котором он объяснил, как подключить ввод к localstorage, который затем обрабатывает установку значения, изменение значений и т. д. и автоматически синхронизируется с LocalStorage в react.

Во-первых, это хорошо работает для обычных компонентов. Однако, например, настраиваемые флажки, которые у меня есть в моем приложении, не работают с логикой. Я пытался немного переделать логику, но, похоже, далеко не ушел.

Эта проблема

В настоящее время мой пользовательский компонент флажка не подключается / не работает с hoc LocalStorageFormControl.

Информация о проекте

Я сделал CodeSandbox, с которым вы можете поиграть: https://codesandbox.io/s/eager-curie-8sj1x

В проекте используется стандартный бутстрап со стилем scss. CustomCheckbox состоит из двух элементов: основного блока и самого ввода. В настоящее время соответствующее значение в состоянии инициирует изменение className в одном из элементов, чтобы разрешить настраиваемый стиль.

Если у вас возникнут дополнительные вопросы, оставьте комментарий ниже. Заранее спасибо за помощь.

Ресурсы

Кент К. Доддс - учебные материалы

Проект CodeSandBox


person Kevin Vugts    schedule 08.07.2020    source источник
comment
Что конкретно не работает?   -  person po.pe    schedule 08.07.2020
comment
@ po.pe Прошу прощения за непонятность. Он не работает, так как не обрабатывает правильную установку значения и изменение состояний.   -  person Kevin Vugts    schedule 08.07.2020


Ответы (1)


Проблемы были:

  1. Компонент LocalStorageFormControl не обновил состояние, когда он получил начальное значение из localStorage.
  2. input не обновлял состояние onChange, поскольку у него не было обработчика onChange.
  3. Компонент CustomCheckboxGroup не имеет опоры name, которая используется как часть key в localStorage

Решение следующее:

App.js

import React, { useEffect, useState } from "react";

// Bootstrap
import { Row, Col, Form } from "react-bootstrap";
import CustomCheckboxGroup from "./CustomCheckboxGroup";

// Function that calls all functions in order to allow the user to provide their own onChange, value etc
const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args));

// Connect any <input /> to LocalStorage and let it manage value / onChange
function LocalStorageFormControl({
  children,
  formControl = React.Children.only(children),
  lsKey = `lsfc:${formControl.props.name}`,
  updateInitialState
}) {
  const [hasChanged, setHasChanged] = useState(false);
  const [value, setValue] = useState(() => {
    return (
      window.localStorage.getItem(lsKey) || formControl.props.defaultValue || ""
    );
  });

  // Let the user control the value if needed
  if (
    formControl.props.value !== undefined &&
    formControl.props.value !== value
  ) {
    setValue(formControl.props.value);
  }

  useEffect(() => {
    if (hasChanged) {
      if (value) {
        window.localStorage.setItem(lsKey, value);
      } else {
        window.localStorage.removeItem(lsKey);
      }
    } else {
      if (value) {
        // if hasChanged is false and there is value that means there was a value in localStorage
        setHasChanged(true);
        // update the state
        updateInitialState(value);
      }
    }
  }, [value, lsKey, hasChanged, updateInitialState]);

  return React.cloneElement(React.Children.only(children), {
    onChange: callAll(formControl.props.onChange, e => {
      setHasChanged(true);
      setValue(e.target.value);
    }),
    value,
    defaultValue: undefined
  });
}

const checkboxes = [
  {
    label: "Dhr",
    name: "aanhef-dhr",
    stateName: "salutation",
    value: "De heer"
  },
  {
    label: "Mevr",
    name: "aanhef-mevr",
    stateName: "salutation",
    value: "Mevrouw"
  }
];

export default function App() {
  const [state, setState] = useState({});

  function handleSubmit(e) {
    e.preventDefault();
    console.log("Handling submission of the form");
  }

  function onChange(e, stateName) {
    e.persist();
    setState(prevState => ({ ...prevState, [stateName]: e.target.value }));
  }

  // Log the state to the console
  console.log(state);

  return (
    <Row>
      <Col xs={12}>
        <Form
          id="appointment-form"
          onSubmit={handleSubmit}
          noValidate
          style={{ marginBottom: 75 }}
        >
          <LocalStorageFormControl
            updateInitialState={value => {
              setState({ ...state, "test-textfield": value });
            }}
          >
            {/* Add onChange handler to update the state with input value*/}
            <input
              type="text"
              name="test-textfield"
              onChange={e => {
                setState({ ...state, "test-textfield": e.target.value });
              }}
            />
          </LocalStorageFormControl>
          <LocalStorageFormControl
            updateInitialState={value => {
              setState({ ...state, salutation: value });
            }}
          >
            <CustomCheckboxGroup
              checkboxes={checkboxes}
              key="salutation"
              label="Salutation"
              name="salutation"
              onChange={(e, stateName) => onChange(e, stateName)}
              required={true}
              value={state.salutation}
            />
          </LocalStorageFormControl>
        </Form>
      </Col>
    </Row>
  );
}

CustomCheckboxGroup.js

import React from "react";

// Bootstrap
import { Form, Row, Col } from "react-bootstrap";

export default ({ onChange, value, name, label, className, checkboxes }) => (
  <Row>
    <Col xs={12}>
      <Form.Label>{label}</Form.Label>
    </Col>

    <Col>
      <Form.Group className="d-flex flex-direction-column">
        {checkboxes.map((checkbox, key) => {
          return (
            <div
              key={key}
              className={
                checkbox.value === value
                  ? "appointment_checkbox active mr-2 custom-control custom-checkbox"
                  : "appointment_checkbox mr-2 custom-control custom-checkbox"
              }
            >
              <input
                name={name}
                type="checkbox"
                value={checkbox.value}
                onChange={e => onChange(e, checkbox.stateName)}
                checked={value === checkbox.value}
                id={"checkbox-" + checkbox.name}
                className="custom-control-input"
              />
              <label
                className="custom-control-label"
                htmlFor={"checkbox-" + checkbox.name}
              >
                {checkbox.label}
              </label>
            </div>
          );
        })}
      </Form.Group>
    </Col>
  </Row>
);

У меня есть совет по поводу вашего кода:

  1. Используйте переключатели вместо флажков, если вы разрешаете пользователю выбирать только один вариант. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio
  2. Вы можете сохранить весь объект состояния, если хотите, заменив это:
    const [state, setState] = useState({});
    
    на это:
    // Get the saved state in local storage if it exists or use an empty object
    // You must use JSON.parse to convert the string back to a javascript object
    const initialState = localStorage.getItem("form-state")
      ? JSON.parse(localStorage.getItem("form-state"))
      : {};
    // Initialize the state with initialState
    const [state, setState] = useState(initialState);
    
    // Whenever the state changes save it to local storage
    // Notice that local storage accepts only strings so you have to use JSON.stringify
    useEffect(() => {
      localStorage.setItem("form-state", JSON.stringify(state));
    }, [state]);
    
person Ahmed Mokhtar    schedule 08.07.2020
comment
К сожалению, это не то решение, которое вы предлагаете. Поле ввода всегда работало. Пользовательский флажок в данный момент не работает с локальным хранилищем. Когда вы оборачиваете ‹CustomCheckboxGroup /› с помощью ‹LocalStorageFormControl /› и обновляете страницу, состояние и локальное хранилище не работают с настраиваемым флажком. :-) - person Kevin Vugts; 10.07.2020
comment
Я обновил ответ. Я не знаю, почему вы используете этот шаблон. Есть ли конкретный вариант использования ?. В реальном мире вам нужно сохранить и получить все состояние, а не каждое отдельное поле. Вы можете сделать это, как я показал вам в примере, и если вы используете библиотеку форм, такую ​​как react-final-form или formik, вы можете сделать то же самое, сохраняя все состояние в localStorage при изменении одного из значений поля и в случае обновления страницы, которую вы может получить состояние при первоначальном рендеринге и использовать его как форму initialValues prop. Я надеюсь, что ответил на ваш вопрос. - person Ahmed Mokhtar; 10.07.2020
comment
Я думаю, вы совершенно правы насчет того, что я должен хранить состояние формы в целом. Хотя это был своего рода эксперимент, основанный на выступлении Кента c. Доддс. Причина, по которой я решил это сделать, заключалась в том, что некоторые поля не должны храниться в локальном хранилище, поэтому я сделал из него HOC. Спасибо за ответ и помощь! - person Kevin Vugts; 10.07.2020
comment
Единственное, что меня беспокоит в решении, это то, что теперь свойство name находится в CustomCheckboxgroup, которая не является собственным элементом, а просто оболочкой вокруг двух полей ввода типа флажка. Что мы можем с этим сделать? - person Kevin Vugts; 10.07.2020
comment
LocalStorageFormControl использует своих дочерних элементов name prop как часть ключа в качестве значения по умолчанию lsKey = lsfc: $ {formControl.props.name} `` Если вы хотите переопределить вместо передачи имени, вы можете передать lsKey prop. - person Ahmed Mokhtar; 11.07.2020
comment
Я понимаю, что он использует свойство childrens name. Однако обычно свойство имени должно быть в поле ввода. Поскольку name - это собственное свойство для native ‹input /›, верно? - person Kevin Vugts; 11.07.2020