I am trying to dynamically update a text input value as the user types (the value loads as data from a state that is set in the useEffect), but it is causing me some trouble. The value does not seem to be updating with the state.
Ive got a state called 'questions' that holds an array of different questions that i fetch from my back end.
Here is an example of the data that is fetched form my back end.
[
{
"uuid": "mS2ZeSSF3N3AomPAaSnnVE",
"question": "test questiong",
"question_type": "Multiple Choice",
"question_answers": "Strongly Disagree/Disagree/Neither Agree Nor Disagree/Agree/Strongly Agree",
"section": "9UCuJXiuyNL3fLMbkMtBcW"
},
{
"uuid": "Xb3xytncs2S6rQtUnAxvvY",
"question": "test question 2",
"question_type": "Text",
"question_answers": null,
"section": "9UCuJXiuyNL3fLMbkMtBcW"
}
]
This data is the set as the questions state
Im creating an update page, so am populating text inputs based on the questions state
{questions.map((question, index) => (
<Form.Group key={index}>
<Form.Label htmlFor={"question" + (index + 1)}>
Question {index + 1}.
</Form.Label>
<Form.Control
id={"question" + (index + 1)}
type="text"
className="form-control questionName"
placeholder="Question..."
required={true}
name="name"
onChange={(e) => {
}}
value={questions[index].question}
/>
<br />
</Form.Group>
))}
The inputs all load as expected, with the input values showing the correct data (question.question)
However, I am struggling to find a way to update the value of the text input as the user types. initially i added this to the onChange event =>
let x = questions
questions[index].question = e.target.value
setQuestions(x)
the idea being to make a copy of the questions state, update the specific question then re-set the questions state to the new updated copy. This does not seem to work and the input value does not change :/
Would love to know what i'm missing. Any help would be appreciated
edit*** Here is my entire jsx file
import React, { useContext, useState, useEffect, useCallback } from "react";
import { useParams, useNavigate } from "react-router-dom";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import AuthContext from "../../../context/AuthContext";
import LoadingSpinner from "../../../components/LoadingComponents/LoadingSpinner";
const EditQuestionnaire = () => {
// context
let { accessToken } = useContext(AuthContext);
// params
let { questionnaireUUID } = useParams();
//states
let [questionnaire, setQuestionnaire] = useState({ uuid: "", name: "" });
let [sections, setSections] = useState([]);
let [currentPage, setCurrentPage] = useState(0);
let [loading, setLoading] = useState(true);
// get questionnaire data api call
let getQuestionnaire = useCallback(async () => {
let response = await fetch(
`${
import.meta.env.VITE_APP_API_DOMAIN
}/api/questionnaire/${questionnaireUUID}/`,
{
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);
let data = await response.json();
if (response.status === 200) {
setQuestionnaire({ uuid: data.uuid, name: data.name });
setSections(data.sections);
console.log(data);
}
});
useEffect(() => {
if (loading) {
getQuestionnaire();
setLoading(false);
}
}, [getQuestionnaire, loading]);
return loading ? (
<LoadingSpinner />
) : currentPage === 0 ? (
<div className="create-survey-wrapper">
<div className="content-section survey-create">
<Form>
<Form.Group>
<legend className="border-bottom mb-4">Edit Questionnaire</legend>
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>Questionnaire Name*</Form.Label>
<Form.Control
type="text"
placeholder="Questionnaire Name"
required={true}
name="name"
onChange={(e) => {
setQuestionnaire({ ...questionnaire, name: e.target.value });
}}
value={questionnaire.name}
/>
</Form.Group>
<Button
variant="outline-primary"
onClick={() => {
setCurrentPage(currentPage + 1);
}}
>
Next
</Button>
</Form>
</div>
</div>
) : (
<div className="create-survey-wrapper">
<div className="content-section survey-create">
<Form>
<Form.Group>
<legend className="border-bottom mb-4">Edit Questionnaire</legend>
</Form.Group>
<Form.Group>
<legend className="border-bottom mb-4">
Section {currentPage}
</legend>
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicSectionName">
<Form.Label>Section Name*</Form.Label>
<Form.Control
type="text"
placeholder="Section Name"
required={true}
name="name"
onChange={(e) => {}}
value={sections[currentPage - 1].name}
/>
</Form.Group>
<br />
<hr />
<Form.Group>
<legend className="border-bottom mb-4">Questions</legend>
</Form.Group>
{sections[currentPage - 1].questions.map((question, index) => (
<Form.Group key={index}>
<Form.Label htmlFor={"question" + (index + 1)}>
Question {index + 1}.
</Form.Label>
<Form.Control
id={"question" + (index + 1)}
type="text"
className="form-control questionName"
placeholder="Question..."
required={true}
name="name"
onChange={(e) => {
let x = sections;
x[currentPage - 1].questions[index].question = e.target.value;
console.log(x);
setSections(x);
}}
value={sections[currentPage - 1].questions[index].question}
/>
<br />
</Form.Group>
))}
<Button
variant="outline-primary"
onClick={() => {
setCurrentPage(currentPage - 1);
}}
>
Prev
</Button>{" "}
{currentPage < sections.length ? (
<Button
variant="outline-primary"
onClick={() => {
setCurrentPage(currentPage + 1);
}}
>
Next
</Button>
) : (
<Button variant="outline-primary">Save</Button>
)}
</Form>
</div>
</div>
);
};
export default EditQuestionnaire;
If you have an array of question, try this way
const handleQuestionChange = (index, value) => {
const updatedSections = [...sections]; // create a copy of section first
const updatedQuestions = [...updatedSections[currentPage - 1].questions]; // copy all the questions as well from that section
updatedQuestions[index].question = value; // updating the value of that question on the given index
updatedSections[currentPage - 1].questions = updatedQuestions; updating that current section
setSections(updatedSections);
};
<Form.Control
id={"question" + (index + 1)}
type="text"
className="form-control questionName"
placeholder="Question..."
required={true}
name="name"
onChange={(e) => handleQuestionChange(index, e.target.value)}
value={sections[currentPage - 1].questions[index].question}
/>
Let me know if this help. I have same method implemented in one of my project where I'm using an array of questions. Although yours is different implementation, I tried using the same idea I used in my project.