Tim Seip
Tim Seip

Reputation: 31

Redux Thunk, Modifying state: Unhandled promise rejection: Error: [Immer] Immer only supports setting array indices and the 'length' property]

While working on a React Native application with a persistent Redux store (in TypeScript) I ran into the following problem:

I built a Thunk that queries an API, this API sends back a set of question data in a fetch. This fetch works fine, but when trying to save any data to the Redux Store using extraReducers I get errors.

My code looks like this:

First I call a dispatch on the Thunk in my page:

questionpage.tsx

-stuff-

    useEffect(() => {
        console.log('dispatching fetch')
        dispatch(fetchQuestions())

    }, []);

-render stuff-

The actual thunk looks like this and does a fetch to my API:

export const fetchQuestions = createAsyncThunk('questions/fetchquestions', async () =>{
    let headers = new Headers();
    headers.append('Authorization', 'Basic ' + Base64.btoa(email + ":" + password))
    
    const response = await fetch(api + "/api/questions", {
            headers: headers,
            method: 'GET'
        })
    const stuff = await response.json();
    console.log(stuff)
    return stuff;

})

Then:

questionSlice

    extraReducers: (builder) => {
        builder.addCase(fetchQuestions.pending, (state, action) => {
          state.status = 'loading' // < when I comment this the console.log works
          console.log('pending')
        }),
        builder.addCase(fetchQuestions.fulfilled, (state, action) => {
          state.status = 'succeeded'
          // Add any fetched posts to the array
          state.questions = state.questions.concat(action.payload) // < comment this, console.log works
          console.log('fulfilled')
        })
        builder.addCase(fetchQuestions.rejected, (state, action) => {
          state.status = 'failed' // < when I comment this the console.log works
          state.error = action.error.message // < when I comment this the console.log works
          console.log('rejected')
        })
      } 

As you see above: In my reducer I use the extraReducers to catch the different results this Thunk can produce. This works, but only when I modify nothing in the state. If I try to save anything to the state I get the following error:

[Unhandled promise rejection: Error: [Immer] Immer only supports setting array indices and the 'length' property]

This error occurs at all times, it occurs when I modify state.status, which is a String, not an Array, it also occurs when I modify state.error and state.questions The last one is an array, but the other two are not.

I looked at a lot of other questions but couldn't find this specific scenario anywhere and am fairly lost on how to proceed.

Update: My problem stems from Immer. If I understand the documentation correctly Immer should allow me to do updates of objects in the Redux state, but I cannot get it to work:


export const questionSlice = createSlice({
    name: 'question',
    initialState,
    reducers: { 
        save: (state, action) => {
            state.questions.push(action.payload) < this action works
        }
    },
    extraReducers: (builder) => {
        builder.addCase(fetchQuestions.fulfilled, (state, action) => {
          state.questions.push(action.payload) < this action errors
        })

Edit 2:

Pointed towards this direction and posting my state here. The app only has one current Slice, the QuestionSlice from earlier, with this state:

interface questionState {
    questions: object[],
    status: 'idle' | 'loading' | 'succeeded' | 'failed',
    error: string | undefined
}

const initialState: questionState = {
    questions : [],
    status: 'idle',
    error: undefined
}

This goes into the RootReducer

const rootReducer = combineReducers({questions: questionReducer})

And then this RootReducer is used to build the store:

const persistConfig = {
    key: 'root',
    storage: AsyncStorage
}

const persistedReducer = persistReducer(persistConfig, rootReducer);


const mystore = createStore(
    persistedReducer,
    applyMiddleware(thunk)
);```

The comment Phry made pointed towards my state being an array of itself, but that doesn't seem to be the case to me. There's also no code I can find that modifies the state to be an array. I'll try and see if I can make a basic version of the project later that others could look at, but it might be a lot of work with my level of experience.

Upvotes: 1

Views: 1866

Answers (2)

Tim Seip
Tim Seip

Reputation: 31

My fix was as follows:

Even though I got an Immer error and I should be able to do state modification inside the Reducer, if I understand the documentation correctly: this specific part of the documentation, it just didn't work.

I take it there is something with the extraReducers that gives me that Immer error. I got my code to work by changing it to be immutable updates.

builder.addCase(fetchQuestions.fulfilled, (state, action) => {
            // Add any fetched posts to the array
            console.log('fulfilled')
            
            return state = {
                ...state,
                status: 'succeeded',
                questions: action.payload
            }
        })

Upvotes: 2

HenriDev
HenriDev

Reputation: 697

in the reducers you have to copy your state as it is immutable you can't just change a property on it.

read more here

Upvotes: 0

Related Questions