How to minimize reducers boilerplate when using redux with a POJO state? - javascript

I face difficulties to write reducers for Redux. Specifically I end up wrapping a lot of my state manipulation in .map, .slice etc. When the structure of my state object grows larger, the reducers become hard to read, with functions wrapping functions etc.
I didn't want to use Immutable.js as I wanted to stick to simple POJO as long as possible
I tried using combineReducers and reduceReducers libs, with are awesome. But often I end up requiring a large chunk of the state in my reducer.
I wrote a little utility, to help with creating a new state:
var path = require('immutable-path').path;
let state = {
level1: {
level21: {
level3: 3
},
level22: [{
id: 1,
val: 1
}, {
id: 2,
val: 2
}]
}
};
let newState = path(state, 'level1.level22[id=1].val', x => x + 10);
It gives a new state, leaving the original state the same. The references of objects and arrays not modified stay the same (=== comparison is true).
Can someone tells me if I'm missing an easier way with es6 syntax to perform the same results? Or another better community maintained lib to get the same benefits?
Thanks for your time and expertise.

I suggest breaking your reducer up into smaller functions.
Below are four functions, each in turn deals with a smaller part of the state. (Though in each case the first parameter is called state, it's different every time). The action though, is always the same.
I haven't checked this code for correctness, but I hope you get the idea.
function reducerTop(state, action) {
switch (action.type) {
case SET_VAL:
return Object.assign({}, state, {
[action.topLevel]: reducerNext(state[action.topLevel], action);
});
}
return state;
}
function reducerNext(state, action) {
switch (action.type) {
case SET_VAL:
return Object.assign({}, state, {
[action.nextLevel]: reducerArray(state[action.nextLevel], action);
});
}
return state;
}
function reducerArray(state, action) {
switch (action.type) {
case SET_VAL:
const index = state.findIndex((item) => item.id === action.id);
if (index > -1) {
return [
...state.slice(0, index),
reducerFinal(state[index], action),
...state.slice(index + 1)
];
}
}
return state;
}
function reducerFinal(state, action) {
switch (action.type) {
case SET_VAL:
return Object.assign({}, state, {
value: action.value
});
}
return state;
}

I think it is a problem of redux itself -- it is just too low-level, and some things (like nested updates, for instance), just should be easier. Also, I believe that there is no problems to put all business logic into action creators, and inside reducers just update our state (and to perform nested updates, if needed).
Another unnecessary thing, from my point of view -- constants. We usually have some object with all redux entities (for instance, reducers), and I think it should be enough to figure out all needed constants.
With this thoughts, I wrote a library redux-tiles, which does exactly that -- based on given type, it creates constants, handles async functions, and creates reducers for you. So, typical API request will look like the following:
import { createTile } from 'redux-tiles';
const requestTile = createTile({
type: ['api', 'request'],
fn: ({ api, params }) => api.get('/items', params),
});
It will create all constants, a reducer, and also handle failure, success and loading states. You can take a look at the full HN API example with redux-tiles here. You might find there nesting, which will update everything automatically, you just need to return an array of values to nest.

Related

simplify redux with generic action & reducer

In React-Redux project, people usually create multiple actions & reducers for each connected component. However, this creates a lot of code for simple data updates.
Is it a good practice to use a single generic action & reducer to encapsulate all data changes, in order to simplify and fasten app development.
What would be the disadvantages or performance loss using this method. Because I see no significant tradeoff, and it makes development much easier, and we can put all of them in a single file! Example of such architecture:
// Say we're in user.js, User page
// state
var initialState = {};
// generic action --> we only need to write ONE DISPATCHER
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer --> we only need to write ONE ACTION REDUCER
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// define component
var User = React.createClass({
render: function(){
// Here's the magic...
// We can just call the generic setState() to update any data.
// No need to create separate dispatchers and reducers,
// thus greatly simplifying and fasten app development.
return [
<div onClick={() => setState({ someField: 1 })}/>,
<div onClick={() => setState({ someOtherField: 2, randomField: 3 })}/>,
<div onClick={() => setState({ orJustAnything: [1,2,3] })}/>
]
}
});
// register component for data update
function mapStateToProps(state){
return { ...state.user };
}
export default connect(mapStateToProps)(User);
Edit
So the typical Redux architecture suggests creating:
Centralized files with all the actions
Centralized files with all the reducers
Question is, why a 2-step process? Here's another architectural suggestion:
Create 1 set of files containing all the setXField() that handle all the data changes. And other components simply use them to trigger changes. Easy. Example:
/** UserAPI.js
* Containing all methods for User.
* Other components can just call them.
*/
// state
var initialState = {};
// generic action
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// API that we export
let UserAPI = {};
// set user name
UserAPI.setName = function(name){
$.post('/user/name', { name }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ name });
});
};
// set user picture URL
UserAPI.setPicture = function(url){
$.post('/user/picture', { url }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ url });
});
};
// logout, clear user
UserAPI.logout = function(){
$.post('/logout', {}, function(){
setState(initialState);
});
};
// Etc, you got the idea...
// Moreover, you can add a bunch of other User related methods,
// like some helper methods unrelated to Redux, or Ajax getters.
// Now you have everything related to User available in a single file!
// It becomes much easier to read through and understand.
// Finally, you can export a single UserAPI object, so other
// components only need to import it once.
export default UserAPI
Please read through the comments in the code section above.
Now instead of having a bunch of actions/dispatchers/reducers. You have 1 file encapsulating everything needed for the User concept. Why is it a bad practice? IMO, it makes programmer's life much easier, and other programmers can just read through the file from top to bottom to understand the business logic, they don't need to switch back and forth between action/reducer files. Heck, even redux-thunk isn't needed! And you can even test the functions one by one as well. So testability is not lost.
Firstly, instead of calling store.dispatch in your action creator, it should return an object (action) instead, which simplifies testing and enables server rendering.
const setState = (obj) => ({
type: 'SET_USER',
data: obj
})
onClick={() => this.props.setState(...)}
// bind the action creator to the dispatcher
connect(mapStateToProps, { setState })(User)
You should also use ES6 class instead of React.createClass.
Back to the topic, a more specialised action creator would be something like:
const setSomeField = value => ({
type: 'SET_SOME_FIELD',
value,
});
...
case 'SET_SOME_FIELD':
return { ...state, someField: action.value };
Advantages of this approach over your generic one
1. Higher reusability
If someField is set in multiple places, it's cleaner to call setSomeField(someValue) than setState({ someField: someValue })}.
2. Higher testability
You can easily test setSomeField to make sure it's correctly altering only the related state.
With the generic setState, you could test for setState({ someField: someValue })} too, but there's no direct guarantee that all your code will call it correctly.
Eg. someone in your team might make a typo and call setState({ someFeild: someValue })} instead.
Conclusion
The disadvantages are not exactly significant, so it's perfectly fine to use the generic action creator to reduce the number of specialised action creators if you believe it's worth the trade-off for your project.
EDIT
Regarding your suggestion to put reducers and actions in the same file: generally it's preferred to keep them in separate files for modularity; this is a general principle that is not unique to React.
You can however put related reducer and action files in the same folder, which might be better/worse depending on your project requirements. See this and this for some background.
You would also need to export userReducer for your root reducer, unless you are using multiple stores which is generally not recommended.
I mostly use redux to cache API responses mostly, here are few cases where i thought it is limited.
1) What if i'm calling different API's which has the same KEY but goes to a different Object?
2) How can I take care if the data is a stream from a socket ? Do i need to iterate the object to get the type(as the type will be in the header and response in the payload) or ask my backend resource to send it with a certain schema.
3) This also fails for api's if we are using some third party vendor where we have no control of the output we get.
It's always good to have control on what data going where.In apps which are very big something like a network monitoring application we might end up overwriting the data if we have same KEY and JavaScript being loosed typed may end this to a lot weird way this only works for few cases where we have complete control on the data which is very few some thing like this application.
Okay i'm just gonna write my own answer:
when using redux ask yourself these two questions:
Do I need access to the data across multiple components?
Are those components on a different node tree? What I mean is it isn't a child component.
If your answer is yes then use redux for these data as you can easily pass those data to your components via connect() API which in term makes them containers.
At times if you find yourself the need to pass data to a parent component, then you need to reconsider where your state lives. There is a thing called Lifting the State Up.
If your data only matters to your component, then you should only use setState to keep your scope tight. Example:
class MyComponent extends Component {
constructor() {
super()
this.state={ name: 'anonymous' }
}
render() {
const { name } = this.state
return (<div>
My name is { name }.
<button onClick={()=>this.setState({ name: 'John Doe' })}>show name</button>
</div>)
}
}
Also remember to maintain unidirectional data flow of data. Don't just connect a component to redux store if in the first place the data is already accessible by its parent component like this:
<ChildComponent yourdata={yourdata} />
If you need to change a parent's state from a child just pass the context of a function to the logic of your child component. Example:
In parent component
updateName(name) {
this.setState({ name })
}
render() {
return(<div><ChildComponent onChange={::this.updateName} /></div>)
}
In child component
<button onClick={()=>this.props.onChange('John Doe')}
Here is a good article about this.
Just practice and everything will start to make sense once you know how to properly abstract your app to separate concerns. On these matter composition vs ihhertitance and thinking in react are a very good read.
I started writing a package to make it easier and more generic. Also to improve performance. It's still in its early stages (38% coverage). Here's a little snippet (if you can use new ES6 features) however there is also alternatives.
import { create_store } from 'redux';
import { create_reducer, redup } from 'redux-decorator';
class State {
#redup("Todos", "AddTodo", [])
addTodo(state, action) {
return [...state, { id: 2 }];
}
#redup("Todos", "RemoveTodo", [])
removeTodo(state, action) {
console.log("running remove todo");
const copy = [...state];
copy.splice(action.index, 1);
return copy;
}
}
const store = createStore(create_reducer(new State()));
You can also even nest your state:
class Note{
#redup("Notes","AddNote",[])
addNote(state,action){
//Code to add a note
}
}
class State{
aConstant = 1
#redup("Todos","AddTodo",[])
addTodo(state,action){
//Code to add a todo
}
note = new Note();
}
// create store...
//Adds a note
store.dispatch({
type:'AddNote'
})
//Log notes
console.log(store.getState().note.Notes)
Lots of documentation available on NPM. As always, feel free to contribute!
A key decision to be made when designing React/Redux programs is where to put business logic (it has to go somewhere!).
It could go in the React components, in the action creators, in the reducers, or a combination of those. Whether the generic action/reducer combination is sensible depends on where the business logic goes.
If the React components do the majority of the business logic, then the action creators and reducers can be very lightweight, and could be put into a single file as you suggest, without any problems, except making the React components more complex.
The reason that most React/Redux projects seem to have a lot of files for action creators and reducers because some of the business logic is put in there, and so would result in a very bloated file, if the generic method was used.
Personally, I prefer to have very simple reducers and simple components, and have a large number of actions to abstract away complexity like requesting data from a web service into the action creators, but the "right" way depends on the project at hand.
A quick note: As mentioned in https://stackoverflow.com/a/50646935, the object should be returned from setState. This is because some asynchronous processing may need to happen before store.dispatch is called.
An example of reducing boilerplate is below. Here, a generic reducer is used, which reduces code needed, but is only possible the logic is handled elsewhere so that actions are made as simple as possible.
import ActionType from "../actionsEnum.jsx";
const reducer = (state = {
// Initial state ...
}, action) => {
var actionsAllowed = Object.keys(ActionType).map(key => {
return ActionType[key];
});
if (actionsAllowed.includes(action.type) && action.type !== ActionType.NOP) {
return makeNewState(state, action.state);
} else {
return state;
}
}
const makeNewState = (oldState, partialState) => {
var newState = Object.assign({}, oldState);
const values = Object.values(partialState);
Object.keys(partialState).forEach((key, ind) => {
newState[key] = values[ind];
});
return newState;
};
export default reducer;
tldr It is a design decision to be made early on in development because it affects how a large portion of the program is structured.
Performance wise not much. But from a design perspective quite a few. By having multiple reducers you can have separation of concerns - each module only concerned with themselves. By having action creators you add a layer of indirection -allowing you to make changes more easily. In the end it still depends, if you don't need these features a generic solution helps reduce code.
First of all, some terminology:
action: a message that we want to dispatch to all reducers. It can be anything. Usually it's a simple Javascript object like const someAction = {type: 'SOME_ACTION', payload: [1, 2, 3]}
action type: a constant used by the action creators to build an action, and by the reducers to understand which action they have just received. You use them to avoid typing 'SOME_ACTION' both in the action creators and in the reducers. You define an action type like const SOME_ACTION = 'SOME_ACTION' so you can import it in the action creators and in the reducers.
action creator: a function that creates an action and dispatches it to the reducers.
reducer: a function that receives all actions dispatched to the store, and it's responsible for updating the state for that redux store (you might have multiple stores if your application is complex).
Now, to the question.
I think that a generic action creator is not a great idea.
Your application might need to use the following action creators:
fetchData()
fetchUser(id)
fetchCity(lat, lon)
Implementing the logic of dealing with a different number of arguments in a single action creator doesn't sound right to me.
I think it's much better to have many small functions because they have different responsibilities. For instance, fetchUser should not have anything to do with fetchCity.
I start out by creating a module for all of my action types and action creators. If my application grows, I might separate the action creators into different modules (e.g. actions/user.js, actions/cities.js), but I think that having separate module/s for action types is a bit overkill.
As for the reducers, I think that a single reducer is a viable option if you don't have to deal with too many actions.
A reducer receives all the actions dispatched by the action creators. Then, by looking at the action.type, it creates a new state of the store. Since you have to deal with all the incoming actions anyway, I find it nice to have all the logic in one place. This of course starts to be difficult if your application grows (e.g. a switch/case to handle 20 different actions is not very maintainable).
You can start with a single reducer, the move to several reducers and combine them in a root reducer with the combineReducer function.

