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

标签 javascript reactjs state react-hooks

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

灵感来自 Dan Abramov https://youtu.be/dpw9EHDh2bM见 49:42

这非常有效。但是现在我想在创建新练习后更新用户名。这是在 onSubmit 方法中。但我不确定该怎么做。在重构之前,我可以使用 setUserName(),但现在用户名由通用自定义 Hook 函数 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 = '/'
  }

自定义 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
  }
}

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

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

最佳答案

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

工作示例:

Edit Hooks - Create Exercise Form


作为对象的状态

hooks/useFormHandler(下面定义的 API 是一个具有模拟 API 调用功能的对象——您将用真正的 API 调用替换它。此外,如果您想要使此 Hook 可重用于其他 form 组件,则需要从自定义 Hook 和放置中删除 useEffecthandleSubmit 函数它们在指定的功能组件中)

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;

组件/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;

状态为独立的数据类型

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

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
  }
}

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

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 )
}

关于javascript - 如何更新使用自定义 Hook 的变量的状态值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56413550/

相关文章:

javascript - 构造函数中的变量在渲染前未定义但在渲染后可见

javascript - PHP、HTML、JS执行顺序冲突

javascript - react : Having some trouble passing multiple pieces of state.

javascript - ReactJS - setState Axios POST 响应

javascript - 将异步获取的数据传递给子 Prop

regex - 筛选 Blazor 输入字段

javascript - 当输入类型日期和输入类型时间使用相同的ng-model时,如何清除仅日期或仅时间?

javascript - 带参数调用函数

node.js - Meteor 构建在部署到 Heroku 时中断

reactjs - create-react-app 的 ES6 Polyfill 不起作用