Как установить два разных состояния использования после res.data

#reactjs

Вопрос:

Я создаю внешний компонент, который будет загружать два разных изображения-фотографию обложки и фотографию аватара пользователя. Я использую node.js для моего бэкенда с express.js. Если пользователь загрузит фотографию обложки, серверная часть отправит запрос на отправку(путь к изображению, который он защищает, если изображение является фотографией обложки или аватарфото). Но у меня возникли проблемы с тем, как загрузить их обоих после того, как пользователь загрузит изображение. я использовал разные useState userInfo.coverPhoto и userInfo.avatarPhoto. Проблема в том, что после того, как пользователь нажмет на обложку, а затем на аватарфото, изображение src coverPhoto будет разбито. но после обновления изображение будет там.

 userProfile.js
    import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import axios from 'axios';
import PersonIcon from '@material-ui/icons/Person';
import {
  Container,
  Grid,
  Typography,
  TextField,
  Button,
  Modal,
  Backdrop,
} from '@material-ui/core';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import ImageIcon from '@material-ui/icons/Image';
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
import { useForm } from 'react-hook-form';
import FormContainer from '../components/FormContainer';
import useStyles from '../styles/style';
import {
  getDetailsUser,
  updateDetailsUser,
} from '../redux/actions/userActions';

const ProfilePage = ({ history }) => {
  const userDetails = useSelector((state) => state.userDetails);
  const { user } = userDetails;

  const userLogin = useSelector((state) => state.userLogin);
  const { userData } = userLogin;

  const userUpdateDetails = useSelector((state) => state.userUpdateDetails);
  const { userUpdate } = userUpdateDetails;

  const [userInfo, setUserInfo] = useState({
    id: user._id,
    username: '',
    email: '',
    fullname: '',
    coverPhoto: '',
    avatarPhoto: '',
    about: '',
    mainAddress: '',
    country: '',
    city: '',
    zipcode: '',
  });
  const [coverPreview, setCoverPreview] = useState('');
  const [avatarPreview, setAvatarPreview] = useState('');
  const [open, setOpen] = useState(false);
  const dispatch = useDispatch();
  const classes = useStyles();

  useEffect(() => {
    if (!userData) {
      history.push('/user/login');
    } else {
      if (!user || !user.username || userUpdate) {
        dispatch(getDetailsUser('profile'));
        // if (!userInfo.coverPhoto) {
        //   document.getElementById(
        //     'coverPhoto-file-button'
        //   ).nextElementSibling.style.opacity = '0';
        // }
      } else {
        setUserInfo(user);
      }
    }
  }, [userData, user, userUpdate, history]);

  const handleClose = () => {
    setOpen(false);
  };

  const handleChange = (level) => (e) => {
    e.preventDefault();
    if (!level) {
      setUserInfo({
        ...userInfo,
        [e.target.name]: e.target.value,
      });
    } else {
      setUserInfo({
        ...userInfo,
        [level]: {
          ...userInfo[level],
          [e.target.name]: e.target.value,
        },
      });
    }
  };

  const uploadCoverPhotoHandler = async (e) => {
    const imgFile = e.target.files[0];
    const imgFieldName = e.target.name;

    if (imgFile.type !== 'image/jpeg' amp;amp; imgFile.type !== 'image/png') {
      console.log('wrong format');
    }

    let formData = new FormData();
    formData.append(imgFieldName, imgFile);

    try {
      await axios({
        method: 'POST',
        url: 'http://localhost:5000/user/profile',
        data: formData,
        headers: {
          'content-type': 'multipart/form-data',
        },
      }).then((res) => {
        if (imgFieldName === 'coverPhoto') {
          setUserInfo({ coverPhoto: res.data });
        } else {
          setUserInfo({ avatarPhoto: res.data });
        }
      });
    } catch (error) {
      console.log(error.response);
    }
  };

  const onSubmit = async (e) => {
    dispatch(updateDetailsUser(userInfo));
    setOpen(true);
  };

  const { register, handleSubmit, errors, getValues } = useForm({
    mode: 'onSubmit',
    reValidateMode: 'onBlur',
  });

  return (
    <FormContainer>
      <Container component="div" className={classes.profileContainer}>
        <Modal
          className={classes.modal}
          open={open}
          closeAfterTransition
          BackdropComponent={Backdrop}
          BackdropProps={{
            root: classes.modalRoot,
            timeout: 500,
          }}
        >
          <div className={classes.modalContainer}>
            <Typography
              component="h1"
              variant="h5"
              className={classes.promptTitle}
            >
              Account has been updated!
            </Typography>
            <Button
              type="submit"
              variant="contained"
              className={classes.button}
              onClick={handleClose}
              style={{ width: '60%', marginBottom: '2.5rem' }}
            >
              Close
            </Button>
          </div>
        </Modal>
        <Grid item xs={12}>
          <Card className={classes.userProfileCard}>
            <CardContent>
              <form noValidate onSubmit={handleSubmit(onSubmit)}>
                <Typography
                  component="h1"
                  variant="h4"
                  className={classes.titleProfile}
                >
                  <PersonIcon
                    style={{
                      position: 'relative',
                      top: '5px',
                      marginRight: '0.2em',
                    }}
                  />
                  Edit Profile
                </Typography>
                <Grid container spacing={2}>
                  <Grid item xs={6}>
                    <TextField
                      variant="outlined"
                      id="username"
                      name="username"
                      type="text"
                      label="Username"
                      value={userInfo amp;amp; userInfo.username}
                      onChange={handleChange()}
                      fullWidth
                      InputLabelProps={{
                        classes: {
                          root: classes.label,
                          focused: classes.focused,
                        },
                        shrink: true,
                      }}
                      InputProps={{
                        className: classes.textfield,
                        classes: {
                          root: classes.cssOutlinedInput,
                          focused: classes.cssFocused,
                          notchedOutline: classes.notchedOutline,
                        },
                      }}
                      inputRef={register({
                        required: 'You must provide an username.',
                        minLength: {
                          value: 4,
                          message:
                            'Your username must be greater than 4 characters',
                        },
                        pattern: {
                          value: /^[A-Za-z0-9_] $/i,
                          message:
                            'Username may only have letters, number and underscores.',
                        },
                      })}
                    />
                    {errors.username amp;amp; (
                      <span className={classes.error}>
                        {errors.username.message}
                      </span>
                    )}
                  </Grid>
                  <Grid item xs={6}>
                    <TextField
                      variant="outlined"
                      id="email"
                      name="email"
                      type="email"
                      label="Email"
                      value={userInfo amp;amp; userInfo.email}
                      onChange={handleChange()}
                      fullWidth
                      InputLabelProps={{
                        classes: {
                          root: classes.label,
                          focused: classes.focused,
                        },
                        shrink: true,
                      }}
                      InputProps={{
                        className: classes.textfield,
                        classes: {
                          root: classes.cssOutlinedInput,
                          focused: classes.cssFocused,
                          notchedOutline: classes.notchedOutline,
                        },
                      }}
                      inputRef={register({
                        required: 'You must provide a email.',
                        pattern: {
                          value: /^[^@ ] @[^@ ] .[^@ .]{2,}$/,
                          message: 'You must provide a valid email address!',
                        },
                      })}
                    />
                    {errors.email amp;amp; (
                      <span className={classes.error}>
                        {errors.email.message}
                      </span>
                    )}
                  </Grid>
                  <Grid item xs={12}>
                    <TextField
                      variant="outlined"
                      id="fullname"
                      name="fullname"
                      type="text"
                      label="Fullname"
                      value={userInfo amp;amp; userInfo.fullname}
                      onChange={handleChange()}
                      fullWidth
                      InputLabelProps={{
                        classes: {
                          root: classes.label,
                          focused: classes.focused,
                        },
                        shrink: true,
                      }}
                      InputProps={{
                        className: classes.textfield,
                        classes: {
                          root: classes.cssOutlinedInput,
                          focused: classes.cssFocused,
                          notchedOutline: classes.notchedOutline,
                        },
                      }}
                      inputRef={register({
                        required: 'You must provide a fullname.',
                        minLength: {
                          value: 6,
                          message:
                            'Your password must be greater than 6 characters',
                        },
                        pattern: {
                          value: /^[A-Za-z ] $/i,
                          message: 'Alphabetical characters only',
                        },
                      })}
                    />
                    {errors.fullname amp;amp; (
                      <span className={classes.error}>
                        {errors.fullname.message}
                      </span>
                    )}
                  </Grid>
                  <Grid item xs={12}>
                    <TextField
                      variant="outlined"
                      id="mainAddress"
                      name="mainAddress"
                      type="text"
                      label="Address"
                      value={userInfo.address amp;amp; userInfo.address.mainAddress}
                      onChange={handleChange('address')}
                      fullWidth
                      InputLabelProps={{
                        classes: {
                          root: classes.label,
                          focused: classes.focused,
                        },
                        shrink: true,
                      }}
                      InputProps={{
                        className: classes.textfield,
                        classes: {
                          root: classes.cssOutlinedInput,
                          focused: classes.cssFocused,
                          notchedOutline: classes.notchedOutline,
                        },
                      }}
                    />
                  </Grid>
                  <Grid item xs={2}>
                    <TextField
                      variant="outlined"
                      id="city"
                      name="city"
                      type="text"
                      label="City"
                      value={userInfo.address amp;amp; userInfo.address.city}
                      onChange={handleChange('address')}
                      fullWidth
                      InputLabelProps={{
                        classes: {
                          root: classes.label,
                          focused: classes.focused,
                        },
                        shrink: true,
                      }}
                      InputProps={{
                        className: classes.textfield,
                        classes: {
                          root: classes.cssOutlinedInput,
                          focused: classes.cssFocused,
                          notchedOutline: classes.notchedOutline,
                        },
                      }}
                    />
                  </Grid>
                  <Grid item xs={2}>
                    <TextField
                      variant="outlined"
                      id="country"
                      name="country"
                      type="text"
                      label="Country"
                      value={userInfo.address amp;amp; userInfo.address.country}
                      onChange={handleChange('address')}
                      fullWidth
                      InputLabelProps={{
                        classes: {
                          root: classes.label,
                          focused: classes.focused,
                        },
                        shrink: true,
                      }}
                      InputProps={{
                        className: classes.textfield,
                        classes: {
                          root: classes.cssOutlinedInput,
                          focused: classes.cssFocused,
                          notchedOutline: classes.notchedOutline,
                        },
                      }}
                    />
                  </Grid>
                  <Grid item xs={2}>
                    <TextField
                      variant="outlined"
                      id="zipcode"
                      name="zipcode"
                      type="text"
                      label="Zipcode"
                      value={userInfo.address amp;amp; userInfo.address.zipcode}
                      onChange={handleChange('address')}
                      fullWidth
                      InputLabelProps={{
                        shrink: true,
                        classes: {
                          root: classes.label,
                          focused: classes.focused,
                        },
                      }}
                      InputProps={{
                        className: classes.textfield,
                        classes: {
                          root: classes.cssOutlinedInput,
                          focused: classes.cssFocused,
                          notchedOutline: classes.notchedOutline,
                        },
                      }}
                      inputRef={register({
                        pattern: {
                          value: /^[0-9] ([0-9] )?$/,
                          message: 'Numbers only',
                        },
                      })}
                    />
                    {errors.zipcode amp;amp; (
                      <span className={classes.error}>
                        {errors.zipcode.message}
                      </span>
                    )}
                  </Grid>
                  <Grid item xs={12}>
                    <Grid item xs={12}>
                      <TextField
                        variant="outlined"
                        id="aboutme"
                        name="about"
                        type="text"
                        label="About me"
                        value={userInfo amp;amp; userInfo.about}
                        onChange={handleChange()}
                        fullWidth
                        multiline
                        rows={2}
                        InputLabelProps={{
                          classes: {
                            root: classes.label,
                            focused: classes.focused,
                          },
                          shrink: true,
                        }}
                        InputProps={{
                          className: classes.textfield,
                          classes: {
                            root: classes.cssOutlinedInput,
                            focused: classes.cssFocused,
                            notchedOutline: classes.notchedOutline,
                          },
                        }}
                      />
                    </Grid>
                  </Grid>
                  <Grid item xs={2}>
                    <Button
                      type="submit"
                      fullWidth
                      variant="contained"
                      className={classes.button}
                    >
                      Update
                    </Button>
                  </Grid>
                </Grid>
              </form>
            </CardContent>
          </Card>
        </Grid>
        <Grid item xs={12}>
          <Card className={classes.userCard}>
            <form id="userPhoto">
              <CardContent className={classes.coverPhotoContainer}>
                <CardMedia
                  component="img"
                  src={userInfo amp;amp; userInfo.coverPhoto}
                  height="180rem"
                  onload="this.style.display=''"
                />
                <input
                  accept="image/*"
                  id="coverPhoto-file-button"
                  type="file"
                  name="coverPhoto"
                  style={{ display: 'none' }}
                  onChange={uploadCoverPhotoHandler}
                />
                <label htmlFor="coverPhoto-file-button">
                  <ImageIcon />
                  Add Cover Photo
                </label>
              </CardContent>
              <CardContent className={classes.imgProfileContainer}>
                <CardMedia
                  component="img"
                  src={userInfo amp;amp; userInfo.avatarPhoto}
                  height="180rem"
                  onload="this.style.display=''"
                />
                <input
                  accept="image/*"
                  id="imgProfile-file-button"
                  type="file"
                  name="avatarPhoto"
                  style={{ display: 'none' }}
                  onChange={uploadCoverPhotoHandler}
                />
                <label htmlFor="imgProfile-file-button">
                  <AccountCircleIcon />
                </label>
              </CardContent>
            </form>
            <CardContent>
              <Typography
                className={classes.titleProfileUsername}
                variant="h6"
                component="h1"
              >
                {userInfo amp;amp; userInfo.username}
              </Typography>
              <Typography
                className={classes.profileAbout}
                variant="body2"
                component="p"
              ></Typography>
            </CardContent>
          </Card>
        </Grid>
      </Container>
    </FormContainer>
  );
};

export default ProfilePage;
 

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

Ответ №1:

useState работает иначе this.setState , чем в компонентах класса.

при записи setUserInfo({ coverPhoto: res.data }) вы удаляете все остальные поля из userInfo .

Тебе нужно написать:

setUserInfo(prevState => ({ ...prevState, coverPhoto: res.data }))

 }).then((res) => {
        if (imgFieldName === 'coverPhoto') {
         setUserInfo(prevState => ({ ...prevState, coverPhoto: res.data }));
        } else {
          setUserInfo(prevState => ({ ...prevState, avatarPhoto: res.data }));
        }
      });
 

Комментарии:

1. Святая Молли, я забыл, что должен был использовать оператор распространения. таким образом, предыдущее состояние не изменится. Огромное спасибо. Я упускаю из виду эту часть.