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

Jel*_*nga 1 javascript state reactjs react-hooks

我的组件有表单输入字段。这些使用了 useState 钩子及其值和每个输入字段的 setValue。我想优化我的组件,以便输入字段使用相同的自定义 Hook,我称之为 useFormInput

灵感来自 Dan Abramov 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 = '/'
  }
Run Code Online (Sandbox Code Playgroud)

自定义 Hook 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
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Mat*_*tta 5

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

工作示例

编辑挂钩 - 创建练习表


状态作为对象

hooks/useFormHandlerAPI下面定义的是一个具有模拟 API 调用功能的对象——您将用真正的 API 调用替换它。另外,如果您想让这个钩子可重用于其他form组件,那么您需要删除useEffect以及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;
Run Code Online (Sandbox Code Playgroud)

组件/CreateExerciseForm

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;
Run Code Online (Sandbox Code Playgroud)

状态作为独立的数据类型

或者,如果您坚持使用分离状态,则在挂钩resetValue中创建一个函数useFormInput

const useFormInput = initialValue => {
  // initialize state from "initialValue"
  const [value, setValue] = useState(initialValue)

  // handle changes to the "value" state via updating it
  // with e.target.value
  const handleChange = useCallback(({ target: { value } => {
    setValue(value)
  }, []);

  // reset the value back to initialValue
  const resetValue = useCallback(() => {
    setValue(initialValue);
  }, []);


  return {
    value,
    handleChange,
    resetValue
  }
}
Run Code Online (Sandbox Code Playgroud)

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

const CreateExercise = () => {
  // use ES6 destructure and aliasing to extract and rename the 
  // "value" (as username), "handleChange" function (as 
  // handleUsernameChange) and "resetValue" function (as resetUsername)
  const { 
    value: username, 
    handleChange: handleUsernameChange, 
    resetValue: resetUsername 
  } = useFormInput('')

  ...other form state

  ...useEffect(() => {}, [])

  const handleSubmit = useCallback(e => {
    e.preventDefault();

    const exercise = {
      username: username,
      description: description,
      duration: duration,
      date: date
    };

    axios
      .post('http://localhost:5000/exercises/add', exercise)
      .then(res => {
        console.log(res.data)
        // only reset the username if the exercise was successfully
        // created
        resetUsername();
      })
      .catch(err => console.log(err.toString());

  }, [date, description, duration, resetUsername, username]);

  return ( ...form )
}
Run Code Online (Sandbox Code Playgroud)