Why redux actions needs to be serializable? - reactjs

https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants
While it is certainly possible to manually create action objects everywhere, and write each type value by hand, defining reusable constants makes maintaining code easier.
I don't feel easy at all. 95% of my actions are used once or twice. Anyone feel writing actions are beneficial?
Background
I got mad writing redux actions. Even for libs like zustand need action. So I decided to write an anonymous function to automatically change state for me. Hence the warning.
reducer: (state, action) => {
let newState = { ...state };
if (action.type === "func") {
newState = produce(state, action.func); // import produce from 'immer'
}
return newState;
},

Yes it is possible and can be done but serialization of actons enable several of Redux's defining features, such as time travel debugging, and recording and replaying actions. As an example: time travel debugging means redux knows when new thread for a specific time is resolved and can be checked back by the store to re-render the Components. Writing it in un-serialable way, redux does not keep this track.

Related

How can I invalidateQueries, react-query, within a redux-toolkit reducer?

As its probably obvious, I am trying to have a couple of APIs, used within the redux-toolkit reducer all invalidate my queries, react-query. BUT, I can't get it to work because of not being able to use "hooks" within the reducer. Instead of tracking down all the api calls.. and then invalidating "n" number of times, I figure to just do something like... but of course, not working. Anyone have ingenius way of invaliding queries from "n" number of api calls?
.addMatcher(
(action): action is AnyAction =>
[
asyncActions.someAction1.fulfilled,
asyncActions.someAction2.fulfilled,
asyncActions.someAction3.fulfilled,
asyncActions.someAction4.fulfilled,
].some(actionCreator => actionCreator.match(action)),
(state, action: AnyAction) => {
// INVALIDATE MY QUERIES HERE
}
)
NOTE: I know redux-toolkit has it's own query. But I have a fast project and don't have time to learn it and also, by upgrading my toolkit, it creates massive amounts of TS errors (I guess a lot TS changes with later versions)...
You are not allowed to trigger any side effect in a Redux Reducer, ever. You will need to find another place to do something like this - if you want to tie it to the Redux action being dispatched, you could look into the listener middleware.

How to use an array as a dependency of a React hook

