Redux - How to extract the same business logic from three different reducers? - redux

I have three different reducers doing different things but at the end they all change a property in the slice object being passed on which is common to all of them. The part that is common is where it unregisters an element by removing a property and a reference in an array.
I wonder what the best practice is to extract the repeated code into a separate method. I can extract it to a helper function but for that I'd like to create a copy of the slice instead of mutating it directly, I don't own the project so I don't have lodash available as of now, so that's another hurdle.
A second option would be to keep the repeated code which goes against the DRY principle. Third, I could also create a function within the same file that does the job, which is not much difference from doing it in a helper.
What is the best practice here? I really don't think that keeping the repeated code is a good idea, but I also don't want to create a function that mutates the slice within it However, I don't have lodash available now for a deep copy so I wonder what I could do here.

You can definitely extract helper methods that can be called in reducers.
Assuming you're using Redux Toolkit and createSlice, you can write those helper functions as "mutating" update logic, and as long as you call them inside of createSlice, it's safe to use them that way, such as:
function addSomeItem(state, item) {
state.items.push(item);
}
// later
createSlice({
name: "fruits",
initialState: {items: []},
reducers: {
fruitAdded(state, action) {
addSomeItem(state, action.payload);
}
}
})

Related

What is the purpose of an actions file in Redux?

I have big problem with understand redux. The worst, I have to use it with vanillia js, but first I have to understand elements of redux. And ok, I understand reducer. There is states and functions. But action? There are only name of possible actions like:
const ADD = 'ADD'
export function add(number) {
return { type: ADD, number}
}
And only what it is given to me, that then in switch/case in reducer I have ADD I run some function (from reducer). And I don't understand what this function add should really do. I have functions in reducer, so why I need something that return me type and number? Even if I have a lot of function, action give me only more names, variables and functions and in the end big mess and problem with find something. And redux should give something opposite.
And, I really understand that problem is that I don't understand something and if I understand it redux should give me clear and understandable global storage.
So please, can someone help me with it?
What you are talking about are called "Action Creators".
They are not strictly required. It is possible for you to call dispatch directly like dispatch({ type: ADD, number}). But most people prefer to use action creators so that they can call dispatch(add(number)). It adds a layer of abstraction such that you can call an action without needing to know how the underlying action object is structured. And it simplifies things if the action object is complicated.
An "action" in redux is an object which has a type property and also has all the other information that the reducer needs to execute the action. In your case the extra information is just a number, but it could be more complex.
This is your action:
{ type: ADD, number}
An action creator is a function which takes some arguments and uses those arguments to create an action object.
This is your action creator:
export function add(number) {
return { type: ADD, number}
}
Yours is very simple, but here's an example to show you how it might be more complicated.
export function updateUser(userId, changes) {
return {
type: UPDATE_USER,
payload: {
id: userId,
changes,
},
meta: {
timestamp: Date.now(),
}
};
}
This function doesn't do anything except for create the action. It doesn't update the user -- that is the job of the reducer.
If you are wanting to reduce the amount of boilerplate, you might consider using the Redux Toolkit package, which simplifies things by creating actions automatically based on the reducer.

Can a redux-toolkit createSlice use a js Map as state?

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)

Redux Tutorial - why can't or shouldn't the action creator dispatch actions?

I am working my way through the redux tutorial here. I have some experience with "traditional Flux" so the first codeblock looks familiar to me, but I am confused by the second statement.
In traditional Flux, action creators often trigger a dispatch when invoked, like so:
function addTodoWithDispatch(text) {
const action = {
type: ADD_TODO,
text
}
dispatch(action)
}
In Redux this is not the case.
Instead, to actually initiate a dispatch, pass the result to the dispatch() function:
dispatch(addTodo(text))
dispatch(completeTodo(index))
If my action creator is this:
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
dispatch(addTodo(text))
Isn't that functionally equivalent to the first code example? Why does redux require the second way to actually dispatch?
Action Creators are used in Redux for adding extra layers of indirection and abstraction.
There are several reasons to use action creators rather than putting all your logic directly:
Encapsulation: Consistently using action creators means that a component doesn't have to know any of the details of creating and dispatching the action.
Abstraction: Put the logic for creating that action in one place.
Brevity: There could be some larger logic that goes into preparing the action object, rather than just immediately returning it.
Testability: It is easy to write tests for the component that pass in a mock version of the function instead. It also enables reusing the component in another situation.
Useful resources:
http://redux.js.org/docs/basics/Actions.html#action-creators
http://blog.isquaredsoftware.com/2016/10/idiomatic-redux-why-use-action-creators/

Composing higher order reducers in Redux

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.

Redux - Use action object method in reducer instead of switch

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.

Resources