Redux mapStateToProps returns undefined

Redux is successfully storing and updating state. The reducers are seemingly working correctly. I'm able to use this.props.dispatch. However, when it actually comes to detailing that information (i.e. this.props.array I always seem to get undefined.
Reducer:
export default function array(state = {}, action) {
switch (action.type) {
case "UPDATE_ARRAY":
state.array = action.array
return state;
default:
return state;
}
}
State-aware component:
constructor(props) {
super(props);
this.state = {
array: array
}
}
--
self.props.dispatch({
type: 'UPDATE_ARRAY',
array: array
})
--
const mapStateToProps = (state) => {
return {
messages: state.messages,
array: state.array
};
};
export default connect(mapStateToProps)(Component);
This only seems to be able to save state btw when I define an empty array. This doesn't seem right, I thought the intention of Redux was a self-contained store? Updating a variable seems to defeat the purpose a bit.
Would appreciate any help.
export default function array(state = {}, action) {
switch (action.type) {
case "UPDATE_ARRAY":state={
...state,
array:action.array
}
return state;
default:
return state;
}
}
you should always update your state immutably,instead of mutating the current application state ,you should create another object and return that.State should be immutable ,only way to change the state is to create a new one.This helps to improve the performance of the application.
I am not sure if you application has more than one reducer or not, if it has, than you must be using combine reducer method .So to access state.array in mapsStateToProps is like this
const mapStateToProps = (state) => {
return {
messages: state.{reducer_name}.message,
array: state.{reducer_name}.array
};
};
in place of 'reducer_name' you have to specify the reducers_name which you have define in combine reducer
And last mapStateToProps return array ,in props not in component state.
which you can access in this way {this.props.array},you cant set component state in componentDidMount and in componentWillRecieveProps (in case of aysnc action).
Your component will receive array as a field in its props field. Your code assumes it's in the state field. So instead of:
this.state = {
array: array
}
you would just access this.props.array wherever in your code you need to use the array. You don't need to put it in the local state at all. Usually, you would use it in the render function, like in this example:
render()
{
return <div>The array contains {this.props.array.length} items.</div>
}
I wonder if you're confusing local state with the Redux store's state? Local state is what you get/set when you access this.state in your component code. Every component can have its own state object that it can read from and write to.
The Redux store's state is what's passed in to mapStateToProps. It's usually the entire state object of all the combined reducers in your top-level reducer (though if you only have one reducer function and are not using combineReducers, then the store state is identical to that single reducer's state).
I suggest choosing more descriptive variable names, so that your code will be more readable. It's hard to understand what your intentions are for your code with such generic names. For example, you could name your reducer something that indicates what it's for, like bookListReducer, and name the array you want to store and retrieve for what will go inside it, like books. Naming both your reducer and all your variables array makes it harder to read your code. This will help anyone who reads your code in the future - including, most importantly, you!, as well as future Stack Overflow readers of your future questions (and perhaps this one if you edit it).
I am not sure the following is your issues, but hope these will help:
export default function array(state = {}, {type, array}) {
switch (type) {
case "UPDATE_ARRAY":
return {...state, array};
default:
return state;
}
}
Your reducer should be pure, which you had is mutating the state.
constructor(props) {
super(props);
this.state = {
array: array // what is array?
}
}
Above constructor is not right. You should be able to access the array from this.props.array as your mapStateToProps
Do a console.log(this.props) in your render function or ComponentWillReceiveProps, see if you can something :)

