This simple counter button does work as expected. Initially set to zero, its label is incremented each time I click on it:
function Counter() {
const INCREMENT = 'INC';
const [state, dispatch] = React.useReducer(reducer, { counter: 0 });
function reducer(state, action) {
switch (action.type) {
case INCREMENT:
return { counter: state.counter + 1 };
default:
throw new Error("Unknown action!");
}
}
return <button onClick={() => dispatch({ type: INCREMENT })}>{state.counter}</button>;
}
However, if I use Symbol for the INCREMENT constant, I get "Unknown action!" each time I click on it:
function Counter() {
const INCREMENT = Symbol();
const [state, dispatch] = React.useReducer(reducer, { counter: 0 });
function reducer(state, action) {
switch (action.type) {
case INCREMENT:
return { counter: state.counter + 1 };
default:
throw new Error("Unknown action!");
}
}
return <button onClick={() => dispatch({ type: INCREMENT })}>{state.counter}</button>;
}
Can you explain this behaviour?
2
Answers
You’re comparing two different Symbols. The
INCREMENT
used within yourreducer
is theINCREMENT
symbol created for the current render (as you’re redefiningreducer()
within your component, which is not typically done), whereas theaction.type
within your reducer is the symbol created for the previous render. That’s because when you calldispatch(action)
:INCREMENT
symbol. As you can’t create the same Symbol twice withSymbol()
, this is unique.function reducer()
is redeclared, allowing it to access the new unique symbol just created.const [state, dispatch] = React.useReducer(reducer, { counter: 0 });
executes, triggering thereducer
function to execute withaction
as an argument. Here action is your object from the previous render that has the oldINCREMENT
symbol.As your new symbol
INCREMENT
is different from the old oneaction.type
, they’ll have different reference identities when compared in theswitch
.Instead, you can create the
Symbol
outside of your component so that the symbol remains consistent across rerenders:Alternatively, you can use
Symbol.for("INCREMENT")
which creates a "shared" symbol, but as Bergi points out, this doesn’t have many benefits over just using a string:The problem is that
const INCREMENT = Symbol();
creates a new symbol on every render of yourCounter
component. Define the reducer and the action type symbols outside of the component: