I working on a react-redux webapp and until now, all action were related to ajax requests, so I'm using redux thunk and axios.
Now, I'm working in a component that would create a list from objects called 'tasks'. This should be very straight forward: the action calls send the task object and the reducer add it in an array inside the 'tasks' state.
The issue is that when called the reducer always rewrite the the tasks object array. Doing a console.log at the beginning of the reducer call I see the state is always empty.
Here is the code:
import {DROP_TASK} from '../actions/types'
export default function (state = {}, action) {
switch (action.type){
case(DROP_TASK):
console.log("state", state); // here is shows object{}
console.log('tasks', state.tasks);
if(state.tasks === undefined || state.tasks === null ) {
return {...state, list: [action.payload]}
}
return {...state, list: [...state.tasks.list, action.payload]};
default:
return state
}
}
at the createStore at the index.js
I'm doing:
const createStoreWithMiddleware = applyMiddleware(reduxThunk, logger)(createStore);
where logger just console.log the whole state before and after every dispatch. So I know that before the dispatch for the reducer function, the "tasks" is populated after a first call.
Here is how I'm combining reducers:
import { combineReducers } from 'redux';
import {reducer as form} from 'redux-form';
import {reducer as uiReducer} from 'redux-ui';
import authReducer from './auth_reducer';
import treeReducer from './tree_reducer';
import media_asset_list_reducers from './media_asset_list_reducers';
import tasks from './tasks';
const reducers = combineReducers({
form: form,
auth: authReducer,
tree: treeReducer,
media_asset_list: media_asset_list_reducers,
tasks: tasks,
ui: uiReducer
});
export default reducers;
Can anyone help me?
Thanks
You arenot showing how are you combining reducers. But it seems to me that half of your reducer is written to get substate in form tasks.list, but it returns new state in form just list.
If you compose reducers with combineReducers, each of the combined reducers gets just its subpart of state (and that is what it should return as well)
Try:
import {DROP_TASK} from '../actions/types'
export default function (state = {}, action) {
switch (action.type){
case(DROP_TASK):
return {...state, list: [...state.list, action.payload]};
default:
return state
}
}
Your reducer will receive the state.tasks slice of the state already, so there is no need to include .tasks in this reducer.
This will result in a state tree like:
{
... //other reducers
tasks: {
list: []
}
}
If you want to remove the list layer and just have tasks be a list you can change the reducer to:
import {DROP_TASK} from '../actions/types'
export default function (state = [], action) {
switch (action.type){
case(DROP_TASK):
return [...state, action.payload];
default:
return state
}
}
Also, DROP_TASK seems like an odd action type for adding an item to list... but not knowing more about your app, this may still be appropriate, so ignore this if so.
Related
Trying to go through redux react tutorial and I am wondering where rootReducer come from
import { createStore } from "redux";
import rootReducer from "./reducers";
export default createStore(rootReducer);
Does combineReducers auto export rootReducer?
https://codesandbox.io/s/9on71rvnyo?file=/src/redux/store.js
Second question in
/components/AddTodo.js
handleAddTodo = () => {
this.props.addTodo(this.state.input);
this.setState({ input: "" });
};
handleAddTodo calls the addTodo action
/redux/actions.js
export const addTodo = content => ({
type: ADD_TODO,
payload: {
id: ++nextTodoId,
content
}
});
Which just returns an object
But what connects the addTodo to the reducer itself in /reducers/todos.js?
export default function(state = initialState, action) {
switch (action.type) {
case ADD_TODO: {
.....
Does combineReducers auto export rootReducer?
Yes it does
export default combineReducers({ todos, visibilityFilter });
is enough
What connects the addTodo to the reducer itself in /reducers/todos.js?
The dispatch function, when you call this:
this.props.addTodo(this.state.input);
under the hood you're actually running something like this:
dispatch(addTodo(this.state.input))
This is the pseudo code for combineReducers:
function combineReducers({ reducer1, reducer2 }) {
return (state, action) => {
if (action.type === 'reducer1') return reducer1(state.reducer1, action);
if (action.type === 'reducer2') return reducer2(state.reducer2, action);
}
}
It's not entirely correct, esp. the returning part, but hopefully it gives you an idea.
Basically, what it does is to create one big reducer that works as if both reducers are combined into one. A reducer is, as you may already know, one function that accepts the previous state and the action, and returns the next state.
Second question: connect does the trick. Once you connect your addTodo prop to the store, the store which already is connected to the reducer, will call the reducer (again, ONE BIG function) with the (prevState, action) value. action will be the return value of addTodo action generator method.
And the reducer does the rest, i.e. checks if the matching action type (in this case, ADD_TODO) exists and does what it does.
I have recently learned the MERN stack and is currently making a side project using it. I am stumbling into a problem in which my Redux Reducer is overriding each other everytime a different action is called. Here is the complete and detailed problem.
Questions:
Behaviour of invoking actions on a React Component. Let's say that we have decided to create an action and implement it in one of our React Component, we have done all of the setup (e.g. creating reducer and updating the state, creating the action itself and connecting it to the react component using connect from react-redux). I was just wondering when I call multiple actions in our React Component from componentDidMount, why does the state override each other. E.g.
componentDidMount(){
// Action updates 'user: {}' state in our reducer
this.props.fetchUserData();
// Action updates 'clients:{}' state
this.props.fetchClientsData();
}
Resulting State from Reducers:
user: {}, // Overrided by the fetchClientsData()
clients { clientsData }
Aren't we essentially update the specific reducer state (fetchUserData() updates 'user:{}' and fetchClientsData() updates 'clients: {}'). WHY IS IT OVERRIDING? I have checked my Redux Devtools middleware and It is infact overriding. HOW DO I NOT OVERRIDE THE OTHER STATE IN THE REDUCER? Thank you!
Update:
- Here is my reducers:
Index.js
import { combineReducers } from "redux";
import AuthReducer from "./AuthReducer";
import NotificationReducer from "./NotificationReducer";
import { reducer as formReducer } from "redux-form";
import DataReducer from "./DataReducer";
export default combineReducers({
form: formReducer,
auth: AuthReducer,
notification: NotificationReducer,
data: DataReducer,
});
AuthReducer.js
import { FETCH_USER, FETCH_HOSPITAL } from "../actionTypes";
export default (state = null, action) => {
switch (action.type) {
case FETCH_USER:
return action.payload || false;
case FETCH_HOSPITAL:
return action.payload || false;
default:
return false;
}
};
DataReducer.js
import { FETCH_DATA } from "../actionTypes";
export default (state = {}, action) => {
switch (action.type) {
case FETCH_DATA:
return { ...state, data: action.payload };
default:
return state;
}
};
The error lies in your AuthReducer, whatever you return from these switch case statement becomes your new state.
The same way you are doing in your DataReducer.js file
Therefore you have to update your state like:
export default (state = null, action) => {
switch (action.type) {
case FETCH_USER:
return {...state, users: action.payload || false};
case FETCH_HOSPITAL:
return {...state, clients: action.payload || false};
default:
return state;
}
};
Also it would be a good thing if you would initialize your state
I have a very weird issue, I have redux store and a react component connected to it with connect() function. I am storing a list of user roles coming from my backend in redux. For this I have two different reducers, userRoleReducer and initialUserRolesReducer. I use the first one to handle changes into the roles in the UI before applying the changes with an API call, and the second one is being used to have the initial roles stored separately after backend responses. The issue I am having, is that both of the reducers are changing, even though only the first one is actually being updated by dispatching an action (Sorry if my use of terms is incorrect). Below are the reducers and action dispatchers.
Reducers:
export function userRolesForUsersRequestSuccess(state = {userRoles: []}, action) {
switch(action.type) {
case 'USER_ROLES_FOR_USERS_REQUEST_SUCCESS':
return action.userRoleDataForUsers;
default:
return state;
}
}
export function initialUserRolesForUsersRequestSuccess(state = {userRoles: []}, action) {
switch (action.type) {
case 'INITIAL_USER_ROLES_FOR_USERS_REQUEST_SUCCESS':
return action.initialUserRoleData;
default:
return state;
}
}
These are the action dispatchers, the first one is called from the connected component, and and after backend response. The second one is called only after the backend response.
export function setUserRolesForUsersRequestSuccess(userRoleDataForUsers) {
return {
type: 'USER_ROLES_FOR_USERS_REQUEST_SUCCESS',
userRoleDataForUsers
};
}
export function setInitialUserRolesForUsersRequestSuccess(initialUserRoleData) {
return {
type: 'INITIAL_USER_ROLES_FOR_USERS_REQUEST_SUCCESS',
initialUserRoleData
};
}
I haven't found anything similar to this from anywhere, so I guess this isn't a common problem, and that's why a good guess is that the issue is in my code. But every other reducer I use are working just fine, and believe me, I have tried to change and check everything I can to make these two work normally as well.
Any help is wanted to track the issue down!
EDIT: The code I use to create the store, not sure if it helps.
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './rootReducer';
import createHistory from 'history/createBrowserHistory';
const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const history = createHistory();
const middleware = routerMiddleware(history);
const initialState = {};
const store = createStore(
rootReducer,
initialState,
composeEnhancers(
applyMiddleware(middleware, thunk))
);
EDIT 2. rootReducer.js file, reducers are combined here.
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import {
userRoleForMeRequestSuccess,
userRolesForUsersRequestSuccess,
userRoleRequestPending,
userPermissionChangeGetResponseMessage,
initialUserRolesForUsersRequestSuccess } from './Common/userRoleReducer';
const appReducer = combineReducers({
userRoleForMeRequestSuccess,
userRolesForUsersRequestSuccess,
userRoleRequestPending,
userPermissionChangeGetResponseMessage,
initialUserRolesForUsersRequestSuccess,
router: routerReducer
});
const rootReducer = (state, action) => {
if (action.type === LOGIN_LOGOUT_REQUEST_SUCCESS) {
state = undefined;
}
return appReducer(state, action);
};
export default rootReducer;
EDIT 3. After I dug more deeply into this problem, I made an observation that if I just pass completely different data for the first reducer, its data stays intact and doesn't change when the other one changes. So could there be some kind of issue in passing exactly the same data as the first new state after the reducers initial state, and that mixes the reducers somehow to always mirror each other?
I have a Container, an actionsCreator, and a reducer. In the below code, what's enabling the Reducer to return action.text instead of an updated state object? I thought reducers had to always return states.
HelloWorldContainer.jsx
import { connect } from 'react-redux';
import HelloWorld from '../components/HelloWorld';
import * as actionCreators from '../actions/helloWorldActionCreators';
const mapStateToProps = (state) => ({ name: state.name });
export default connect(mapStateToProps, actionCreators)(HelloWorld);
helloWorldActionCreators.jsx
import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
export const updateName = (text) => ({
type: HELLO_WORLD_NAME_UPDATE,
text,
});
helloWorldReducer.jsx
import { combineReducers } from 'redux';
import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
const name = (state = '', action) => {
switch (action.type) {
case HELLO_WORLD_NAME_UPDATE:
return action.text
default:
return state;
}
};
const mainReducer = combineReducers({ name });
export default mainReducer;
(Code source: React on Rails).
The name is just a slice of state. And action.text is the updated state.
After combineReducers({ name }), the state tree looks like:
{
name: '..'
}
Besides, redux doesn't limit you that you can only use object as your state. If you pass name to createStore() directly without combineReducers, your entire state will become a plain string.
I thought reducers had to always return states.
No. Reducer has to always return data. Moreover, you should not return state, but a new object (or other data types).
So what is done in your case is that the reducer returns a new string (or whatever data type is text) every time HELLO_WORLD_NAME_UPDATE action is dispatched. It does not care what was already in the state and returns a new text string.
When I look at a reducer being called via combineReducers, it's not getting the state or action in the arguments.
My reducers file is this:
import { combineReducers } from 'redux';
import nav from './reducers/nav';
import pages from './reducers/pages';
import entities from './reducers/entities';
export default function(initialData) {
return function(state, action) {
if (!state) {
console.log('no state', initialData);
return initialData;
}
// This is showing state as having expected value
console.log('state', state);
return combineReducers({
nav,
pages,
entities
});
}
};
My store initialization is like this:
import reducerWrapper from './reducers';
// Initial state is defined above
var reducers = reducerWrapper(initialState),
store = Redux.createStore(reducers, initialState);
The example code on the site doesn't use a wrapper (which I had seen in some other example). I tried that too and it didn't work. I mean in either example I'm not sure how it would get state/action given what is written out. I feel like I'm missing some magic here.
Updated
Here is the reducers file now:
import { combineReducers } from 'redux';
import nav from './reducers/nav';
import pages from './reducers/pages';
import entities from './reducers/entities';
export default combineReducers({
nav,
pages,
entities
});
followed by store = Redux.createStore(reducerWrapper, initialState), also doesn't work, even when I remove the wrapping inside reducerWrapper and just export default combineReducers`
The answer wasn't easily seen here. The problem is that my reducer did not have a default state.
The store triggers an INIT event here: https://github.com/reactjs/redux/blob/master/src/createStore.js#L204
It then eventually gets to here:
https://github.com/reactjs/redux/blob/master/src/combineReducers.js#L52
Meaning if my reducer is function (state, action) { return state } rather than function (state='anyDefaultValue') { return state }, combineReducer will error saying that the reducer did not return state
combineReducers returns a function that needs to be invoked with state and action. You can just export that directly:
const rootReducer = combineReducers(...);
export default rootReducer;
Or you can wrap it as you currently are:
const rootReducer = combineReducers(...);
export default function (initialData) {
return function (state, action) {
if (!state) {
console.log('no state', initialData);
return initialData;
}
// This is showing state as having expected value
console.log('state', state);
return rootReducer(state, action);
}
}