Как сделать функцию, подобную функции componentDidUpdate, в функциональном компоненте React? - PullRequest
2 голосов
/ 23 июня 2019

Я пытаюсь установить сообщение об ошибке для формы регистрации, но поскольку я использую пользовательский интерфейс материала, мне пришлось использовать функциональный компонент для моего проекта. Я следовал учебнику на YouTube, но парень использовал компонент класса. Мне удалось преобразовать большинство кодов в функциональный компонент, но я потерялся в функции componentDidUpdate.

Я также пытался использовать функцию useEffect, но безрезультатно, что я не смог отобразить ошибку / сообщение об ошибке, когда кто-то нажимает кнопку отправки, если они не вводят никаких полей. Я также получил сообщение об ошибке, что сообщение msg не определено в компоненте Alert в компоненте SignUp, хотя оно уже определено в функции useState.

{msg ? <Alert color="danger">{msg}</Alert> : null}

Ниже приведен код для компонента SignUp

import React, { useState, useEffect } from "react";
import Avatar from "@material-ui/core/Avatar";
import Button from "@material-ui/core/Button";
import CssBaseline from "@material-ui/core/CssBaseline";
import TextField from "@material-ui/core/TextField";
import Link from "@material-ui/core/Link";
import Grid from "@material-ui/core/Grid";
import Box from "@material-ui/core/Box";
import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/core/styles";
import Container from "@material-ui/core/Container";
import Logo from "./assets/images/logo_transparent.png";
import { Alert } from "reactstrap";


//redux
import { connect } from "react-redux";
//proptypes
import PropTypes from "prop-types";
import { register } from "./actions/authActions";


const useStyles = makeStyles(theme => ({
//styles- for brevity only
})); 

function SignUp(props) {
  const classes = useStyles();
  const [form, setValues] = useState({
    name: "",
    email: "",
    password: "",
    msg: null
  });

  // Similar to componentDidMount and componentDidUpdate
  //By running an empty array [] as a second argument,
  //we’re letting React know that the useEffect function doesn’t
  //depend on any values from props or state.
  useEffect(() => {
    const { error } = props;
    if (error !== form.error) {
      //check for register error
      if (error.id === "REGISTER_FAIL") {
        setValues({ msg: error.msg.msg });
      } else {
        setValues({ msg: null });
      }
    }
  }, []);

  const onChange = e => {
    setValues({
      ...form,
      [e.target.name]: e.target.value,
      [e.target.email]: e.target.value,
      [e.target.password]: e.target.value
    });
  };

  const handleClick = e => {
    e.preventDefault();
    const { name, email, password } = form;

    //create user object
    const newUser = {
      name,
      email,
      password
    };

    //attempt to register
    props.register(newUser);

    window.confirm("Registration details: " + name + " " + email);
    //window.location.href = "http://localhost:3000/";
  };

  return (
    <Container component="main" maxWidth="xs">
      <CssBaseline />

      <div className={classes.paper}>
        {msg ? <Alert color="danger">{msg}</Alert> : null}
        <form className={classes.form} noValidate>
          <Grid container spacing={2}>
            <Grid item xs={12}>
              <TextField
                autoComplete="name"
                name="name"
                variant="outlined"
                required
                fullWidth
                id="name"
                label="Full Name"
                autoFocus
                onChange={onChange}
              />
            </Grid>
            <Grid item xs={12}>
              <TextField
                variant="outlined"
                required
                fullWidth
                id="email"
                label="Email Address"
                name="email"
                autoComplete="email"
                onChange={onChange}
              />
            </Grid>
            <Grid item xs={12}>
              <TextField
                variant="outlined"
                required
                fullWidth
                name="password"
                label="Password"
                type="password"
                autoComplete="current-password"
                onChange={onChange}
              />
            </Grid>
            <Grid item xs={12}>
              <TextField
                variant="outlined"
                required
                fullWidth
                name="cpassword"
                label="Confirm Password"
                type="password"
                id="cpassword"
                autoComplete="confirm-password"
              />
            </Grid>
            <Grid item xs={12} />
          </Grid>
          <Button
            type="submit"
            fullWidth
            variant="contained"
            color="primary"
            className={classes.submit}
            onClick={handleClick}
          >
            Sign Up
          </Button>

          <Button
            href="http://localhost:3000/"
            fullWidth
            variant="contained"
            color="primary"
            className={classes.home}
          >
            Home
          </Button>
          <br />
          <br />

          <Grid container justify="flex-end">
            <Grid item>
              <Link href="http://localhost:3000/signin" variant="body2">
                Already have an account? Sign in
              </Link>
            </Grid>
          </Grid>
        </form>
      </div>
      <Box mt={5}>
      </Box>
    </Container>
  );
}

SignUp.propTypes = {
  isAuthenticated: PropTypes.bool,
  error: PropTypes.object.isRequired,
  register: PropTypes.func.isRequired
};

const mapStateToProps = state => ({
  isAuthenticated: state.auth.isAuthenticated,
  error: state.error //getting from reducer
});

export default connect(
  mapStateToProps,
  { register } //from redux actions //mapdispatchtoprop
)(SignUp); //component 

Это оригинальная функция componentDidUpdate

