如何使用高阶组件将自定义复选框/输入连接到 React 中的 LocalStorage?

How to connect a custom checkbox / input to LocalStorage in React using a Higher Order Component?

简介

昨天我学习了 Kent C. Dodds 的高级教程,他解释了如何将输入连接到 which then handles the setting of value, change of values etc and automatically sync with LocalStorage in

首先,这对普通组件来说效果很好。但是,例如,我的应用程序中的自定义复选框不适用于逻辑。我试图稍微改变一下逻辑,但似乎我并没有走得太远。

问题

目前我的自定义复选框组件无法连接/使用 LocalStorageFormControl.

项目信息

I have made a CodeSandbox for you to play around with: https://codesandbox.io/s/eager-curie-8sj1x

该项目使用带有 scss 样式的标准 bootstrap。 CustomCheckbox 由两个元素组成:主要 div 和实际输入本身。目前,state 中的匹配值将触发其中一个元素中的 className 更改,以允许自定义样式。

如有任何其他问题,请在下方评论。预先感谢所有帮助。

资源

Kent C. Dodds - Tutorial resource

CodeSandBox Project

问题是:

  1. LocalStorageFormControl组件没有更新状态 它从 localStorage 获取初始值。
  2. input 没有更新 onChange 状态,因为它没有 onChange 处理程序。
  3. CustomCheckboxGroup 组件没有使用 name 道具 作为 localStorage
  4. key 的一部分

解决方法如下:

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