如何更新使用自定义Hook的变量的状态值

时间:2019-06-02 08:49:21

标签: javascript reactjs state react-hooks

“我的组件”具有表单输入字段。这些使用了useState挂钩以及每个输入字段的value和setValue。我想优化组件,以便输入字段使用与我称为useFormInput

相同的自定义Hook

由丹·阿布拉莫夫https://youtu.be/dpw9EHDh2bM启发,请参阅49:42

这很好用。但是,现在我想在创建新练习后更新用户名。这在onSubmit方法中。但是我不确定该怎么做。在重构之前,我可以使用setUserName(),但是现在用户名是由通用自定义钩子函数useFormInput设置的。

用户名具有onChange方法,所以我想我可以使用此方法。但这使用e.target.value,因为它用于输入字段。

组件: 我注释掉了setUserName(''),在这里我想更新用户名

  const CreateExercise = () => {
  const inputEl = useRef(null)
  const username = useFormInput('')
  const description = useFormInput('')
  const duration = useFormInput(0)
  const date = useFormInput(new Date())
  const [users, setUsers] = useState([])
  useEffect(() => {
    axios
      .get('http://localhost:5000/users/')
      .then(res => {
        if (res.data.length > 0) {
          setUsers(res.data.map(user => user.username))
        }
      })
      .catch(err => console.log(err))
  }, [])
  const onSubmit = e => {
    e.preventDefault()
    const exercise = {
      username: username.value,
      description: description.value,
      duration: duration.value,
      date: date.value
    }
    axios
      .post('http://localhost:5000/exercises/add', exercise)
      .then(res => console.log(res.data))
      debugger
    // setUsername('')
    window.location = '/'
  }

自定义挂钩useFormInput:

const useFormInput = initialValue => {
  const [value, setValue] = useState(initialValue)
  const handleChange = e => {
    const newValue = e.target ? e.target.value : e
    setValue(newValue)
  }
  return {
    value,
    onChange: handleChange
  }
}

我希望用户名状态下的值更新为空字符串''

完整的代码位于我在https://github.com/jeltehomminga/mern-tracker上的仓库中

2 个答案:

答案 0 :(得分:2)

我建议不要将所有状态都合并到一个对象中,而要尝试维护多个状态。然后,您可以将所有内容移到您的自定义钩子中。此外,请始终确保您处理所有错误并将其传达给用户。

工作示例

Edit Hooks - Create Exercise Form


状态为对象

hooks / useFormHandler (下面定义的find_package是一个具有模仿API调用功能的对象,您将用真实的API调用替换它。此外,如果要进行该钩子可用于其他team.save(function (err) { if (err) { return res.status(400).send(err); // here i have added a return } res.status(200).json({ message: 'Team successfully created', team: team }); } 组件,那么您需要从自定义钩子中删除APIform函数,并将它们放在指定的功能组件中)

useEffect

组件/ CreateExerciseForm

handleSubmit

状态为独立数据类型

或者,如果您坚持使用分离的状态,则在import { useCallback, useEffect, useState } from "react"; import API from "../../API"; // create a custom useFormHandler hook that returns initial values, // a handleChange function to update the field values and a handleSubmit // function to handle form submissions. const useFormHandler = initialState => { const [values, setValues] = useState(initialState); // on initial load this will attempt to fetch users and set them to state // otherwise, if it fails, it'll set an error to state. useEffect(() => { API.get("http://localhost:5000/users/") .then(res => { if (res.data.length > 0) { setValues(prevState => ({ ...prevState, users: res.data.map(({ username }) => username) })); } else { setValues(prevState => ({ ...prevState, error: "Unable to locate users." })); } }) .catch(err => setValues(prevState => ({ ...prevState, error: err.toString() })) ); }, []); // the handleChange function will first deconstruct e.target.name and // e.target.value, then in the setValues callback function, it'll // spread out any previous state before updating the changed field via // [name] (e.target.name) and updating it with "value" (e.target.value) const handleChange = useCallback( ({ target: { name, value } }) => setValues(prevState => ({ ...prevState, error: "", [name]: value })), [] ); // the handleSubmit function will send a request to the API, if it // succeeds, it'll print a message and reset the form values, otherwise, // if it fails, it'll set an error to state. const handleSubmit = useCallback( e => { e.preventDefault(); const exercise = { username: values.username, description: values.description, duration: values.duration, date: values.date }; // if any fields are empty, display an error const emptyFields = Object.keys(exercise).some(field => !values[field]); if (emptyFields) { setValues(prevState => ({ ...prevState, error: "Please fill out all fields!" })); return; } API.post("http://localhost:5000/exercises/add", exercise) .then(res => { alert(JSON.stringify(res.message, null, 4)); setValues(prevState => ({ ...prevState, ...initialState })); }) .catch(err => setValues(prevState => ({ ...prevState, error: err.toString() })) ); }, [initialState, setValues, values] ); return { handleChange, handleSubmit, values }; }; export default useFormHandler; 钩子中创建一个import isEmpty from "lodash/isEmpty"; import React, { Fragment } from "react"; import { FaCalendarPlus } from "react-icons/fa"; import Spinner from "react-spinkit"; import Button from "../Button"; import Input from "../Input"; import Select from "../Select"; import useFormHandler from "../../hooks/useFormHandler"; const fields = [ { type: "text", name: "description", placeholder: "Exercise Description" }, { type: "number", name: "duration", placeholder: "Duration (in minutes)" }, { type: "date", name: "date", placeholder: "Date" } ]; // utilize the custom useFormHandler hook within a functional component and // pass it an object with some initial state. const CreateExerciseForm = () => { const { values, handleChange, handleSubmit } = useFormHandler({ username: "", description: "", duration: "", date: "", error: "" }); // the below will show a spinner if "values.users" hasn't been fulfilled yet // else, it'll show the form fields. in addition, if there's ever a // "values.error", it'll be displayed to the user. return ( <form style={{ width: 500, margin: "0 auto", textAlign: "center" }} onSubmit={handleSubmit} > {isEmpty(values.users) ? ( <Spinner name="line-scale" /> ) : ( <Fragment> <Select name="username" placeholder="Select a user..." handleChange={handleChange} value={values.username} selectOptions={values.users} style={{ width: "100%" }} /> {fields.map(({ name, type, placeholder }) => ( <Input key={name} type={type} name={name} placeholder={placeholder} onChange={handleChange} value={values[name]} /> ))} <Button type="submit"> <FaCalendarPlus style={{ position: "relative", top: 2 }} /> Create Exercise </Button> </Fragment> )} {values.error && <p>{values.error}</p>} </form> ); }; export default CreateExerciseForm; 函数:

resetValue

然后,解构用户名的属性(以及其他状态,如果需要):

useFormInput

答案 1 :(得分:1)

我看了看,做了一个带有验证的PR-Formik实现。

这里是PR-https://github.com/jeltehomminga/mern-tracker/pull/1

UI视图


<>
  <h3>Create New Exercise Log</h3>

  <pre>{JSON.stringify({ formData }, null, 2)}</pre>

  <ExerciseForm {...{ users }} onChange={data => setFormData(data)} />
</>

CreateExercise表单

import React from "react";

import * as Yup from "yup";
import { Formik, Form, Field } from "formik";

import DatePicker from "react-datepicker";
import cx from "classnames";

const requiredMessage = "Required";

const exerciseFormSchema = Yup.object().shape({
  username: Yup.string().required(requiredMessage),
  description: Yup.string()
    .min(2, "Too Short!")
    .required(requiredMessage),
  duration: Yup.number()
    .integer()
    .min(1, "Min minutes!")
    .max(60, "Max minutes!")
    .required(requiredMessage),
  date: Yup.string().required(requiredMessage)
});

const ExerciseForm = ({ users = [], onChange }) => {
  return (
    <Formik
      initialValues={{
        username: "",
        description: "",
        duration: "",
        date: ""
      }}
      validationSchema={exerciseFormSchema}
      onSubmit={values => onChange(values)}
    >
      {({
        values,
        touched,
        errors,
        handleChange,
        handleBlur,
        isSubmitting,
        setFieldValue
      }) => {
        const getProps = name => ({
          name,
          value: values[name],
          onChange: handleChange,
          onBlur: handleBlur,
          className: cx("form-control", {
            "is-invalid": errors[name]
          })
        });

        return isSubmitting ? (
          // Replace this with whatever you want...
          <p>Thanks for the Exercise!</p>
        ) : (
          <Form>
            <FormControl label="Username">
              <>
                <select {...getProps("username")}>
                  <>
                    <option value="default">Select user...</option>
                    {users.map(person => (
                      <option key={person} value={person.toLowerCase()}>
                        {person}
                      </option>
                    ))}
                  </>
                </select>
                <FormErrorMessage {...{ errors }} name="username" />
              </>
            </FormControl>

            <FormControl label="Description">
              <>
                <Field {...getProps("description")} />
                <FormErrorMessage {...{ errors }} name="description" />
              </>
            </FormControl>

            <FormControl label="Duration in minutes">
              <>
                <Field {...getProps("duration")} type="number" />
                <FormErrorMessage {...{ errors }} name="duration" />
              </>
            </FormControl>

            <FormControl label="Date">
              <>
                {/* Was present before refactor */}
                <div>
                  <DatePicker
                    {...getProps("date")}
                    selected={values.date}
                    minDate={new Date()}
                    onChange={date => setFieldValue("date", date)}
                  />
                  <FormErrorMessage {...{ errors }} name="date" />
                </div>
              </>
            </FormControl>

            <button type="submit" className="btn btn-primary">
              Create Exercise log
            </button>
          </Form>
        );
      }}
    </Formik>
  );
};

export default ExerciseForm;

// Created to manage label and parent className
const FormControl = ({ label, children }) => (
  <div className="form-group">
    <label>{label}:</label>

    {children}
  </div>
);

const FormErrorMessage = ({ name, errors }) => {
  const error = errors && errors[name];

  return error ? (
    <div
      class="invalid-feedback"
      // Add inline style override as error message cannot sit as sibling to datePicker (bootstrap css)
      style={{ display: "block" }}
    >
      {error}
    </div>
  ) : null;
};