Overwrite entire state in redux

This is yet another novice question about redux. In my app, i would like to be able to load the state from a text file, i.e. to be able to completely re-initialise the whole state object. So I don't want to reset the state to an initial value, but replace it with a new one. (FYI the application stores data merely in the browser's localStorage. Also, so far I have followed the awesome tutorial from http://redux.js.org/docs/introduction/index.html) I have tried several approaches, but none of them have yielded results. For example, in my reducers/index.js i have:
export default function App (state = {}, action) {
return {
todos: todos(state.todos, action),
...
global: global(state, action)
}
}
In reducers/global.js I have:
const global = (state = {}, action) => {
switch(action.type) {
case 'LOAD_DB_FROM_FILE':
return action.fileContents;
default:
return state
}
}
What happens is that the state object, oddly (or not :)) enough, gets a new field called global which contains the original state (and not the one read from the file) and it even gets nested for a couple of levels (so i have a replica of the state at state.global.global.)
I am aware of the hackiness of this approach, even willing to accept a fundamental flaw (due to my ignorance) in my setup, still i haven't been able to find a simple and unambiguous answer to my problem.
As always, any help would be much appreciated.
I know little about redux, but based on what I know about JavaScript I would say you need something like this:
// reducers/index.js
export default function App (state = {}, action) {
state = global(state, action);
return {
todos: todos(state.todos, action),
...
};
}
So the reducer named global has a chance to replace the whole state object at once.

