【问题标题】:React setState array is appending the same item twice on second call onwardReact setState 数组在第二次调用时附加相同的项目两次
【发布时间】:2021-12-18 09:44:29
【问题描述】:

我正在使用 React 构建一个简单的 twitter 克隆,并且在创建新 Tweet 时遇到了奇怪的行为。现在,它的工作原理是我使用 useState 在本地存储推文数组,然后使用 setState 将新推文添加到数组中。根据我最初的方法,它在第一次创建推文时效果很好。但是,在随后创建推文时,它被两次附加到数组中。因此,在第一个推文之后创建的所有推文都会有各自的副本。

这里是问题的截图

以下是相应的代码:

Home.js

import React, { useState } from "react";
import { MainContentWrapper, StyledHeader } from "../style";
import { Tweet } from "../Tweet";
import { CreateTweet } from "./CreateTweet";

function Home() {
  const [ tweets, setTweets ] = useState([{
    id: 1,
    content: 'Alyssa\'s First Tweet!',
    createdAt: '2021-10-28T21:33:41.453Z',
    user: {
      username: 'aly',
      name: 'Alyssa Holmes',
    }
  }, {
    id: 2, 
    content: 'Hello Twitter Clone!',
    createdAt: '2021-10-28T21:33:41.453Z',
    user: {
      username: 'martinxz',
      name: 'Martin San Diego'
    }
  }, {
    id: 3,
    content: 'Going to starbucks today :D',
    user: {
      username: 'rickyyy',
      name: 'Rick & Morty'
    }
  }]);
  
  const createTweetHandler = (newTweet) => {
    console.log('Appending new tweet into state', newTweet); 
    setTweets((prevTweets) => {
      console.log('setState');
      prevTweets.push(newTweet);
      return [...prevTweets];
    })
  }

  return (
    <MainContentWrapper>
      <StyledHeader>
        <h3>Home</h3>
      </StyledHeader>
    <CreateTweet onCreateTweet={createTweetHandler}/>
      { tweets.map((tweet) => {
        return <Tweet key={tweet.id} tweet={tweet} />
      }) }
    </MainContentWrapper>
  );
}

export default Home;

CreateTweet.js

import React from 'react';
import { useForm } from "react-hook-form";
import { useSelector } from 'react-redux';
import { authUser } from '../../authentication/authenticationSlice';
import styled from 'styled-components/macro';
import avatarImg from "../../../assets/images/avatar_placeholder.jpg";
import { Button } from '../../../shared/Button.styled';
import { MainContentWrapper, StyledAvatar,StyledText } from '../style';

const CreateTweetWrapper = styled(MainContentWrapper)`
  & form {
    padding: 10px 20px;
    margin: 0 0 5px 0;
    display: grid;
    grid-template-columns: min-content 1fr;
    grid-gap: 5px 20px;
    align-items: center;
    border-width: 0 0 1px 0;
  }

`

const StyledInput = styled.textarea`
  background-color: ${({ theme }) => theme.colors.background};
  color: ${({ theme }) => theme.colors.text};
  border: none;
  font-size: 20px;
  resize: none;
  &:focus {
    outline: none;
  }
`;

const ButtonWrapper = styled.div`
  grid-column: 2;
  display:flex;
  justify-content: space-between;
  align-items: center;
`;

const StyledButton = styled(Button)`
  justify-self: end;
  height: 35px;
  padding: 0 15px;
`

export function CreateTweet({onCreateTweet}) {
  const user = useSelector(authUser);
  const { register, handleSubmit } = useForm({
    defaultValues: {
      content: "",
    },
  });
  const onSubmit = (data) => {
    const newTweet = {
      id: Math.floor(Math.random() * 10000),
      content: data.content,
      user: {
        username: user.username,
        name: user.attributes.name
      }
    }
    console.log('happens here');
    onCreateTweet(newTweet);
  }
 
  return (
    <CreateTweetWrapper>
      <form>
        <StyledAvatar src={`${avatarImg}`}/>
        <StyledInput rows="1" placeholder="What's happening?" {...register("content")} />
        <ButtonWrapper>
          <StyledText> Icons </StyledText>
          <StyledButton buttonType="secondary" type="submit" onClick={handleSubmit(onSubmit)}>Tweet</StyledButton>
        </ButtonWrapper>
      </form>
    </CreateTweetWrapper>
  )
}

更新:我设法通过在 createTweetHandler 函数中使用扩展运算符来解决错误(但不确定为什么我的初始代码不起作用)。所以我假设这与状态的不变性有关。有人可以向我解释为什么我以前的代码不起作用吗?

这是显然解决了重复问题的更新代码:

  const createTweetHandler = (newTweet) => {
    console.log('Appending new tweet into state', newTweet); 
    setTweets((prevTweets) => {
      return [...prevTweets, newTweet];
    })
  }

这是导致问题的旧代码:

  const createTweetHandler = (newTweet) => {
    console.log('Appending new tweet into state', newTweet); 
    setTweets((prevTweets) => {
      console.log('setState');
      prevTweets.push(newTweet);
      return [...prevTweets];
    })
  }

【问题讨论】:

    标签: javascript reactjs react-hooks use-state


    【解决方案1】:

    问题

    当你进入之前的状态时,你正在改变它。

    setTweets((prevTweets) => {
      console.log('setState');
      prevTweets.push(newTweet); // <-- mutates state!!
      return [...prevTweets];
    })
    

    原因通常是您的应用被渲染成一个React.StrictMode 组件,该组件调用某些 函数/生命周期方法两次,以帮助您检测意外的副作用,例如状态突变。

    Detecting unexpected side effects

    严格模式不能自动为您检测副作用,但它 可以通过使它们更具确定性来帮助您发现它们。 这是通过有意双重调用来完成的 功能:

    • 类组件constructorrendershouldComponentUpdate方法
    • 类组件静态getDerivedStateFromProps方法
    • 函数组件体
    • 状态更新函数(setState 的第一个参数)
    • 函数传递给 useStateuseMemouseReducer

    这意味着在第二次调用时,您会看到突变的结果,即第一个 .push,然后出现第二个 .push,因此最终结果是 两个 添加了新元素!

    解决方案

    正如您所发现的,解决方法是改变状态,而是应用不可变的更新模式。 Redux 找到了一个很好的解释 here

    这个想法是创建状态的浅表副本,而不会对现有状态进行任何更改。

    setTweets((prevTweets) => {
      return [
        ...prevTweets, // shallow copy into new array reference
        newTweet,      // append new element
      ];
    })
    

    setTweets((prevTweets) => {
      return prevTweets.concat(newTweet); // append and return new array reference
    })
    

    【讨论】:

      猜你喜欢
      • 2017-07-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-17
      • 2016-06-25
      • 1970-01-01
      • 2020-12-31
      相关资源
      最近更新 更多