Home > Software engineering >  How to map over useState array of objects and edit the needed object?
How to map over useState array of objects and edit the needed object?

Time:01-02

const [Questions, setQuestions] = React.useState(props.QuestionsData);
  const handleClick = (id, isCorrect) => {
    if (isCorrect) {
      setQuestions((prev) => {
        prev.map((item, index) => {
          if (index === id) {
            return {
              ...item,
              [item.Answers.correct.selected]: !item.Answers.correct.selected,
            };
          } else {
            return item;
          }
        });
      });
    } else {
      return;
    }
  };

This is the code im doing and what I want to do it loop over the Questions (which has a list of objects that look like this.

{
  "category": "Entertainment: Video Games",
  "type": "boolean",
  "difficulty": "hard",
  "question": "The first "Metal Gear" game was released for the PlayStation 1.",
  "correct_answer": "False",
  "incorrect_answers": [
      "True"
  ],
  "Answers": {
      "correct": {
          "MasterId": "MXTOfKnKw7dU7QP0UP0td",
          "id": 0,
          "answer": "False",
          "selected": false,
          "correct": true,
          "userChosen": true
      },
      "wrong": {
          "id": 1,
          "answer": "True",
          "selected": false
      }
  }
}

I tried to do everything but it returns undefined. I even tried to do the map alone with the value and logged out the needed value and it worked.

If someone can tell me a good way to loop over usestate array of objects and only edit the objects that satisfy the condition that would be great.

CodePudding user response:

Remove the curly braces so that the value is actually returned.

setQuestions((prev) =>
    prev.map((item, index) => {
        if (index === id) {
            return {
                ...item,
                [item.Answers.correct.selected]: !item.Answers.correct.selected,
            };
        } else {
            return item;
        }
    });
);

CodePudding user response:

  1. I don't recommend using the capitalization convention for variable names. Use camelCase and save those for Components, Classes, and Types.
  2. For the sake of readability and clean code, I recommend you write data processing code outside of setSomeState()

Here's an example.

import { useState } from "react";
...

const [questionList, setQuestionList] = useState(props.questionsData);

const handleClick = (id, isCorrect) => {
  if (isCorrect) {
    const updatedList = questionList.map((item, index) => {
      if (index === id) {
        return {
          ...item,
          [item.answers.correct.selected]: !item.answers.correct.selected,
        };
      } else {
        return item;
      }
    });
    // no messy prev state stuff, it's now clean and easy to maintain
    setQuestionList(updatedList);
  } else {
    return;
  }
};

Further, I see that you use props.questionData as a default value of the setState. It's not a good approach since one component's state's default value depends on the other component's value and can potentially cause errors.

I personally recommend you to keep the questions default state to an empty array, and use useEffect and do setQuestions inside the useEffect with props.questionData.

CodePudding user response:

Can you imagine writing this handler every time you have array-based stated? Also map doesn't make sense because we only want to update one item, however map iterates over the entire array.

Write a generic function to update an arr at a specific index using a user-supplied func -

function update(arr, index, func) {
  return [
    ...arr.slice(0, index),
    func(arr[index]),
    ...arr.slice(index   1)
  ]
}

Now in your component -

function MyComponent({ questions = [] }) {

  const [questions, setQuestions] = useState(questions)

  const updateQuestion = index => event =>
    setQuestions(arr => update(arr, index, q => ({
       /* update q here */
    })))

  return questions.map((q, index) =>
    <Question key={index} question={q} onClick={updateQuestion(index)} />
  )
}

Developing your own utility functions to operate on arrays and objects can be a fun exercise, but maybe challenging to get right. Look to popular libraries like Immutable.js and Immer for inspiration.

See these Q&A for live examples you can run in your browser -

CodePudding user response:

You forgot to return the result of the map. If a function doesn't return anything in JS, undefined will be returned. In addition, the computed property isn't used correctly here:

{
   ...item,
   [item.Answers.correct.selected]: !item.Answers.correct.selected,
}

Try this:

const handleClick = (id, isCorrect) => {
    if (isCorrect) {
      setQuestions((prev) =>
        prev.map((item, index) => {
           if (index === id) {
              item.Answers.correct.selected = !item.Answers.correct.selected
           }
           return item
        }));
    }
  };
  • Related