javascript - How to update state value of variable that uses custom Hook - Stack Overflow

My Component has form input fields. These made use of a useState hook with their value and setValue for

My Component has form input fields. These made use of a useState hook with their value and setValue for each input field. I want to optimize my ponent so the input fields made use of the same custom Hook which I called useFormInput

Inspired by Dan Abramov see at 49:42

This works perfectly. However now I want to update the username after a new exercise is created. This is in the onSubmit method. But I'm not sure how to do this. Before I refactored I could use setUserName(), but now username is set by the generic custom hook function useFormInput

the username has an onChange method, so I thought I can maybe use this. However this uses the e.target.value because it is used for an input field.

Component: I mented out the setUserName(''), here I want to update the username

  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 = '/'
  }

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

I expect the value in the state of username is updated to an empty string ' '

Complete code is on my repo on

My Component has form input fields. These made use of a useState hook with their value and setValue for each input field. I want to optimize my ponent so the input fields made use of the same custom Hook which I called useFormInput

Inspired by Dan Abramov https://youtu.be/dpw9EHDh2bM see at 49:42

This works perfectly. However now I want to update the username after a new exercise is created. This is in the onSubmit method. But I'm not sure how to do this. Before I refactored I could use setUserName(), but now username is set by the generic custom hook function useFormInput

the username has an onChange method, so I thought I can maybe use this. However this uses the e.target.value because it is used for an input field.

Component: I mented out the setUserName(''), here I want to update the username

  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 = '/'
  }

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

I expect the value in the state of username is updated to an empty string ' '

Complete code is on my repo on https://github./jeltehomminga/mern-tracker

Share Improve this question edited Jun 2, 2019 at 8:59 Jelte Homminga asked Jun 2, 2019 at 8:49 Jelte HommingaJelte Homminga 2681 gold badge2 silver badges10 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 5

Instead of trying to maintain more than 1 state, I'd remend bining all state into one object. Then you can move everything into your custom hook. In addition, always make sure you handle and municate any errors to the user.

Working example:


State as an object

hooks/useFormHandler (the API defined below is an object with functions to mimic API calls -- you'll replace this with real API calls. Also, if you wanted to make this hook reusable for other form ponents, then you'll need to remove the useEffect and handleSubmit functions from the custom hook and place them inside the specified functional ponent instead)

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;

ponents/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 ponent 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;

State as independent data types

Or, if you insist on using separated states, then create a resetValue function in the useFormInput hook:

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

Then, destructure properties for the username (and other states, if needed):

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

I took a look and did a PR - Formik implementation w/validation.

Here is the PR - https://github./jeltehomminga/mern-tracker/pull/1

UI View


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

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

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

CreateExercise Form

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

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744176068a4561760.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信