skip to Main Content

I’ve been banging my head against this, hopefully getting another pair of eyes on it will help.

I’m using Redux + Redux Toolkit in a React Native App, in a pretty simple way. I can tell (through a log statement) that my action is being called and the state is getting set, but my useSelector on the state never updates. I’ve tried it with shallowEqual as well, but that shouldn’t be needed, since Redux Toolkit uses Immer and the object shouldn’t pass an equality check after updating (most of the other similar issues I researched were due to that)

Here’s my main slice, followed by all the related code. Pardon the code dump, but I want to give a full picture:


export interface Metadata {
    title: string
    author: string
    firstLines: string
    id: string
}

type MetadataState = Record<string, Metadata>

export const metadataSlice = createSlice({
    name: "metadata",
    initialState: {} as MetadataState,
    reducers: {
        setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
            state = action.payload
            console.log("new metadata: ", state)
        },
        addMetadata: (state: MetadataState, action: PayloadAction<Metadata>) => {
            state[action.payload.id] = action.payload
        }
    }
});

I have an async action to load the metadata from AsyncStorage (like LocalStorage on mobile), as follows:

export function loadMetadata() {
    return async (dispatch: AppDispatch, getState: () => RootState) => {
        const maybeMetadata = await AsyncStorage.getItem("metadata");
        if(maybeMetadata) {
            dispatch(metadataSlice.actions.setMetadata(JSON.parse(maybeMetadata)))
            return true
        } else {
            return false
        }
    }
}

And I dispatch that in my main component as follows:

  const dispatch = useAppDispatch()

  useEffect(() => {
    dispatch(loadMetadata())
  }, [])

In another component, I’m trying to access the state simply by doing:

const metadata = useAppSelector(state => state.metadata)

Any idea what’s going on? The state just never seems to update, even though I see my action being called and update the state within it. Is it not being dispatched correctly? I tried directly accessing the state with store.getState() and the state seems empty, is it somehow just not being set?

I’m honestly pretty lost, any help is appreciated.

2

Answers


  1. Chosen as BEST ANSWER

    The issue had to do with how Immer (which Redux Toolkit leverages for allowing mutable operations) works.

    setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
      state = action.payload
      console.log("new metadata: ", state)
    }
    

    Instead of mutating state, I reassigned it, which messed up the way Immer keep track of draft states. The console.log statement returned the new state, but it didn't work with Immer. Instead, I needed to do this:

    setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
      // simply return the new state, since I'm changing the whole state
      return action.payload
    }
    

    And it works fine now. I'm kind of surprised I didn't see this documented (it may be somewhere) or get some sort of warning, but good to know for the future!


  2. An addition to Nathan‘s answer, to avoid linters flooding your code and for proper readability, instead of:

    setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
      return action.payload
    }
    

    Do it like this:

    setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
      return {...state, ...action.payload}
    }
    

    By so doing, first parameter of the action state, is put to use as it should

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search