Write action and reducer efficient and clean (react redux) - javascript

I have below actions and reducer:
actions:
import { OPEN_NODE, CLOSE_NODE, GET_NODES } from '../constants/NodeActionTypes';
export function openNode(path) {
return {
type: OPEN_NODE,
path: path
};
}
export function closeNode() {
return {
type: CLOSE_NODE
};
}
export function getNodes(path) {
return {
type: GET_NODES,
path: path
};
}
reducer:
export default function opener(state = initialState, action) {
switch (action.type) {
case OPEN_NODE:
var { path } = action
var {nodes} = getFileList(path)
return {
...state,
open:true,
nodes:nodes
};
case CLOSE_NODE:
return {
...state,
open:false
};
case GET_NODES:
var { path } = action
var {nodes} = getFileList(path)
return {
...state,
nodes:nodes
};
default:
return state;
}
}
Obviously, OPEN_NODE contain GET_NODES (only plus open:true), but there seems many way to organize the code:
pack GET_NODES reducer to a function, call this in OPEN_NODE , and add open:true.
modify openNode action, send [OPEN_NODE, GET_NODES] together , but how to write switch(action.type)'s case ?
let OPEN_NODE reducer dispatch a getNodes action to trigger GET_NODES reducer
which is best ? Or any another better way?

