0

I can't quite wrap my head around the boilerplate of . I looked up common patterns for immutable modifying of state but issue is, all these patterns simply push towards the end and not for a specific index.

Before I'll go into actual code, here's what the structure of the state looks like for better imagination (pseudo-code):

state = {
   quizMenu: {...},
   quizEditor: Array<Question>,

    > type Question = {
        id: number,
        question: string,
        questionOptions: Array<QuestionOption>,
      }

        > type QuestionOption = {
           id: number,
           optionText: string,
           isValid: boolean,
          }
}

enter image description here

Hopefully it makes sense. I have created an action for adding questions, which works fine. Now I'm trying to create an action for adding option to an already existing question, but I can't wrap my head around how to in the nested arrays of objects.

Here's how my action in question is defined:

const AQO = 'ADD_QUESTION_OPTION';

/*
 * @param questionId - ID of the question we're accesssing in quizEditor array
 * @param id - id of the option we're adding (handled in component)
*/
const actionAddQuestionOption = createAction(
  AQO, 
  (questionId: number, id: number) => ({ 
    payload: {
      id,
      optionText: 'New option',
      isValid: false,
      questionId,
    },
  })
);

Now my reducer is the following way:

const reducer = createReducer({//...}, {
   [actionAddQuestionOption.type]: (state, action) => ({
     ...state,
     quizEditor: [...state.quizEditor][action.payload.questionId].questionOptions.push({
        id: action.payload.id,
        optionText: action.payload.optionText,
        isValid: action.payload.isValid,
     })
   })
}

This just ends up in this monster type-error: https://pastebin.com/raw/pBbnxcQp

But I'm pretty sure I'm accessing the Array inside the array of objects incorrectly.

quizEditor: [...state.quizEditor][action.payload.questionId].questionOptions

Does anyone know what would be the proper way of going about accessing it? Much appreciated!

2 Answers 2

1

Since you are using redux-toolkit which has immer built in you can just mutate the state directly and it will transform it into an immutable update internally

const reducer = createReducer({
  [actionAddQuestionOption.type]: (state, { payload: { questionId, ...option }}) => {
    const question = state.questionquizEditor(question => question.id === questionId)
    question.questionOptions.push(option)
  }
})

The way to make it an immuable update is like this

const reducer = createReducer({
  [actionAddQuestionOption.type]: (state, { payload: { questionId, ...option } }) => ({
    ...state,
    quizEditor: state.quizEditor.map(question =>
      (question.id === questionId
        ? {
          ...question,
          questionOptions: [...question.questionOptions, option],
        }
        : question)),
  }),
})
Sign up to request clarification or add additional context in comments.

1 Comment

Hey everyone, I've solved the question thanks to AsafAviv's help, either way, just leaving this here as a working example in case anyone might find it useful: codesandbox.io/s/…
1

the push method of Array returns the new length of the array not the array itself. What you can do is just concat the new object to the array which in turn will return the new array with the new question option.

[...state.quizEditor][action.payload.questionId].questionOptions.concat({
        id: action.payload.id,
        optionText: action.payload.optionText,
        isValid: action.payload.isValid,
     })

Furthermore, we have to modify only that property in the state with our new array:

const reducer = createReducer({
  //...}, {
  [actionAddQuestionOption.type]: (state, action) => {
    const quizEditor = [...state.quizEditor];
    quizEditor[action.payload.questionId].questionOptions = quizEditor[
      action.payload.questionId
    ].questionOptions.concat({
      id: action.payload.id,
      optionText: action.payload.optionText,
      isValid: action.payload.isValid
    });

    return {
      ...state,
      quizEditor
    };
  }
});

Thanks to immer in redux toolkit we can make it more readable:

const reducer = createReducer({
  //...}, {
  [actionAddQuestionOption.type]: (state, action) => {
    const question = state.quizEditor[action.payload.questionId];
    question.questionOptions = [
      ...question.questionOptions,
      {
        id: action.payload.id,
        optionText: action.payload.optionText,
        isValid: action.payload.isValid
      }
    ];

    return state;
  }
});

4 Comments

Yup, that's my bad for overlooking. Was actually half way through editing my question. Either way, sadly even with your solution i still get a similar type-error as mentioned in my original question
Yes, I now know the problem! Give me a sec rewriting the solution :)
This should return the modified state. Instead of only state I'm guessing easiest way would be to splice the array until optionId and spread ...until splice, new item, ...after splice?
It is returning the modified state. Thanks to Immer we can do this and will get an immutable result regardless. Check out their docs for more info.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.