2

I have a react-redux application, and I have a reducer named dataReducer that has a default state like this:

const defaultState = {
  isLoading: false,
  data: [{
    id: 1,
    label: 'abc',
    elements: [{ color: 'red', id: 1}],
  }],
};

One of the reducers adds elements to data in the defaultState. I need to test this reducer by passing the payload and then validating the new state. I want to use the spread operator to build the new state from the old defaultState, but I am having some trouble achieving it. I tried the following, but it's not working:

const newElement = {
  colour: 'blue',
  id: 1
};

const newState = [
  {
    ...defaultState.data[0],
    elements: [{
      ...defaultState.data[0].elements,
      ...newElement,
    }]
  }
];

expect(dataReducer(defaultState, action)).toEqual(newState); // returns false

It would be great if I could somehow avoid using array index (defaultState.data[0]) as there might be multiple objects in the defaultState array in the real application, though for the purpose of testing, I am keeping just one object to keep things simple.

3
  • 1
    The new entry goes in state.data[0].elements? Not state.data? Commented Mar 23, 2020 at 12:52
  • newElement has the same structure than state.data[0].elements elements, so it seems to belong to it. Do you want to mix state.data elements with different model? Commented Mar 23, 2020 at 12:57
  • 1
    @T.J.Crowder The new entry goes in elements of that object in the data array that has the same id as that object. So the id of the object and the newElement should match. Commented Mar 23, 2020 at 13:04

1 Answer 1

1

If you're adding to the end, you spread out the other aspects of state in the new state object, then override data with the current contents of it followed by the new entry:

const newState = {               // New state is an object, not an aray
  ...defaultState,               // Get everything from defaultState
  data: [                        // Replace `data` array with a new array
    {
      ...defaultState.data[0],   // with the first item's contents
      elements: [                // updating its `elements` array
        ...defaultState.data[0].elements,
        newElement
      ]
    },
    ...defaultState.data.slice(1) // include any after the first (none in your example)
  ]
};

Live Example:

const defaultState = {
  isLoading: false,
  data: [{
    id: 1,
    label: 'abc',
    elements: [{ color: 'red', id: 1}],
  }],
};

const newElement = {
  colour: 'blue',
  id: 1
};

const newState = {               // New state is an object, not an aray
  ...defaultState,               // Get everything from defaultState
  data: [                        // Replace `data` array with a new array
    {
      ...defaultState.data[0],   // with the first item's contents
      elements: [                // updating its `elements` array
        ...defaultState.data[0].elements,
        newElement
      ]
    },
    ...defaultState.data.slice(1) // include any after the first (none in your example)
  ]
};

console.log(newState);
.as-console-wrapper {
    max-height: 100% !important;
}

There's no getting around specifying the entry in data that you want (e.g., data[0]).


In a comment you've asked how to handle this:

Let's say data (present inside defaultState) has multiple objects entries in it. First object has id of 1, second one has id of 2. Now the newElement to be added has an id of 2. So the newElement should get added to the second object. Where in second object? Inside the elements property of the second object. The addition should not over-write existing entries in the elements array.

You'll need to find the index of the entry in data:

const index = defaultState.data.findIndex(({id}) => id === newElement.id);

I'm going to assume you know that will always find something (so it won't return -1). To then apply that index to the code above, you'd do this:

const newState = {                        // New state is an object, not an aray
  ...defaultState,                        // Get everything from defaultState
  data: [                                 // Replace `data` array with a new array
    ...defaultState.data.slice(0, index), // Include all entries prior to the one we're modifying
    {
      ...defaultState.data[index],        // Include the entry we're modifying...
      elements: [                         // ...updating its `elements` array
        ...defaultState.data[index].elements,
        newElement
      ]
    },
    ...defaultState.data.slice(index + 1) // include any after the one we're updating
  ]
};

The only real change there is adding the ...defaultState.data.slice(0, index) at the beginning of the new data, and using index instead of 0.

Live Example:

const defaultState = {
  isLoading: false,
  data: [
      {
        id: 1,
        label: 'abc',
        elements: [{ color: 'red', id: 1}],
      },
      {
        id: 2,
        label: 'def',
        elements: [{ color: 'green', id: 2}],
      },
      {
        id: 3,
        label: 'ghi',
        elements: [{ color: 'yellow', id: 3}],
      }
  ],
};

const newElement = {
  colour: 'blue',
  id: 2
};

const index = defaultState.data.findIndex(({id}) => id === newElement.id);

const newState = {                        // New state is an object, not an aray
  ...defaultState,                        // Get everything from defaultState
  data: [                                 // Replace `data` array with a new array
    ...defaultState.data.slice(0, index), // Include all entries prior to the one we're modifying
    {
      ...defaultState.data[index],        // Include the entry we're modifying...
      elements: [                         // ...updating its `elements` array
        ...defaultState.data[index].elements,
        newElement
      ]
    },
    ...defaultState.data.slice(index + 1) // include any after the one we're updating
  ]
};

console.log(newState);
.as-console-wrapper {
    max-height: 100% !important;
}

Sign up to request clarification or add additional context in comments.

6 Comments

If I have multiple objects in the data array, is there no way to 'spread' all the existing object entries in data before adding the last object entry at the end?
@maverick - Yes, but I thought you wanted to update the elements property of the first object in data?
The newElement to be added will actually get added in the elements array if the id of the newElement matches the id of any of the objects in data array.
@maverick - I'm afraid I can't figure out what you're saying there. Do you mean it'll get added to the first entry in data that has any entry in elements with a matching id?
@maverick - I've added to the answer.
|

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.