I am using combineReducer to combine reducers and reducer like this
const todo = (state = {}, action) => {
switch (action.type) {
//...
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state
}
return Object.assign({}, state, {
completed: !state.completed
})
default:
return state
}
}
My problem is if i am defining reducer like that i am getting sonar code smell
Function parameters with default values should be last1
but combine reducer pass argument in this sequence only how to work on this?
we did have the same issue within our project, and sonar allows you to define exclusions for rules and files in Administration -> Congifuration -> Analysis Scope.
you will find there a section called Ignore issues on Multiple Criteria and there you can enter the rule and a "file pattern" to exclude files from this rule.
like:
From the Sonarqube docs:
Function parameters with default values should be last:
...But all function parameters with default values should be declared
after the function parameters without default values. Otherwise, it
makes it impossible for callers to take advantage of defaults; they
must re-specify the defaulted values or pass undefined in order to
"get to" the non-default parameters.
This does, however, work with Redux as it calls your reducer for the first time with undefined as the first argument. If you want to continue using this pattern, you'll need to disable the rule or skip that line from analysis.
What if you define a default value for the second arg?
const todo = (state = {}, action = null/undefined) => {
Set action with {}:
for example, changing the below code
const todo = (state = {}, action) => { }
to this
const todo = (state = {}, action = {}) => { }
Will be the safest way.
Related
I am developing an app with React Native and Redux. I have some authentication and localisation logic handled by Redux, and now I want to fetch needed data from remote API using Redux as well.
An example of what I had for authentication:
actions.auth.js
export function saveAuthToken(authToken) {
return {
type: "SAVE_AUTH_TOKEN",
authToken
};
}
...
reducers.auth.js
export function authToken(state = "", action) {
switch (action.type) {
case "SAVE_AUTH_TOKEN":
return action.authToken;
...
default:
return state;
}
}
What I am now trying to add:
actions.data.js
export function fetchData() {
return {
type: "FETCH_DATA"
};
}
...
reducers.data.js
export function dataList(state = {}, action) {
switch (action.type) {
case "FETCH_DATA":
return {};
...
default:
return state
}
}
For test purposes, I now don't even make any calls to API. Just try to return {} for data in any case.
Even though I think that I made everything identical to the way I handled authentication (which worked), when I try to call:
store.dispatch('FETCH_DATA');
I get the following error:
Error: Actions must be plain objects. Use custom middleware for async actions.
I don't quite get where exactly I try to use async actions and why my actions aren't plain objects.
Any help is appreciated.
store.dispatch() takes an action as an argument.
And, as the error says, the action must be plain object.
The error is obvious, because
what you are passing to dispatch() is a string and not an object.
Here is what you would want to do:
import { fetchData } from '<file-path-here>'
store.dispatch(fetchData())
You see, the fetchData() would then return a plain object which is required by store.dispatch()
If you are not using any custom middleware then your action must be plain object in your case like below
store.dispatch({
type : 'FETCH_DATA',
payload : {} // if any pass here
})
You can not dispatch action as string it must have a plain object like
{
type: 'YOUR_ACTION',
payload: yourPayload
}
if you want to pass function or else you must use custom middleware it will also use to dispatch function you can use thunk as middleware or redux-saga.
This link will help you.
redux-thunk
redux-saga
It will help you.
reducers.data.js
export function dataList(state = {}, action) {
switch (action.type) {
case "FETCH_DATA":
return {...state};
...
default:
return state
}
}
export function fetchData() { return dispatch{ type: "FETCH_DATA" }; }
use dispatch to dispatch your actions
basically I always have this problem whereby as reducers are keys of the object store
they are accessed like this.props.users for examples
so if users state is an array, this is fine
const initialState = []
but if I have this:
const initialState = {
users: [],
loading: false,
error: ""
};
coz you know, my reducer needs to do a few more things
then suddenly I have this horrible thing all over my codebase where I'm doing things like:
this.props.users.users clearly, disgusting. how can I get it back to this.props.users but then able to access the other stuff as well?
one example of reducer code case:
case FETCHING_USERS_SUCCEEDED: {
return {
...state,
loading: false,
users: [].concat(...state.users).concat(action.userData)
};
}
you can declare user as a variable like this
const {users} = this.props.users
thereafter you can use users instead of this.props.users.users
When you map your state to props, map state.users.users instead of just state.users.
I have made a like functionality with Redux and React Native.
I have a reducer with initial state posts = [] and my reducer looks like
const initialState = {
posts: []
};
function postReducer(state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
posts: action.posts
};
case LIKE_POST:
return {
...state,
posts: state.posts.map(post => {
if (post._id === action.postId) {
return {
...post,
likes: !!post.likes ? post.likes.concat(action.userId) : [action.userId]
}
}
return post;
})
};
case UNLIKE_POST:
return {
...state,
posts: state.posts.map(post => {
if (post._id === action.postId) {
return {
...post,
likes: post.likes.filter(userId => userId !== action.userId)
}
}
return post;
})
};
default:
return state
}
};
I know that I can not mutate the posts array in my state, so I have to return a new array of posts where I have modified the post that a user tries to like/unlike.
It seems to work very well, but it's daunting slow. I only have very few posts, but I still have to wait almost a second for it the like to be visible.
Is this the right approach? Am I storing my posts correctly as a simple array in the state? I'm not sure what the alternative is, but I have, for instance, seen in this GitHub repo that it can be done different, although I don't fully understand the structure of it.
You are right that this is slow and tedious, but it is necessary if you want to ensure you don't mutate state and stick to the correct patterns that are suggested.
That said you have a few options, an obvious one is to make helper functions that solve the common problems you face, however a (personally) better solution is to use ImmutableJS. It will provide you with three things:
The first is it guarantees you aren't mutating state as all changes return a new and different copy.
The second is a convenient API that will allow you to do your mutations with ease instead of using awkward syntax to work around vanilla javascript.
The third is more of a minor benefit but you get access to Immutable types such as List, Map, a variety of Collections and define your own types with Records, which I recomment to use for each reducer state as you can declare the shape of the state, and only mutate the defined properties, instead of adding new ones.
With Immutable a single part of your reducer would look like this:
case FETCH_POSTS:
return state.set('posts', action.posts);
case LIKE_POST:
let posts = state.posts.map( (p) => {
if (p.id === action.postId) {
return p.set('likes', 'something');
}
return p;
}
It gets even more exciting if you build your state off a Record, so you would have:
export default Record({
posts: List(),
}, 'SocialRecord');
Which would then let you have state.get('posts') or state.posts and still have immutable functionality as Records have getters.
Check out the docs for more information:
Immutable Documentation
I am working on my first React-Redux app.
This is the code of my reducer (I only have one):
const CalculationsReducers = (state = {}, action) => {
switch (action.type) {
case CalculationsActions.LOAD_CALCULATIONS:
return Object.assign({}, state, {
calculations: [{
id: 'abc',
name: 'test',
date: 'test',
status: 'in progress'
}]
});
default:
return state;
}
};
This is the code of my 'mapStateToProps' function I use with connect()
const mapStateToProps = (s) => {
return {
calculations: s.calculations ||[]
};
};
When I dispatch an action with type LOAD_CALCULATIONS, I can see a log trace (using react-logger), but the state object seems very strange to me. Can someone point me my error?
log trace using react-logger
The workflow looks good to me. The state object also looks fine, but you might want to change the alias when importing reducer to configure your store, to make it more intuitive.
In your configure store file:
import calculations from 'reducers/CalculationReducer';
Note that it requires you to export CalculationReducer as default. Then you will see calculations instead of CalculationReducer in your state object.
And I noticed you have a payload along with your action but never got handled in reducer. If you need it in state object then handle it in reducer using action.payload.
Problem: When using thunk middleware before introducing Redux.combineReducers, the getState passed to the thunk correctly returns an object with the correct keys. After refactoring to use Redux.combineReducers, the getState passed to the thunk now returns an object with nested keys. See code below which (hopefully) illustrates my point. This could lead to a potential maintenance nightmare of having to constantly grab the correct key for any thunk method that accesses state.
Question: Is there a simple way to set the correct context key within the thunk? The code feels brittle when I combine reducers and have to insert keys to access the correct state. Am I missing something simple?
Before code:
const Redux = require('redux'),
Thunk = require('redux-thunk');
// this is an action generator that returns a function and is handled by thunk
const doSomethingWithFoo = function() {
return function(dispatch, getState) {
// here we're trying to get state.fooValue
const fooValue = getState().fooValue;
dispatch({ type: "DO_SOMETHING", fooValue });
}
};
// this is a simple action generator that returns a plain action object
const doSimpleAction = function(value) {
// we simply pass the value to the action.
// we don't have to worry about the state's context at all.
// combineReducers() handles setting the context for us.
return { type: "SIMPLE_ACTION", value };
}
const fooReducer(state, action) {
// this code doesn't really matter
...
}
const applyMiddleware = Redux.applyMiddleware(Thunk)(Redux.createStore);
const fooStore = applyMiddleware(fooReducer);
After code (introducing a more global appStore):
// need to rewrite my thunk now because getState returns different state shape
const doSomethingWithFoo = function() {
return function(dispatch, getState) {
// here we're trying to get state.fooValue, but the shape is different
const fooValue = getState().foo.fooValue;
dispatch({ type: "DO_SOMETHING", fooValue });
}
};
const appReducers = Redux.combineReducers({
foo: fooReducer,
bar: barReducer,
});
const appStore = applyMiddleware(appReducers);
After thinking about it some more, I think the answer is to refactor the doSomethingWithFoo action generator so that it accepts fooValue as a parameter. Then I don't have to worry about state object shape changing.
const doSomethingWithFoo(fooValue) {
return function(dispatch, getState) {
// now we don't have to worry about the shape of getState()'s result
dispatch({ type: "DO_SOMETHING", fooValue });
}
}
You're over-thinking things. By definition, store.getState() returns the entire state, and combineReducers() pulls together multiple sub-reducers into a larger object. Both are working as intended. You're writing your own application, so you're responsible for how you want to actually organize your state shape and deal with it. If you feel things are too "brittle" this way, it's up to you to find a good way to structure things, but that's not a problem with Redux.
Also, using getState() in an action creator to determine what to do IS an entirely valid approach. In fact, the Reducing Boilerplate section of the Redux docs even does that as a demonstration:
export function addTodo(text) {
// This form is allowed by Redux Thunk middleware
// described below in “Async Action Creators” section.
return function (dispatch, getState) {
if (getState().todos.length === 3) {
// Exit early
return
}
dispatch(addTodoWithoutCheck(text))
}
}