You don't have to keep everything inside your switch statement. If you have 2 similar actions, just refactor into a private function and call it.
In your case, it might be something like:
// your reducer helper
const getNodes = (state) => {
var { path } = action
var {nodes} = getFileList(path)
return {
...state,
nodes:nodes
};
};
// your reducer function
export default function opener(state = initialState, action) {
switch (action.type) {
case OPEN_NODE:
return { ...getNodes(state), open:true };
case GET_NODES:
return getNodes(state);
// ....
}

You can simply use the switch statement to execute both actions :
export default function opener(state = initialState, action) {
switch (action.type) {
case OPEN_NODE:
case GET_NODES:
var { path } = action
var {nodes} = getFileList(path)
return {
...state,
nodes:nodes
open: action.type === OPEN_NODE ? true : state.open
};
case CLOSE_NODE:
return {
...state,
open:false
};
default:
return state;
}
}

Check out my github project for creating generic reducers. The solution I purpose will address many of the concerns you currently have.
Redux-Reducer-Generator

Related

How to save an object in redux?

I build an app in React with Redux and I try to send to my state an object and I try to save it in 'thisUser' but I don't know how to write that 'return' because mine doesn't work.
My Redux state:
const initialState = {
thisUser: {}
}
export function usersReducer(state = initialState, action) {
switch (action.type) {
case 'users/addUser':
return { ...state, thisUser: { ...state.thisUser, ...action.payload} } //the problem
default:
return state
}
}
Dispatch method:
dispatch({ type: "users/addUser", payload: new_user });
Can you tell me how to write that return, please?
If you want to append new user then why are you using object type. You should use Array Type thisUser.
const initialState = {
thisUser: []
}
export function usersReducer(state = initialState, action) {
switch (action.type) {
case 'users/addUser':
return { ...state, thisUser: [ ...state.thisUser,action.payload ] }
default:
return state
}
}
Or
If you want to save only single user object then change only that line in your code:
return { ...state, thisUser: action.payload }
It's better to use an array type for if you have a list of users .
If you have a case when you need to use an object just change the brackets [ ] on my code to curly braces { } .
const initialState = {
thisUser: [],
}
export function usersReducer(state = initialState, action) {
switch (action.type) {
case 'users/addUser':
return { ...state, thisUser: [ ...state.thisUser, ...action.payload]}
default:
return state
}
}

Is it possible to merge two reducers into one?

I would like to merge two reducers, the first being created as a generic one and the second one would be more specific to it's own state. Both these reducers would not handle the same cases. Merging these would only result in default case being duplicated, the default case always returning the default state anyways. This would help as I would only test the generic one once.
In case you were thinking about reduceReducers or combineReducers, that would not work since I have many "special" reducers with every one of them having the same action type to handle and all of those reducers have a different part of the state to modify.
const initialState = {
byId : {},
ids: []
}
const dogsReducer = ({ dogs: state = initialState, ...restOfState }, action) => {
switch (action.type) {
case INITIALIZE:
return {
byId : _.keyBy(state.dogs, 'id'),
ids: state.map(({id}) => id)
}
case RESET:
return initialState
case SPECIFIC_DOG_ACTION:
...
default:
return state
}
}
const catsReducer = ({ cats: state = initialState, ...restOfState}, action) => {
switch (action.type) {
case INITIALIZE:
return {
byId : _.keyBy(state, 'id'),
ids: state.map(({id}) => id)
}
case RESET:
return initialState
case SPECIFIC_CAT_ACTION:
...
default:
return state
}
}
I want to isolate the following cases : INITIALIZE and RESET in a generic switch/case function or a generic reducer, so I would only have to test those cases once and not in every reducer. There would be more generic cases in the future, that's why I want to avoid repetition.
This is the expected result :
const genericReducer = (state = initialState, action) => {
switch (action.type) {
case INITIALIZE:
return {
byId : _.keyBy(state.dogs, 'id'),
ids: state.map(({id}) => id)
}
case RESET:
return initialState
default:
return state
}
}
const dogsReducer = ({ dogs: state = initialState, ...restOfState }, action) => {
switch (action.type) {
case SPECIFIC_DOG_ACTION:
...
default:
return state
}
}
const catsReducer = ({ cats: state = initialState, ...restOfState}, action) => {
switch (action.type) {
case SPECIFIC_CAT_ACTION:
...
default:
return state
}
}
const finalCatsReducer = mergeReducers(catsReducer, genericReducer)
const finalDogsReducer = mergeReducers(dogsReducer, genericReducer)
I could imagine using the following however I want to say that would have a similar effect to just putting them all flat. The only added benefit I can see is that the specific switch cases won't be verified unless the general cases fail.
const genericSwitch = type => {
switch (type) {
case 1:
do something x
default: //specificSwitch
switch (type) {
case 2:
do something y
default:
return z
}
}
}
The simplest solution is to wrap into the upper method:
const combinedSwitch = type => {
const result = genericSwitch(type);
return result === z ? specificSwitch(type) : result;
}
const genericSwitch = type => {
switch (type) {
case 1:
do something x
default:
return z
}
}
const specificSwitch = type => {
switch (type) {
case 2:
do something y
default:
return z
}
}

How to use reduce-reducers with combineReducers

So, I'm trying to use some piece of state from one reducer in another reducer, and I looked for a solution and seems the package reduce-reducers does exactly that, buth here's my question ...
I have the following rootReducer.js file where I import all my reducers and combine them with combineReducers like this ...
import { combineReducers } from "redux";
import globalReducer from "./globalReducer";
import booksReducer from "../features/books/booksReducer";
import categriesReducer from "../features/categories/categoriesReducer";
import authorsReducer from "../features/authors/authorsReducer";
const rootReducer = combineReducers({
global: globalReducer,
books: booksReducer,
categories: categriesReducer,
authors: authorsReducer
});
export default rootReducer;
Now I want to use some state from the authors reducer in the books reducer, how can I achieve that with reduce-reducers package?
reduce-reducers works like "merge" of reducers:
const reducer = (state = 0, action) => {
switch (action.type) {
case 'ADD':
return state + action.value;
case 'MULTIPLE':
return state * action.value;
default:
return state;
}
};
The same can be written with reduce-reducers:
const addReducer = (state = 0, action) => {
switch (action.type) {
case 'ADD':
return state + action.value;
default:
return state;
}
};
const multipleReducer = (state = 0, action) => {
switch (action.type) {
case 'MULTIPLE':
return state * action.value;
default:
return state;
}
};
const reducer = reduceReducers(addReducer, multipleReducer, 0);
So on your task, it means, that you should rewrite your authorsReducer and booksReducer so it gets entire state as first argument, not only its own authors part:
const before = (state = [], action) => {
switch (action.type) {
case `NEW_BOOK`:
return state.concat([action.book]);
default:
return state;
}
}
const now = (state = {}, action) => {
switch (action.type) {
case `NEW_BOOK`:
return {
...state,
books: state.books.concat([action.book]),
};
default:
return state;
}
}
BUT! I think this is not what you're actually want.
There is another package that does exactly what you need combine-section-reducers
It allows you to access root state from any other reducer:
const before = (state = [], action) => {
switch (action.type) {
case `NEW_BOOK`:
return state.concat([action.book]);
default:
return state;
}
}
const now = (state = [], action, rootState) => {
switch (action.type) {
case `NEW_BOOK`:
return state.concat([{
...action.book,
author: rootState.authors[action.book.authorId],
}]);
default:
return state;
}
}

returning nested state with redux and thunk

I'm fairly new to redux & thunk, and have been following tutorials to try and understand, and am managing to work it into my app ok. One thing i'm not understanding, is how i can get several state objects on the root level into one nested object. For example, right now my state looks like:
{
timeline: [Array] // My timeline data in an array of objects
timelineHasErrored: false,
timelineIsLoading: false
}
But what I really want is:
{
timeline : {
data: [Array] // My timeline data in an array of objects
hasErrored: false,
isLoading: false
}
}
and i'm really not quite sure how to nest these, or what the proper way to do that is. Below is my redux code, it's pretty simple so i'll post it all.
Reducers index
import { combineReducers } from 'redux'
import { timeline, timelineHasErrored, timelineIsLoading } from './timeline'
export default combineReducers({
timeline, timelineHasErrored, timelineIsLoading
});
Timeline Reducers
import { TIMELINE_HAS_ERRORED, TIMELINE_IS_LOADING, TIMELINE_FETCH_DATA_SUCCESS } from '../constants/action-types.js'
export function timelineHasErrored(state = false, action) {
switch (action.type) {
case TIMELINE_HAS_ERRORED:
return action.hasErrored;
default:
return state;
}
}
export function timelineIsLoading(state = false, action) {
switch (action.type) {
case TIMELINE_IS_LOADING:
return action.isLoading;
default:
return state;
}
}
export function timeline(state = [], action) {
switch (action.type) {
case TIMELINE_FETCH_DATA_SUCCESS:
return action.timeline;
default:
return state;
}
}
Actions
import { TIMELINE_HAS_ERRORED, TIMELINE_IS_LOADING, TIMELINE_FETCH_DATA_SUCCESS } from '../constants/action-types.js'
import api from '../services/api'
export function timelineHasErrored(bool) {
return {
type : TIMELINE_HAS_ERRORED,
hasErrored : bool
}
}
export function timelineIsLoading(bool) {
return {
type : TIMELINE_IS_LOADING,
isLoading : bool
}
}
export function timelineFetchDataSuccess(timeline) {
return {
type : TIMELINE_FETCH_DATA_SUCCESS,
timeline
}
}
export function timelineFetchData() {
return dispatch => {
dispatch( timelineIsLoading(true) )
api.getTracks().then(
res => {
dispatch( timelineIsLoading(false) )
dispatch( timelineFetchDataSuccess(res.body) )
},
err => {
dispatch( timelineIsLoading(false) )
dispatch( timelineHasErrored(true) )
}
)
}
}
And then in my react component I format the object like how i want it... but i think it would be better to have it nested in the actual state so i'm not creating extra work for myself if things change
// Redux State
const mapStateToProps = (state) => {
const obj = {
timeline : {
data : state.timeline,
hasErrored: state.tracksHasErrored,
isLoading: state.tracksIsLoading
}
}
return obj
}
// Redux Dispatch
const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch( timelineFetchData() )
}
}
If anybody has any tips or corrections for me bring em on, i'm trying to get a solid grasp on redux, thanks!
Your timeline reducer is pretty small, so you could have it as a single reducer as follows:
const initialState = {
data: [],
hasErrored: false,
isLoading: false
};
export function timeline(state = initialState, action) {
switch (action.type) {
case TIMELINE_HAS_ERRORED:
return {
...state,
hasErrored: action.hasErrored
};
case TIMELINE_IS_LOADING:
return {
...state,
isLoading: action.isLoading
};
case TIMELINE_FETCH_DATA_SUCCESS:
return {
...state,
data: action.timeline
};
default:
return state;
}
}
Then you wouldn't need to call combineReducers(), unless you had other reducers.

