I'm building a simple twitter clone with React and I'm facing a weird behaviour when creating a new Tweet. Right now, how it works is that I locally store a tweets array using useState and I use setState to add the new tweet into the array. According to my initial approach it works fine on the first time creating a tweet. However, on subsequent creation of tweet it is being appended twice into the array. So, all the tweets that was created after the first one will have their own respective duplicate.
Here is the screen shot of the problem
Here are the respective code:
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>
)
}
UPDATE: I managed to solve the bug (but not sure why my initial code won't work) by using the spread operator instead in the createTweetHandler function. So I am assuming it is something to do with immutability of states. Could someone explain to me why my previous code doesn't work?
Here's the updated code the apparently solved the duplicate problem:
const createTweetHandler = (newTweet) => {
console.log('Appending new tweet into state', newTweet);
setTweets((prevTweets) => {
return [...prevTweets, newTweet];
})
}
And here's the old code that was causing the issue:
const createTweetHandler = (newTweet) => {
console.log('Appending new tweet into state', newTweet);
setTweets((prevTweets) => {
console.log('setState');
prevTweets.push(newTweet);
return [...prevTweets];
})
}