https://codesandbox.io/s/sad-hoover-d4e1wl?file=/src/App.js
In my interactive comments section, comment components are dynamically rendered in the App.js by mapping through an array of comments stored in state that is added to when the user submits a comment from the create box. You can add replies which are stored in state in each component similar to the comments with an array and mapped over, but these replies for each individual comment should persist between renders. However, without any error messages, it seems that the replies for each dynamically rendered comment are deleted on rerender, but any replies to comments hardcoded in the app.js maintain their replies just fine. I tried to use a Ref, hoping that would maintain state between renders, but that broke functionality completely for adding replies to the dynamically rendered comments. This begs the question, is it impossible to maintain individual state in dynamically rendered components? or am I missing something about the bigger picture of React.js's system? I supplied a code sandbox to demonstrate the issue and functionality. Again, the issue is that replies (the state) you add to hard coded comment component stay between renders (when you add a new comment at the bottom it rerenders), but dynamically rendered comments lose their replies upon re-render. Thank you!
CodePudding user response:
React does maintain state between renders (thats key to its purpose). The problem is you are making a nanoid()
call in render and using it for the key
property in several places. You should never use a random ID that changes on each render for the key -- as this key would be "unstable".
In react when key
changes, it will entirely unmount and destroy the component and load it again. Which is why you are losing state.
The random ID you store in state with each comment/reply is fine though. Yes its random, but its set once when its added and does not change in each render. This key is what is called "stable". Which is a requirement.
To fix, just use the ID you stored in state for the key for each instance nanoid()
is called inside of render.
App.js
import Comment from "./Comment.js";
import "./styles.css";
import React, { useState } from "react";
import { nanoid } from "nanoid";
function App() {
const [comments, setComments] = useState([]);
function addComment(e) {
let input = e.target[0].value;
e.preventDefault();
setComments((comments) =>
comments.concat({
key: nanoid(),
comment: input
})
);
}
return (
<div id="flex-container">
<Comment username={"user1"}></Comment>
<Comment username={"user2"}></Comment>
{comments.map((comment) => (
<Comment
key={comment.key}
username={"you"}
commentText={comment.comment}
></Comment>
))}
<Comment
username={"you"}
isCreateBox={true}
addComment={addComment}
></Comment>
</div>
);
}
export default App;
Comment.js
import React, { useState, useEffect } from "react";
import Reply from "./Reply.js";
import CommentText from "./CommentText.js";
// import CommentText from "./CommentText.js";
import { nanoid } from "nanoid";
function Comment({ isCreateBox, username, isReply, addComment, commentText }) {
const [replies, setReplies] = useState([]);
function addReply() {
setReplies((replies) =>
replies.concat({
key: nanoid()
})
);
}
if (isCreateBox) {
return (
<div className="comment-container">
<div className="comment">
<div className="comment-content-container">
<div className="comment-header-container">
<form onSubmit={addComment}>
<input></input>
<button type="submit">Send</button>
</form>
</div>
</div>
</div>
</div>
);
} else if (isReply) {
return (
<div className="comment-container">
<div className="comment">
<div className="comment-content-container">
<div className="comment-header-container">
<div className="username">{username}</div>
<Reply addReply={addReply}></Reply>
</div>
{/* <CommentText username={username}></CommentText> */}
</div>
</div>
<div className="reply-container">
{replies.map((reply) => (
<Comment key={reply.key} isReply={true} username={"you"}></Comment>
))}
</div>
</div>
);
} else {
return (
<div className="comment-container">
<div className="comment">
<div className="comment-content-container">
<div className="comment-header-container">
<div className="username">{username}</div>
<Reply addReply={addReply}></Reply>
</div>
<CommentText
username={username}
commentText={commentText}
></CommentText>
</div>
</div>
<div className="reply-container">
{replies.map((reply) => (
<Comment key={reply.key} isReply={true} username={"you"}></Comment>
))}
</div>
</div>
);
}
}
export default Comment;