redux - how to store and update a key/value pair

I am using redux wth reactjs.
I want to store simple key/value pairs but can't get the reducer syntax right.
In this case each key/value pair will hold a connection to an external system.
Is this the right way to do it? I'm at the beginning with redux so it's a bit of mystery.
export default (state = {}, action) => {
switch(action.type) {
case 'addConnection':
return {
connections: {
...state.connections, {
action.compositeKey: action.connection
}
}
default:
return state
}
}
This worked for me:
export default (state = {}, action) => {
switch(action.type) {
case 'addConnection':
return {
...state,
connections: {
...state.connections,
[action.compositeKey]: action.connection
}
}
default:
return state
}
}
From the docs:
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#correct-approach-copying-all-levels-of-nested-data
You just have a couple mistakes with {} instead of [] and forgetting to use Object.assign.
const reducer = (state = {}, action) => {
switch (action.type) {
case 'addConnection':
return Object.assign({}, state, {
connections: [
...state.connections,
{
[actions.compositeKey]: action.connection
}
]
});
default:
return state;
}
}
export default reducer;
It might help to see it expressed this way too. It does the same thing but I think it reads a little nicer
const reducer = (state = {}, {type, compositeKey, connection}) => {
switch (type) {
case 'addConnection':
return Object.assign({}, state, {
connections: state.connections.concat({
[compositeKey]: connection
})
});
default:
return state;
}
}
export default reducer;
Or if you're using Immutable, something like this
import Immutable from 'immutable';
const reducer = (state = Immutable.Map(), {type, compositeKey, connection}) => {
switch (type) {
case 'addConnection':
return state.set(
'connections',
state.get('connections').concat({
[compositeKey]: connection
})
);
default:
return state;
}
}
export default reducer;
This may work
const reducer = (state = {}, {type, compositeKey, connection}) => {
switch (type) {
case 'addConnection':
var newData={};
newData[compositeKey]=connection;
return Object.assign({}, state, newData)
});
default:
return state;
}
}
export default reducer;

Categories

Resources