7

I'm trying to recreate a Memory-like game with React. I'm using Redux Toolkit for state management, but I'm having trouble with one use case.

In the selectCard action, I want to add the selected card to the store, and check if there's already 2 of them selected. If so, I want to empty the selected array after a delay.

const initialState : MemoryState = {
    cards: [],
    selected: [],
}

const memorySlice = createSlice({
    name: 'memory',
    initialState: initialState,
    reducers: {        
        selectCard(state: MemoryState, action: PayloadAction<number>) {
            state.selected.push(action.payload);
            if (state.selected.length === 2) {
                setTimeout(() => {
                    state.selected = [];
                }, 1000);
            }
        }
    }
});

The cards get selected just fine, but when I select 2 I get this error after 1 sec:

TypeError: Cannot perform 'set' on a proxy that has been revoked, on the line state.selected = [];

I'm new to this stuff, how do I access the state after a delay? Do I have to do it asynchronously? If so, how?

Merig
  • 1,751
  • 2
  • 13
  • 18

2 Answers2

6

As stated in their documentation, don't perform side effects inside a reducer.

I would add the setTimeout when dispatching the action instead:

// so the reducer:
...
if (state.selected.length === 2) {
  state.selected = [];
}
...

// and when dispatching
setTimeout(() => {
  dispatch(selectCard(1))
}, 1000)
alextrastero
  • 3,703
  • 2
  • 21
  • 31
  • This would just delay both the selection of the card and emptying the array by 1sec: what i'm looking for is to select immediately, then have the array emptied after 1sec if the condition is true – Merig Oct 19 '20 at 13:49
  • 1
    Then dispatch two actions. It is a core rule of redux that a reducer must never have any side effects. – phry Oct 19 '20 at 20:28
0

I ran into this issue too. I solved it by using the 'side effect' code into a function and then used the result of it in the reducer

const initialState : MemoryState = {
  cards: [],
  selected: [],
}

const memorySlice = createSlice({
  name: 'memory',
  initialState: initialState,
  reducers: {        
    selectCard(state: MemoryState, action: PayloadAction<number>) {
        state.selected = action.payload
    }
  }
});

export const { selectCard } = memorySlice.actions

export const sideEffectFunc = (param) => (dispatch) => {
  let selected = []
  selected.push(action.payload);
  if (selected.length === 2) {
     setTimeout(() => {
        selected = [];
     }, 1000);
  }
  dispatch(selectCard(selected));
};

Don't pay attention to the logic of the function (haven't tested it and it might be wrong) but I wanted to show the way we could handle 'side effect' code when using redux toolkit

gustavozapata
  • 429
  • 2
  • 7
  • 13