illumiMatii
illumiMatii

Reputation: 45

setState for multiple values in objects list

I have problem and I don't know how to fix it. So i have component in which I've declared an array of objects. I want to set its state separately but I don't want to declare multiple useStates.

I have an array of objects which look like this:

const [card, setCard] = useState({
    name: "",
    questions: [
      {
        question: "",
        answer: "",
      },
      {
        question: "",
        answer: "",
      },
      {
        question: "",
        answer: "",
      },
      {
        question: "",
        answer: "",
      },
      {
        question: "",
        answer: "",
      },
      {
        question: "",
        answer: "",
      },
      {
        question: "",
        answer: "",
      },
      {
        question: "",
        answer: "",
      },
      {
        question: "",
        answer: "",
      },
    ],
  });

and here's component:

const NewCard = () => {
  const handleNameChange = (event) => {
    setCard({ name: event.target.value, ...questions });
  };
  return (
    <div className="newcard-container">
      <div className="card-container">
        <h3>Podaj nazwe fiszki</h3>
        <input type="text" value={card.name} />
      </div>
      <div className="questions-container">
        {card.questions.map((q) => {
          return (
            <div className="question">
              <h4>Podaj pytanie </h4>
              <input type="text" value={q.question} />
              <h4>Podaj odpowiedź</h4>
              <input type="text" value={q.answer} />
            </div>
          );
        })}
        <button>Dodaj pytanie</button>
      </div>
    </div>
  );
};

I've tried to figure out how to change the setState to get that approach but I didn't made it. Any ideas how can I get that?

Upvotes: 2

Views: 1388

Answers (3)

AbsoluteZero
AbsoluteZero

Reputation: 401

Again, not sure if this is what you needed so let me know.

import React, { useState, useCallback } from 'react';

export function App() {
  const [card, setCard] = useState({
    name: "",
    questions: [
      {
        id: 'question-1',
        question: "Question 1",
        answer: "",
      },
      {
        id: 'question-2',
        question: "Question 2",
        answer: "",
      },
      {
        id: 'question-3',
        question: "Question 3",
        answer: "",
      },
    ]
  });

  const handleCardNameChange = useCallback((ev) => {
    setCard((c) => ({ ...c, name: ev.target.value }))
  }, [setCard]);

  const handleAnswerChange = useCallback((cardId, value) => {
    const updatedQuestions = card.questions.map((c) => {
       
      if (c.id !== cardId) {
        return c;
      }

      return {
        ...c,
        answer: value,
      }
    });

    setCard({
      ...card,
      questions: updatedQuestions,
    })
  }, [card, setCard]);

  return (
    <div>
      <input placeholder="Card Title" value={card.name} onChange={handleCardNameChange} />
      {card.questions.map((c) => (
        <div key={c.id}>
          <p>Q: {c.question}</p>
          <input placeholder="Answer" value={c.answer} onChange={(ev) => handleAnswerChange(c.id, ev.target.value)} />
        </div>
      ))}
    </div>
  );
}

This handles answer change per question and card title change separately. I wrote this in a some weird editor online so it might not be perfect but it should work.

Upvotes: 2

You have a few approaches to do this.


    const [ card, setCard ] = useState( {
      name: "",
      questions: {
        1: {
          statement: "",
          answer: "",
        },
        2: {
          statement: "",
          answer: "",
        },
        //...
      }
    } );

    // To set an especifique answer or question, you can set the state like this:
    setCard( prev => ( {
      ...prev,
      questions: {
        ...prev.questions,
        1: {
          ...prev.questions[ 1 ],
          answer: "New answer"
        }
      }
    } ) );

    // To add a new question, you can set the state like this:
    setCard( prev => ( {
      ...prev,
      questions: {
        ...prev.questions,
        [ Object.keys( prev.questions ).length + 1 ]: {
          statement: "",
          answer: "",
        }
      }
    } ) );

    // To remove a question, you can set the state like this:
    setCard( prev => {
      const questions = { ...prev.questions };
      delete questions[ 1 ];
      return {
        ...prev,
        questions
      };
    } );

But if you wanna use with array, you can do like this:


    // Solution with array
    const [card, setCard] = useState({
      name: "",
      questions: [
        {
          question: "",
          answer: "",
        },
        {
          question: "",
          answer: "",
        },
        //...
      ],
    } );

    // To set an especifique answer or question, you will need the index of the question, or null to set a new question.

    const setCardQuestions = ( index, question, answer ) => {
      setCard( ( prev ) => {
        const questions = [...prev.questions];
        if ( index === null ) {
          questions.push( {
            question,
            answer,
          } );
        } else {
          questions[ index ] = {
            question,
            answer,
          };
        }
        return {
          ...prev,
          questions,
        };
      });
    };

    // To remove a question, you will need the index of the question.
    const removeCardQuestion = ( index ) => {
      setCard( ( prev ) => {
        const questions = [...prev.questions];
        questions.splice( index, 1 );
        return {
          ...prev,
          questions,
        };
      });
    }

Upvotes: 0

Tarmah
Tarmah

Reputation: 159

it should be

setCard((card) => { ...card , name: event.target.value });

Upvotes: 1

Related Questions