Redux: what is the correct way to filter a data array in reducer?

i want to filter an array on search SEARCH_TEXT is an on change action
what I'm confused with is how i return the state when the delete key is pressed and the text now becomes empty, i figure i could use initial state in the else statement but my inclination is this is wrong? when i return just state it has all ready been manipulated in the if statement.
simple example.
thanks in advance.
const initialState = ['hello', 'wahhh', 'yo'];
export default function searchSimple(state = initialState, action) {
switch (action.type) {
case SEARCH_TEXT:
if(action.text.length > 0){
return state.filter(item =>
item.startsWith(action.text)
)
}
else {
return state
}
Remember always that the state is your "source of truth". Be wary of eliminating state on the basis of a temporary filter. Once you do so those items are gone. (The only way to get them back is to reset your state to the initialState, which may not be ideal.)
A better approach is to keep your items list as is, and simply store the search text.
const initialState = {
searchText: '',
items: [ 'hello', 'wahhh', 'yo' ]
};
export default function searchSimple(state = initialState, action) {
switch (action.type) {
case SEARCH_TEXT:
return Object.assign({}, state, {
searchText: action.text
});
}
}
Whilst your state won't contain the filtered list, it tells you everything you need to know to construct the filtered list.
Assuming you're using React, your "smart component" can be setup with the following mapStateToProps() function:
function mapStateToProps(state) {
const { items, searchText } = state.searchSimple;
return {
filteredItems: items.filter((item) => item.startsWith(searchText))
};
}
Should you need this filtered list in more than one place, consider creating a "selector" function, as demonstrated in the Redux shopping cart example.
https://github.com/reactjs/redux/blob/master/examples/shopping-cart/src/reducers/cart.js
It would look something like this:
export function filteredItems(state) {
const { items, searchText } = state.searchSimple;
return items.filter((item) => item.startsWith(searchText));
}
For a more advanced approach to selectors, check out the reselect library.
https://github.com/rackt/reselect
IMO, the right place to filter data is not directly in the reducers but in the selectors.
From redux docs:
Computing Derived Data
Reselect is a simple library for creating memoized, composable selector functions. Reselect selectors can be used to efficiently compute derived data from the Redux store.
I'm currently using selectors to filter and sort data.
No data repetition in the state. You don't have to store a copy of the filtered items.
The same data can be used in different components, each one using a different filter for example.
You can combine selector applying many data computations using selector that you already have in the application.
If you do right, your selectors will be pure functions, then you can easily test them.
Use the same selector in many components.

How to divide the logic between Redux reducers and action creators?

I have some logic that I've put in the reducer which I'm thinking should be possibly put in the Action and passed down?
Is it best practice to put this sort of stuff in the actions or reducer?
Working example here.
Reducer code:
function Card() {
this.card = (Math.random()*4).toFixed(0);
}
Card.prototype = {
getRandom: function(){
var card;
//console.log(this.card)
switch (this.card) {
case '1':
card = 'heart';
break;
case '2':
//card = 'diamonds';
card = 'heart'; // weight the odds abit
break;
case '3':
card = 'club';
break;
case '4':
card = 'spade';
break;
default:
card = 'heart';
break;
}
return card;
}
}
var dealer = {
deal: function(){
var results = [];
for(var i = 0; i <4; i++){
var card = new Card();
results.push(card.getRandom());
}
console.log(results);
return results;
}
}
const initialState = {
count: 0,
data: []
}
function counter (state = initialState, action) {
let count = state.count
switch (action.type) {
case 'increase':
return Object.assign({}, state, {
data: dealer.deal(),
count: state.count+1
})
default:
return state
}
}
Your reducer must be pure. Currently it is not pure. It calls deal() which calls getRandom() which relies on Math.random() and thus is not pure.
This kind of logic (“generating data”, whether randomized or from user input) should be in the action creator. Action creators don’t need to be pure, and can safely use Math.random(). This action creator would return an action, an object describing the change:
{
type: 'DEAL_CARDS',
cards: ['heart', 'club', 'heart', 'heart']
}
The reducer would just add (or replace?) this data inside the state.
In general, start with an action object. It should describe the change in such a way that running the reducer with the same action object and the same previous state should return the same next state. This is why reducer cannot contain Math.random() calls—they would break this condition, as they would be random every time. You wouldn't be able to test your reducer.
After you figure out how the action object looks (e.g. like above), you can write the action creator to generate it, and a reducer to transform state and action to the next state. Logic to generate an action resides in action creator, logic to react to it resides in the reducer, reducer must be pure.
Finally, don’t use classes inside the state. They are not serializable as is. You don’t need a Card class. Just use plain objects and arrays.
My understanding is that actions should be simple objects that contain two things: (i) the action type and (ii) what changed (i.e. the new data).
Reducers on the other hand, are pure functions that take actions and the previous app state as inputs and return the new app state. How they accomplish this is up to you. You can add whatever logic necessary to take the combination of previous state + action and return the new state as long as you don't mutate data outside your reducer function(s).
As for your code specifically, I'm not sure the deal() function belongs in either an action or a reducer. I think a better place might be in some sort of event handler (e.g. onClick). You could then pass the results of the deal call as an action to your reducer.
It appears then it's a best practice to have an static class that handles the first level entery points that instantiates the redux actions outside of the redux.
I suppose that makes sense to keep the store and action chains pure.
This might look like a lot of replicated code at first but when you start dispatching based on conditions or need to dispatch from multiple places it starts to open up and make sense.

Categories

Resources