I have a large reducer where I'm storing all data entities in a flat structure.
I want to seperate this into 2 seperate reducers so that some of the entities are persisted with Redux Persist and some are not.
To acheive this, I'm trying to add a matcher to detect if the state already has the entity and reject if not.
i.e.
//these cases are used by both reducers
builder
.addCase(update, (state, action) => {})
.addCase(copy, (state, action) => {})
.addCase(clone, (state, action) => {})
.addMatcher(isEntityAction, matcher);
function isEntityAction(action: AnyAction): action is PayloadAction {
return !!action.payload?.entity;
}
function matcher(state, action: AnyAction): action is PayloadAction<any> {
return state[action.payload?.entity?.entityType] !== undefined;
}
However the matcher is having no effect.
What's the right way to go about this? Thanks
You are misinterpreting addMatcher here.
The sigature is addMatcher(matcher, reducer) - the matcher can only say "I want to handle this action" or "I do not want to handle it" - it cannot look at the state. It can also not "reject" something.
Related
I've always struggled to get my head around Redux-thunk, as it really don't understand what great purpose it serves. For example, here's a random Redux-Thunk example I found from a website:
export const addTodo = ({ title, userId }) => {
return dispatch => {
dispatch(addTodoStarted());
axios
.post(ENDPOINT, {
title,
userId,
completed: false
})
.then(res => {
setTimeout(() => {
dispatch(addTodoSuccess(res.data));
}, 2500);
})
.catch(err => {
dispatch(addTodoFailure(err.message));
});
};
};
It's seemingly simple, addTodo is a function that takes in the title and userId and returns a function with dispatch as a parameter, which then uses dispatch once and then again for the response of the HTTP request. Because in this case Redux-Thunk is being used, you would just do dispatch(addTodo(x,x));
Why would I not just do something like this though?
function addTodo(dispatch, title,userId){
dispatch(addTodoStarted());
axios
.post(ENDPOINT, {
title,
userId,
completed: false
})
.then(res => {
setTimeout(() => {
dispatch(addTodoSuccess(res.data));
}, 2500);
})
.catch(err => {
dispatch(addTodoFailure(err.message));
});
}
Then from anywhere, I can just call addTodo(dispatch, x, x);
Why would I use the Redux-Thunk example over my own?
Here are few points through which i will try to explain why should go with redux-thunk.
Its middle ware so it will make dispatch and state object available in every action you define without touching you component code.
When you pass dispatch function which is either from props or from mapDispatchToProps(react-redux) which creates closure. This closure keeps memory consumed till asyc operation finished.
When ever you want to dispatch any action, after completing or in async operation you need to pass dispatch function and in this case you need to modify two files like your component and actions.
If something is already available and tested with lot effort and community support why not use it.
your code will be more readable and modular.
Worst case for both approach, say after completing project, need to change thunk approach, you can easily mock thunk middle ware with your custom middle ware code and resolve it but in case of passing dispatch function it will refactoring all code and search and replace and find way to manage it.
What is a better way to create reducers with handleActions in redux-actions:
1. Create reducers for each CRUD operations (like add data, delete data) and combine it. How set initialState in this case?
2. Set actions in one reducer (fetchDeleteDataRequest, fetchDeleteDataSuccess, fetchAddDataRequest, fetchAddDataSuccess by example)?
You can have sperate reducers and or common reducers to add or delete data that is up to you. If you are considering separate actions for add and delete you will need to keep the data consistent. But having a common function to deal with the CRUD operations is ideal since this will reduce the amount of code that you have to use but you will need a way to distinguish them as well (maybe bypassing some variable ADD or DELETE). let's consider and list of items that you will be adding or deleting. This list can be an empty array in the beginning (initialState) and pass it as props to your component.
Actions
export const addDeleteItem = data => dispatch => {
// you can perform REST calls here
dispatch({
type: ADD_REMOVE_DATA,
payload: data
});
};
Reducers
let initialState = {
items:[]
}
export default (state = initialState, action) => {
switch (action.type) {
case ADD_REMOVE_DATA:
if(action.payload.event === 'ADD'){
return {...state,items:[...state.items,action.payload.item]}
}else if(action.payload.event === 'DELETE'){
return {...state,items:state.items.filter(item=>item.id !== action.payload.item.id)}
}else if (action.payload.event === 'UPDATE'){
//handle your update code
}
}
}
This is just an example you can follow something like this to avoid code duplication.
Lets say I have an application that displays issues for a given project. A user can open an issue, meaning they can see it on the screen, or close the issue, meaning it disappears. When I close the project, I want to also hide the issue visible to the user.
How can I avoid duplicating the business logic for mutating the state within the reducer? I can think of three options, and am wondering which is best, or what alternatives are available.
Idea one: Just repeat the code. I copy the CLOSE_PROJECT code into any method that needs it, like CLOSE_ISSUE.
const reducer = (state, action) => {
switch (action.type) {
case OPEN_PROJECT:
state.project = action.payload.project
case CLOSE_PROJECT:
state.project = null
state.issue = null
case CLOSE_ISSUE:
state.issue = null
}
}
Idea two: Move re-used code into helper functions. Pass the state into the helper function, get back the new state. (Note: I am using immer.js, but just picture this as psuedo code that doesn't actually mutate the state)
const closeProject = (state, action) => {
state.project = null
state
}
const closeIssue = (state, action) => {
state.project = null
state
}
const reducer = (state, action) => {
switch (action.type) {
case OPEN_PROJECT:
state.project = action.payload.project
case CLOSE_PROJECT:
state = closeProject(state)
state = closeIssue(state)
case CLOSE_ISSUE:
state.issue = null
}
}
Idea three: Handle the logic outside of the reducer. Have helper functions that co-ordinate multiple dispatch calls.
const closeProject = (dispatch) {
dispatch(closeProjectAction())
dispatch(closeIssueAction())
}
const ReactThing = (dispatch) => {
const handleCloseProjectClick = () => {
closeProject(dispatcher)
}
return (
<div onClick={ e => handleCloseProjectClick() }>Close</div>
)
}
I think idea three is the best. But it feels strange to have all these business logic functions just kind of floating around outside of Redux. Are there better alternatives?
All three options are entirely valid. To some extent, it's a question of how you want to model your logic, and how you want to abstract common functionality.
There's a couple sections of the Redux docs that mostly address your questions:
Redux FAQ: Where should my "business logic" live?
Structuring Reducers: Reusing Reducer Logic
I also discussed several aspects related to this in my post The Tao of Redux, Part 2: Practice and Philosophy.
Also, as a side note: I strongly recommend using our Redux Starter Kit package, which uses Immer internally for simplified reducer setup.
For example we have reducer photos, which handles array of photos via actions ADD_PHOTO and REMOVE_PHOTO. And if we have arrays users and posts, they both have field for array of photos.
So, in order to avoid code duplicates I'm going to do the following:
Create reducer user = combineReducers(..., photos, ...)
Create actionCreator updateUser
const updateUser = (id, subAction) => ({
type: UPDATE_USER,
payload: {
id,
subAction
}
})
Create reducer users (Here I'm using Immutable.js)
function users(state = List(), action) {
switch (action.type) {
//...
case UPDATE_USER:
const { id, subAction } = action.payload
const index = state.findIndex(user => user.id == id)
return state.updateIn(
[index, 'photos'],
state => photos(state, subAction)
)
break
//...
default:
return state
}
}
And then I'm going to use all of it like this:
dispatch(updateUser(id, addPhoto(url)))
Is this a correct solution of my problem?
Why not simply dispatch both in the place where this is initiated by the user?
dispatch(updateUser(id));
dispatch(addPhoto(url));
I haven't come across this pattern you're applying before. It seems a bit unusual that one reducer is responsible for reducing state of another. Creates a dependency between them that doesn't feel very pure. I'm not even sure one reducer should be able to/can see the state of another.
So no idea about "correct", but I'd say it's not ideal to do it your way. I'd try dispatching both sequentially or maybe in a sort of meta-action that takes care of nested updates and dispatches actions to multiple reducers.
In this example I'm using an action named ADD_TODO
import { createStore, combineReducers } from 'redux';
function todos(state, action) {
state = state || [];
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.text ]);
default:
return state;
}
}
function counter(state, action){
state = state || 0;
switch (action.type){
case 'INCREMENT':
return state+1;
case 'DECREMENT':
return state-1;
case 'ADD_TODO':
return state+100;
default:
return state;
}
}
var app = combineReducers({
todos: todos,
counter: counter
});
var store = createStore(app);
store.dispatch({ type: 'ADD_TODO': text: 'buy eggs' });
This cause both the "todos" and "counter" reducers to trigger.
Should I make all reducers have unique actions unless I actually intended it?
How can we implement this with multiple reducers that almost do the same thing? Multiple counters for example can have "INCREMENT" and a "DECREMENT" actions.
Should name spacing actions solve it?
eg: "POINT_INCREMENT", "POINT_DECREMENT".
There's nothing inherently wrong with having different reducers respond to the same action -- for example, if you refresh the entire state at once. But yeah, if you have two counters that correspond to different things, you probably want to come up with a naming scheme to differentiate. But I would think the action names probably should have some noun to indicate what they apply to.
This cause both the "todos" and "counter" reducers to trigger. Should I make all reducers have unique actions unless I actually intended it?
Yes, probably they should have different unique actions.
From your example it becomes not really clear what you actually intend.
Should the counter count the amount of todo's ?
In that case it can actually be sensible that a "ADD_ITEM" action would both update the counter and also add a todo item. In that case please refer to the answer of acjay
How can we implement this with multiple reducers that almost do the same thing? Multiple counters for example can have "INCREMENT" and a "DECREMENT" actions.
When displaying a list of counters in the same app, each counter can be assigned a unique identifier (id).
An action should pass along the id of the counter.
export const toggleTodo = id => ({
type: 'INCREMENT',
id
})
The reducer should then check by id which counter to update.
See this example of a todo list in the official redux docs.
https://redux.js.org/basics/example
Redux actions are in a way globals. There are different strategies to workaround this problem on a larger scale.
https://kickstarter.engineering/namespacing-actions-for-redux-d9b55a88b1b1