In general, using a mutable object such as Map is strongly discouraged.
However, the magic of immer allows immutable objects to be manipulated as though they are mutable.
Specifically, immer supports immutable versions of Map using enableMapSet
In redux-toolkit createReducer and createSlice wrap state manipulation with immer's produce.
Collectively, I think these facts mean code like this should be safe:
import { createSlice } from '#reduxjs/toolkit'
export const testmapSlice = createSlice({
name: 'testMap',
// Using a Map() as redux state
initialState: new Map(),
reducers: {
add: (state, action) => {
state.set(action.payload.identity, action.payload)
},
},
})
However, when I use this in a React component, I get the polite error message A non-serializable value was detected in the state, in the path: `testMap`. Value: Map(1) {"A" => {…}} Take a look at the reducer(s) handling this action type: testMap/add..
Is there a way to safely use Map without getting this error message?
Define "safely" :)
In theory, you could put anything you want into the Redux store.
In practice, per that FAQ, non-serializable values are likely to cause things like the DevTools to break (which defeats much of the purpose of using Redux in the first place). Use of Maps and other mutable instances are also likely to cause portions of your UI to not re-render correctly, because React-Redux relies on reference checks to determine if data has changed. So, we specifically tell users that you should never put non-serializable values in the Redux state.
In this particular case, you should be able to use a plain JS object as a lookup table rather than a Map, and accomplish the same behavior.
As an absolute last resort, you can turn off the serialization checking for certain parts of the state, but we strongly discourage folks from doing that.
I think it is not safe to use Map() on the state because Redux is already designed to avoid the mutation at the level of Reducers, and when you use createSlice() it even takes care of it in the background. Your idea of a double security at the State level may appear to be another issue that you provoke. It might either provoke the UI not to update. or throw an error
(Ps: This is purely analogic. I have not tried it)
Related
I have a side effect that detects the browser language and dispatches a browserLanguageSupported action if it is a language that my application can handle.
Now I have following reducer function that only updates the states preferredLanguage property in case it is not defined already. This is important because there are other actions that update this state property and I do not want a late browserLanguageSupported action to overwrite such a state update.
export interface State {
preferredLanguage: AppLanguage | undefined;
rehydrationComplete: boolean;
}
export const initialState: State = {
preferredLanguage: undefined,
rehydrationComplete: false
};
export const reducer = createReducer(
initialState,
on(LanguageActions.browserLanguageSupported, (state, {browserLanguage}) => {
if (!state.preferredLanguage) {
return {...state, preferredLanguage: browserLanguage};
}
return state;
})
);
Now for my question: Is it good practice to have such a condition in a reducer operator? The function itself is still pure. But I am not sure if it is good design or if I should solve it differently, lets say by adding state slice selection in the side effect that dispatches this action.
Btw. the reason I am not setting it directly in the initial state is because I get the browser language from an angular service and I am not sure if it is even possible to set initial feature state from service injection?
Best regards,
Pascal
I would to this the same way, so you get a 👍 from me.
Adding a slice of the state into the effect just adds needless complexity.
The reducer contains the state, and it's OK to add logic to see if state needs to be updated or not.
Also, let's say you need to add this logic into another action/effect.
Having it in the reducer makes it easier to reuse if it's needed. Otherwise you end up with duplicate logic.
As long as the rejection (or mutation) of the data is irrelevant to the chain of actions & effects, this is absolutely valid.
However, it's worth noting that if the action in question triggers an effect which triggers an action, the triggered action will not know whether the data was rejected (or mutated) without checking the state—which is exactly what this pattern is attempting to avoid.
So, if you wanted to be able react to that rejection (or mutation), you would want to handle this in the effect. But, if you would proceed in exactly the same manner regardless of the result, then it belongs reducer.
I'm working with Redux and my state is a normalized one with a lot of different models. Now I was wondering myself if it was better to have specific actions like:
{type: CHANGE_MODEL_NAME, modelId, name}
vs
{type: UPDATE_MODEL, modelId, {name}}
I did a bit of searching and I found this question:
Is it ok to create generic redux update action
Now what I'm wondering is that no one is adressing the fact that having specific action types allow for different reducers to 'react' to an action in a cleaner way.
IE: I have a model that is copied from another model like so:
{
name: 'foo',
originalModel: id_0
}
It then becomes easier to react to specific actions in my reducer of copied models if I only want to react to the name change action.
Is it wrong for 2 reducers to react to the same actions? Is that why nobody adressed this issue in the original question?
Having multiple slice reducers respond to the same actions is absolutely an intended use case for Redux. I covered that background in my post The Tao of Redux, Part 1 - Implementation and Intent.
As for your specific question: I think it's entirely valid to have an update action for normalized data that contains the item type name and the item ID. In fact, I demonstrated this exact approach in my post Practical Redux, Part 7: Form Change Handling, Data Editing, and Feature Reducers.
Overall, Redux itself doesn't care what specific action types you have and how generic they are. You are encouraged to define whatever actions are appropriate for your app, and what level of "abstraction" they represent. It's very reasonable to make them a bit more generic - for example, I'd prefer UPDATE_USER_ATTRIBUTES instead of SET_USER_FIRST_NAME and SET_USER_LAST_NAME, but ultimately it's up to you.
This is perfectly valid. This pattern even has a name. "Applying a change set"
Your message becomes the following:
{type: APPLY_CHANGSET, data: {id: idOfThingToApplyTo, propOne: '1', propTwo: '2'}}
Your reducers can then look like this:
const propOneReducer = (value = 'default', {type, {data: {propOne}}) => {
return type === APPLY_CHANGSET && propOne !== undefined ? propOne : value;
}
This makes it a lot easier to add new properties (attributes) to your objects in your store. Adding a reducer, and sending the data from your react views to the actionCreator. In simple cases, you might not even need to change the actionCreator.
In these simple cases you can even build a reducer creator, basically creating the reducer for you.
Pro's
Less actions in the system
Simple sweet actionCreators
Not Pro's
Actions don't describe exactly what is happening. It's also harder to parse exactly what happens to the store after a actionCreator is invoked. This because the reducers now take the shape of the data into account.
Slightly more complex reducers
The store has a method called getState that will return the current state of the store.
What prevents code somewhere in my application from (accidentally) modifying the returned state from store?
Let's say i call this:
let state = store.getState();
state.someProperty = 'fun';
The implementation that i've found on getState on the store object simply returns the inner state object that gets overwritten with each new action.
const getState = () => state;
In between actions/new states what prevents code from modifying the state that will be read by another subscriber? In my above example, setting someProperty to 'fun' will persist inside the store on the state property, until overwritten.
While i'm obviously not supposed to modify the state, a simple mistake might bind the state to some component that (unknowingly) modifies its inputs - perhaps on a 2-way binding in an angular environment?
<app-some-component [user]="state"></app-some-component>
Shouldn't getState() be implemented as a clone of its state model?
P.S. This is not specifically related to Angular - which is why i didn't add the tag - to allow more people not used to Angular to answer the question.
The answer is: nothing :)
The core Redux library itself technically doesn't actually care if state gets mutated or not. You could actually mutate in your reducers, or have other parts of your app get the state tree and mutate it, and the store itself wouldn't know or care.
However, mutation will break time-travel debugging, as well as make tests unreliable. Even more importantly, the React-Redux library assumes that you will handle your state immutably, and relies on shallow equality comparisons to see if the state has changed. (This is the reason why "Why isn't my component re-rendering?" is in the Redux FAQ. 99.9% of the time, it's due to accidental mutation.)
If you are concerned about mutation, you can use a library like Immutable.js instead of plain JS objects, or use one of the several tools for freezing your state in development to catch mutations.
I've created some factory functions that give me simple (or more advanced) reducers. For example (simple one - base on action type set RequestState constant as a value):
export const reduceRequestState = (requestTypes: RequestActionTypes) =>
(state: RequestState = RequestState.None, action: Action): RequestState => {
switch (action.type) {
case requestTypes.start:
return RequestState.Waiting;
case requestTypes.success:
return RequestState.Success;
case requestTypes.error:
return RequestState.Error;
case requestTypes.reset:
return RequestState.None;
default:
return state;
}
};
Using those factory functions and combineReducers from redux I can compose them into fully functional reducer that handles most of my casual actions. That gives me readable code and prevents me from making silly mistakes.
Factories are good for common actions but when I need to add some custom behavior (for action type) which should modify some part of the store significantly I would like to write a custom part of the reducer that will handle that action for me.
The idea is to compose reducers in an iterate manner, so combineReducers but for an array. This way I could use my factories creating reducer and then combine it with my custom reducer that handles some specific actions. The combineReducers for an array would then call the first one, recognize that nothing has changed and call the second (custom) one to handle the action.
I was looking for some solution and found redux-actions but do not quite like the way it links actions and reducers making the semantics little different from what I'm used to. Maybe I do not get it, but eventually I like to see that my reducer is written as pure function.
I am looking for some hint that will show me the way.
Is there any library or project that uses any kind of higher order reducers and combines them in some way?
Are there any downsides regarding composing reducers like described above?
Yep, since reducers are just functions, there's an infinite number of ways you can organize the logic, and composing multiple functions together is very encouraged.
The "reducers in an array" idea you're looking for is https://github.com/acdlite/reduce-reducers. I use it frequently in my own app for exactly that kind of behavior - running a combineReducers-generated reducer first, then running reducers for more specific behavior in turn.
I've written a section for the Redux docs called Structuring Reducers, which covers a number of topics related to reducer logic. That includes useful patterns beyond the usual combineReducers approach.
I also have a list of many other reducer-related utilities as part of my Redux addons catalog.
I'm new to redux and looked at redux-actions or using switch statements in reducer, and though I'm not against using a switch statement, I'm wondering, isn't it easier to just use the call the action method?
Here's what I'm thinking
import actions from './actions'
const reducer = (state = {}, action) => {
if (actions[action.type]) return Object.assign({},
state, actions[action.type](action)
);
return state;
}
I've just tested this on my first reducer and action, and it works, but it seems quite obvious so I'm wondering why the switch type is the chosen way?
Switch statements are certainly the most common approach, but lookup tables are common as well. You can even use plain if/then conditions if you want. Ultimately, how you write your reducers is up to you.
FYI, this topic is covered in the Redux FAQ, in the FAQ: Reducers section. You might also want to read the new "Structuring Reducers" how-to section as well.
Some observations:
Don't refer to these external functions as "actions". They're not actions. They're actually reducers themselves.
Being reducers, you really ought to be passing the state object to them. Oftentimes, you'll want/need to utilise information contained in the current state, as well as information contained in the action object.
Otherwise, this seems like an appropriate approach.