So I am Trying to implement Google Forms Clone and I want to add new question div at a specific index. But my div array's state gets lost.
//array of Divs
const [questionDivs, setQuestionDivs] = useState([]);
//Function to add new div to the questionDivs at a specific index
const addQuestionDiv = () => {
const newKey = `question_${questionDivs.length + 1}`;
const newQuestionDivs = [...questionDivs];
newQuestionDivs.splice(selectedDivIndex + 1, 0, (
<div
key={newKey}
className="my-[1%] ml-[28%] w-[44%] min-h-[30%]"
onClick={handleQuestionClick}
>
{<Questions key={newKey}/>}
</div>
));
setQuestionDivs(newQuestionDivs);
};
//Rendering the divs
{questionDivs.map((questionDiv, index) => (
<div key={`question_${index}`} onClick={()=> SetCurrentDivHandler(index)}>{questionDiv}</div>
))}
[Here are 2 question divs and when i press the + icon which i Highlighted in the image, I want a new div to be added right below the current selected div, I have already implemented a function where you get the index of the current div. ] (https://i.sstatic.net/jskYD.png) [When I press the + button a new div is getting added but the div which is below it is loosing its state, It had Option A and Option B but now it is empty after Insertion of new Div. But the div above that didn't loose its state. To whichever index I try to add a new div, the indexes which comes after that looses their states. I even tried looping but same results] (https://i.sstatic.net/a8OAT.png)
React uses key
s as part of the decision to update the DOM. A comparison is made between the old DOM tree and the new DOM tree. For elements with the same key, React will decide it can keep the DOM trees the same.
{ questionDivs.map((questionDiv, index) => ( <div key={`question_${index}`} onClick={() => SetCurrentDivHandler(index)} > {questionDiv} </div> )) }
As you map over questionDivs
you return a div
with its key essentially set to its index position in the array (we can ignore the question_
prefix because that is constant). When you insert something, all subsequent items will have their keys changed, resulting in React rebuilding those trees and losing state. Here's a demo of the problem, note the div
using the index key wrapping the Question
component – this demo is also reused in the solution so a comparison can be easily made.
const { createRoot } = ReactDOM;
const { StrictMode, useEffect, useState } = React;
function Question({ addQuestion, updateQuestion }) {
const [title, setTitle] = useState("");
const [state, setState] = useState("");
const handleTitleChange = (event) => {
setTitle(event.target.value);
};
const handleStateChange = (event) => {
setState(event.target.value);
};
return (
<div>
<input
type="text"
placeholder="Question"
value={title}
onChange={handleTitleChange}
/>
<br/>
<textarea
value={state}
placeholder="Some other state"
onChange={handleStateChange}
/>
<button aria-label="Add after" onClick={addQuestion}>✚</button>
</div>
);
}
function App() {
const [questions, setQuestions] = useState([crypto.randomUUID()]);
const addQuestionAfter = (index) => {
const updatedQuestions = [...questions];
updatedQuestions.splice(index + 1, 0, crypto.randomUUID());
setQuestions(updatedQuestions);
};
return (
<div>
{
questions.map((questionKey, index) => (
<div key={index}>
<Question
key={questionKey}
addQuestion={() => addQuestionAfter(index)}
/>
</div>
))
}
</div>
);
}
const root = createRoot(document.getElementById("root"));
root.render(<StrictMode><App /></StrictMode>);
body {
font-family: san-serif;
}
input, textarea {
width: 50vw;
}
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
You need more stable keys. IDs of some sort are a good choice. Not critical to the solution but generally important: you should also consider thinking about views as a representation of data – this means instead of storing parts of the view as JSX inside an array, just storing the data that represents it and mapping it across to the JSX representation. Below is a demo, note the following:
Questions
component).The demo is also naive in the sense that the state is an array of IDs. A more complex application might store a list/map of objects representing the state of each question instead which would be kept in sync with a server. However, given the complexity, I won't show that in the demo.
const { createRoot } = ReactDOM;
const { StrictMode, useEffect, useState } = React;
function Question({ addQuestion, updateQuestion }) {
const [title, setTitle] = useState("");
const [state, setState] = useState("");
const handleTitleChange = (event) => {
setTitle(event.target.value);
};
const handleStateChange = (event) => {
setState(event.target.value);
};
return (
<div>
<input
type="text"
placeholder="Question"
value={title}
onChange={handleTitleChange}
/>
<br/>
<textarea
value={state}
placeholder="Some other state"
onChange={handleStateChange}
/>
<button aria-label="Add after" onClick={addQuestion}>✚</button>
</div>
);
}
function App() {
const [questions, setQuestions] = useState([crypto.randomUUID()]);
const addQuestionAfter = (index) => {
const updatedQuestions = [...questions];
updatedQuestions.splice(index + 1, 0, crypto.randomUUID());
setQuestions(updatedQuestions);
};
return (
<div>
{
questions.map((questionKey, index) => (
<Question
key={questionKey}
addQuestion={() => addQuestionAfter(index)}
/>
))
}
</div>
);
}
const root = createRoot(document.getElementById("root"));
root.render(<StrictMode><App /></StrictMode>);
body {
font-family: san-serif;
}
input, textarea {
width: 50vw;
}
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>