I have a component that has a callback. It depends on an array of plain old objects stored in redux which won't change very often while the component itself will change its state pretty frequently. Some subcomponents should be rerendered on those state changes, but the one that uses the callback, should not.
What's the best approach to making an array a dependency of useCallback()? So far, I've been using
const handleAllItemsSelectedChange = useCallback(
checked => {
if (checked) {
dispatch(setSelected(items));
} else {
dispatch(selectSelected([]));
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[JSON.stringify(items)]
);
This doesn't seem ideal, and potentially slower than just rerendering the component every time. I can't imagine this isn't a very common use-case. The React team surely has a best practice for this, right? I can't find it in the documentation.
JSON.stringify or any deep comparison is going to be inefficient and slow. React has no plans to support it
Depending on whether you add or remove items (if not mutating the objects) you can just compare with items.length. Or you could possibly save performance by just creating the function each time, as opposed to trying to save performance putting it in a useCallback.
It's a case by case scenario
In redux reducer every time the array changes you have to create new array. So, your array becomes immutable and you can use it for dependency by reference. Example below just demonstrates the principle.
function reducer(state, action) {
switch(action.type) {
case "addItem":
return {...state, items: [...state.items, action.value]};
case "changeProp":
return {...state, prop: action.value}
default:
return state;
}
}
As you can see every time the array changes you'll get the new instance of the array. That means you can use it by reference and don't need to strignify it anymore:
const handleAction = useCallback(checked=>{
....
}, [items]);
By the way immutability is the approach recommended by redux documentation

Sending error message from reducer to user

I'm new to React and Redux and I'm trying to write a simple application where a person can submit a URL for an image and it will show up on the page. Note that there is no backend to the application as of yet.
export const addImage = (url) => {
return {
type: ADD_IMAGE,
key: Guid.create().toString(),
payload: url
}
}
Adding an image creates an action of type ADD_IMAGE and my reducer updates the state consequently. However I also check if the URL is already in the list.
switch (action.type) {
case ADD_IMAGE:
if (state.find(image => image.url === action.payload)) {
return state;
} else {
return(
[
...state,
{key: action.key, url: action.payload}
]
);
}
break;
default:
}
The problem is that when I deny a post because the URL is already in the state I also want to convey that message to the user by showing it in a div next to the form. From what I've read I think I'm not supposed to try to access React state from reducers (if that is even possible) and... well.. I'm just stuck. I've been trying to find a simple guide on how to do this but I find nothing I can quite understand. After adding a database I guess I will have to do this as part of the async process but as I have it now I guess there should be some kind of simple solution.
You are starting to introduce logic into your reducer and this will inevitably lead to situation where you need to process some state outside of the reducer's scope.
The solution is to transfer your reducer logic into a thunk using a middleware package such redux-thunk (or similar package). This allows you to treat special kinds of actions as functions which means you can extend a plain action with specific action-related logic. The example you give of needing to dispatch an error action under certain conditions is an excellent use-case for redux-thunk.
Below is a example of how you might pull the logic out of your reducer into a thunk. You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.
Thunk example
export const addImage = (url) => {
return (dispatch, getState) => {
const key = Guid.create().toString()
dispatch({
type: ADD_IMAGE,
key,
payload: url
})
const state = getState()
// you would want to use a `selector` here to locate the existing image
// within the state tree
const exists = selectors.images.exists(state, url)
if (exists) {
dispatch(actions.ERROR_IMAGE_EXISTS({key, url}))
}
}
}
A note on selectors
You will see that I am using a selector to determine if the image exists. In the same way that thunks are the place to put your dispatch logic, a selector is the place to put your state-traversal logic. They are used to return portions of the state-tree or provide simple state-utilities such as the exists function shown above. Packages are available to help, for example reselect.
Follow on questions from comments
Are selectors not a built-in thing in Redux?
No they are not. Selectors are an idea that builds on top of redux and the concept exists as a place to put your state searching, caching, reading logic. This extracts the sometimes complex state traversal logic out of your thunks and components and into a nice tidy, structured collection of selectors.
Why use a selector instead of state.images.find(i => i.url === url)?
If you use a selector package then you get far more benefit than just a good separation of concerns, you get a big performance improvement (see usage example below).
Here are the headlines from the popular reselect package:
Selectors can compute derived data, allowing Redux to store the minimal possible state.
Selectors are efficient. A selector is not recomputed unless one of its arguments change.
Selectors are composable. They can be used as input to other selectors.
Why doesn't actions.ERROR_IMAGE_EXISTS(url) work for me
Because I just made that up for the example. The point is that you can dispatch actions from within the thunk, how you declare or get access to the action is up to you. I tend to centralise all my shared actions into an actions object that I import.
Selector usage example
Here is an example from my real-life code that shows how I use selectors to passing portions of the state as props to a react component:
const mapStateToProps = (state) => ({
model: services.model.selector.getSelected(state),
build: services.build.selector.getLastBuild(state),
recommendations: services.recommend.selector.getRecommendations(state)
})
Each of these selectors is finding the correct portion of the state tree and delivering it back ready for use. Nice and tidy, and if you use reselector, very efficient.

how to reduce redux boilerplate

I'm new to redux and I find that every little thing x turns into x_success and x_failure, usually when fetching data or trying to create a new entity, and that means more action creators, and more handling in the reducers. What's the recommended approach here? thanks.
Recommended approach is x_success, x_failure etc. But this is for async operations only. Let's see why :
Async operations in your SPA are the operations you want to know
when operation started,
when you got response back
Type of response , success or failure
So that you will have seperate actions creator functions which return objects and one async action creator function which can return function instead of object and calls other action creators from its body.
For the reasons above you should have seperate action creators, one async action creator and of course for every action creator you should have a constant in your reducer.
Assuming you are writing your constants, actions and reducers in seperate folders, this can be a nightmare. If this is the case, you should take a look at here duck modular redux .
Duck modular redux is something you should definetely implement to reduce the boilerplate. Other things like seperate action creators, seperate constants, seperately checking on reducers for constants are required. There is nothing to feel bad about it.
I started to work with Redux almost since the beginning of its story, 2 years ago. While it is a great thing and it allows to eliminate a whole type of bugs and makes all business logic more explicit, it has a lot of concepts. For each entity, you need to create:
constants (for each state -- in case of async function, there are three of them, for start, failure and success)
action (function)
reducer (update state, sometimes a nested update)
It was mentioned that you can something like redux-ducks, which is an approach to organize your code, but you will not write less of the code. So, I strongly believe people should consider writing their own wrapper around redux more seriously.
I wrote a library redux-tiles, which deals exactly with this kind of situations -- it takes the burden of creating constants, update the state (so you don't need to write a reducer by yourself), and do nested updates if needed. So, code for async request, will look something like this:
import { createTile } from 'redux-tiles';
const apiRequest = createTile({
type: ['api', 'request'],
fn: ({ api, params }) => api.get('/api/items', params),
});
It also allows you to combine other actions easier, because in the fn you have access to dispatch and actions. You can take a look at more examples here.
I started using Redux and I loved the concept, but I got really annoyed by the big amount of boilerplate. I ended up creating actionware lib. Basically here's what you have with actionware:
no more action creators and action types, just actions (simple functions) and reducers
actions dispatch their result automatically
error status for every
action with no extra code busy status for every async action (yep, no extra code!)
cancellable actions
There are a number of ways you can reduce the bolierplate. Here's an excellent resource from official repo:
https://github.com/reactjs/redux/blob/master/docs/recipes/ReducingBoilerplate.md
Besides, there are various community-driven libraries that help you in reducing redux boilerplate and in organising your store better. Redux Box might interest you in this regard:
https://github.com/anish000kumar/redux-box
A huge Boilerplate is one of the drawbacks of the flux architecture in general. You might want to checkout redux-fluent, it has been designed with that in mind.
import { createAction, createReducer, ofType } from 'redux-fluent';
const addTodo = createAction('todos | add');
export const todos = createReducer('todos')
.actions(
ofType(actions.addTodo).map(
(state, { payload }) => state.concat(payload),
),
)
.default(() => []);
To reduce Redux boilerplate you can generate action creators and reducer by describing one function with redux-blaze:
import { buildReducer } from "redux-blaze";
export const { actionCreators, reducer: filtersReducer } = buildReducer(initialState, {
setMySearch: ({ search }) /* <- payload */ => state => ({ ...state, search }),
setCategory: ({ category }) => state => ({ ...state, category }),
setSort: ({ sort }) => state => ({ ...state, sort }),
}, {
prefix: 'MY_FILTER',
});
...
// Just add auto generated reducer to root reducer:
export const rootReducer = combineReducers({
filters: filtersReducer
})
...
// dispatch an action:
dispatch(actionCreators.setCategory({category: 'my category'}))
You can also find TypeScript examples here:
https://github.com/spiderpoul/redux-blaze

Should I use one or several action types to represent this async action?

I'm building a front-end for a search system where almost all user actions need to trigger the same async action to re-fetch search results. For example, if a user enters a keyword, then we need to fetch /api/search?q=foo, and if they later select a category we fetch /api/search?q=foo&categoryId=bar. I originally had separate action types for FETCH_RESULTS, SELECT_CATEGORY, DESELECT_CATEGORY, etc. I created one asynchronous action creator for FETCH_RESULTS, but the others are synchronous. The more I think about it, they all end up needing to re-fetching the results from the backend and update the app state based on the response from the backend.
Would it make sense for me to use the single async action-creator for any change? Or would it be better to use async action creators for each distinct user action (selecting a keyword, category, or filter)?
I think the advantage of granular actions would be the events more accurately reflect what the user did (e.g. the user selected a category) vs having to peer into the payload to figure out what actually changed, but they are all pretty similar.
This is of course something only you can really answer based on what you know about the project. I don't think that there is any inherent advantage to having the actions be more granular, and if there aren't any, its not worth the extra effort. I would have a generic FILTER_CHANGED event and not worry about being able to see what specifically changed--presumably the action isn't going to be complicated, so I'm not going to be debugging the action a lot. As the filter state becomes more complicated and diverse, it might make more sense to break out the actions. By default though, I don't really see much value.
I fully agree with Nathan’s answer.
I just want to add that in order to tell whether actions A and B are really one or two actions, you need to ask yourself: “If I change how some reducers react to A, will I also need to change how they react to B?”
When the handlers change together in the reducer code, it’s likely they should be a single action. When their changes may not affect each other, or if many reducers handle just one of them but not the other, they should probably stay separate.
I agree with Dan Abramov: if the text and categories are highly coupled in your interface, just fire FETCH_RESULTS with the text and categories as action payload.
If the text input and categories selection widget do not share a close parent component, it is complicated to fire a FETCH_RESULTS which contains the text and categories (unless passing a lot of props down the tree...): you then need the action granularity.
One pattern that I have found helpful when such granularity is needed is the Saga / Process manager pattern. I've written a bit about it here: https://stackoverflow.com/a/33501899/82609
Basically, implementing this on redux would mean there's a very special kind of reducer that can trigger side-effects. This reducer is not pure, but do not have the purpose of triggering React renderings, but instead manage coordination of components.
Here's an example of how I would implement your usecase:
function triggerSearchWhenFilterChangesSaga(action,state,dispatch) {
var newState = searchFiltersReducer(action,state);
var filtersHaveChanged = (newState !== state);
if ( filtersHaveChanged ) {
triggerSearch(newFiltersState,dispatch)
}
return newState;
}
function searchFiltersReducer(action,state = {text: undefined,categories: []}) {
switch (action.type) {
case SEARCH_TEXT_CHANGED:
return Object.assign({}, state, {text: action.text});
break;
case CATEGORY_SELECTED:
return Object.assign({}, state, {categories: state.categories.concat(action.category) });
break;
case CATEGORY_UNSELECTED:
return Object.assign({}, state, {categories: _.without(state.categories,action.category) });
break;
}
return state;
}
Note if you use any time-traveling (record/replay/undo/redo/whatever) debugger, the saga should always be disabled when replaying actions because you don't want new actions to be dispatched during the replay.
EDIT: in Elm language (from which Redux is inspired) we can perform such effects by "reducing" the effects, and then applying them. See that signature: (state, action) -> (state, Effect)
There is also this long discussion on the subjet.
EDIT:
I did not know before but in Redux action creators can access state. So most problems a Saga is supposed to resolve can often be solved in the action creators (but it creates more unnecessary coupling to UI state):
function selectCategory(category) {
return (dispatch, getState) => {
dispatch({type: "CategorySelected",payload: category});
dispatch({type: "SearchTriggered",payload: getState().filters});
}
}

Resources