reactjsreact-state

How to update input value by state in react


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;

Solution

  • 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.