componentDidUpdate(prevProps) {
  const {error} = this.props;
  if(error !== prevProps.error){
    //check for register error
    if(error.id === "REGISTER_FAIL"){
      this.setState({msg:error.msg.msg});
    }else {
        setValues({ msg: null });
      }
  }
}

Ответы [ 3 ]

3 голосов
/ 23 июня 2019

Когда вы думаете о useEffect как о функции, которая запускается при использовании «побочного эффекта» (изменение состояния) в качестве componentDidUpdate, вам необходимо следить за изменениями в состояниях error и msg.

Вместо

  // Similar to componentDidMount and componentDidUpdate
  //By running an empty array [] as a second argument,
  //we’re letting React know that the useEffect function doesn’t
  //depend on any values from props or state.
  useEffect(() => {
    const { error } = props;
    if (error !== form.error) {
      //check for register error
      if (error.id === "REGISTER_FAIL") {
        setValues({ msg: error.msg});
      } else {
        setValues({ msg: null });
      }
    }
  }, []);

Вам нужно сделать

  useEffect(() => {
    const { error } = props;
    if (error !== form.error) {
      //check for register error
      if (error.id === "REGISTER_FAIL") {
-        setValues(({ msg: error.msg});
+       setValues({ ...form, msg: error.msg});
      } else {
-       setValues({ msg: null });
+       setValues({ ...form, msg: null });
      }
    }
  }, [error, form.msg]);

Причина, по которой я распространяю ...form как setValues({ ...form, msg: error.msg.msg }), заключается в том, что функция обновления (2-й аргумент), возвращаемая React.useState, не обновляет другие свойства в состоянии.

Так что setValues({ msg: null }); превратится form с

{
  name: 'previous name',
  password: 'previous password',
  email: 'previous email',
  msg: 'previous message...'
}

в {msg: null} удаление name, email, password свойств.

Поскольку поведение между React.useState в обновителе ("aliased" как setState в React документации ) и setState отличается, кто-то в итоге создал use-legay-state NPM пакет , который вы, вероятно, не должны использовать без крайней необходимости.

примечание

Не имеющий отношения к вашему вопросу, вы также хотели бы 1. разделить name/email/password/msg на отдельные состояния 2. "или" используйте useReducer hook.

Изменение реализации для случая 1

const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [message, setMessage] = useState('');

Если вы хотите продолжать использовать объект form, вы можете следовать соглашению об именах, изменив setValues на setForm или const [formValues, setFormValues] = useState({...}).

И вы можете обновить каждое поле, как,

<TextField
  autoComplete="name"
  name="name"
  variant="outlined"
  required
  fullWidth
  id="name"
  label="Full Name"
  autoFocus
  value={name}
  onChange={e => setName(e.target.value)}
/>

Вы можете использовать useCallback для onChange как onChange={useCallback(e => setName(e.target.value), [name])} здесь, но не обязательно, если у вас нет проблем с производительностью. Начните с простого изначально ?

Кроме того, ваш useEffect зависит только от msg.

  useEffect(() => {
    const { error } = props;
    if (error !== form.error) {
      //check for register error
      if (error.id === "REGISTER_FAIL") {
       setMessage(error.msg);
      } else {
       setMessage(null);
      }
    }
  }, [error, message]);

Здесь я изменил setValues на setMessage и массив зависимостей с [error, form] на [error, message].

Изменение реализации для случая 2

Если вы хотите использовать useReducer, вам кажется, что вам нужно больше работать, так как вы изменяете поле индивидуальной формы, которое изменяется отдельно. useReducer работает лучше, когда вы обновляете связанный набор состояний, которые должны изменяться вместе. Но в вашем случае каждое состояние (password/name и т. Д.) Изменяется независимо от пользовательского ввода, поэтому было бы легче приступить к описанному выше случаю № 1.

1 голос
/ 23 июня 2019

useEffect с пустым массивом зависимостей будет не действовать как componentDidUpdate, он будет только работать при первом монтировании компонента и не будет, когда компонент re-renders

Для имитации componentDidUpdate укажите error в качестве зависимости.

const [errorMsg, setErrorMsg] = useState('');

useEffect(() => {
  if (error.id === "REGISTER_FAIL") {
    setErrorMsg(props.error.msg.msg);
  }
}, [props.error])

return (
  <div className="App">
  {errorMsg && <span>{errorMsg}</span>}
  </div>
);

Я бы также рекомендовал каждый раз, когда вы отправляете форму, отправлять действие и сбрасывать ошибку msg до ложного значения'' в магазине

Таким образом, при каждой отправке вы не увидите последний error и не нужно сравнивать errors.

1 голос
/ 23 июня 2019

Я создал минимальный пример с проверкой поля формы и функциональными компонентами. Обратите внимание, что вы можете написать любой компонент как функцию и как класс. Я лично предпочитаю функции, потому что они часто бывают более краткими.

Запустите пример здесь: https://codesandbox.io/s/zen-antonelli-noovi?fontsize=14

import React, { useState } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const [error, setError] = useState(null);
  return (
    <div>
     {error && <div>{error}</div>}
      <form>
        <input
          type="email"
          onInvalid={() => setError("That is not an email.")}
          required
        />
        <input type="submit" />
      </form>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...