Looks like my reducer is not updating the store. Any idea why that would be happening?
import 'babel-polyfill'
const initialState = {
user: {
name: 'Username',
Avatar: '/img/default/avatar.png'
},
friendsList: []
}
export default (state = initialState, action) => {
switch (action.type) {
case 'setUserInfo' :
// If I console.log action.user here, I see that I'm getting good user data
return Object.assign({}, state, {
user: action.user
})
default: return state
}
}
Your reducer is not having any problem it will update the state and returning the correct one.
Please make sure that you have the following codes present.
create an action creator like this
var updateUser = function (user) {
return {
type: 'setUserInfo',
user: user
}
}
Create a store importing your reducer
import { createStore } from 'redux'
import userReducer from 'your reducerfile'
let store = createStore(userReducer)
You must need to dispatch the action by
store.dispatch(updateUser({name: 'some', Avatar: '/image/some'}));
Related
I'm fairly new to Redux and I'm trying to understand why is the combineReducers function calling the reducers twice.
My reducer/index.js looks like this
import {combineReducers} from "redux"
const AReducer = (state = "A", action) =>{
console.log("In A reducer")
switch (action.type){
case "Alpha":
return state + action.payload
default:
return state
}
}
const BReducer = (state = "B", action) =>{
console.log("In B reducer")
switch(action.type)
{
case "Beta":
return state + action.payload
default:
return state
}
}
const allReducers = combineReducers({
A : AReducer,
B : BReducer
})
export default allReducers
and my store/index.js looks like this
import {createStore} from "redux";
import allReducers from "../Reducer"
const store = createStore(allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default store
And the console log is displaying this
index.js:4 In A reducer
index.js:4 In A reducer
index.js:15 In B reducer
index.js:15 In B reducer
index.js:4 In A reducer
index.js:15 In B reducer
I only want to understand why it's behaving like this. I want a better grasp around what's going on in the background
First of all, let's print the action.
import { combineReducers, createStore } from 'redux';
const AReducer = (state = 'A', action) => {
console.log('In A reducer, action: ', action);
switch (action.type) {
case 'Alpha':
return state + action.payload;
default:
return state;
}
};
const BReducer = (state = 'B', action) => {
console.log('In B reducer, action: ', action);
switch (action.type) {
case 'Beta':
return state + action.payload;
default:
return state;
}
};
const allReducers = combineReducers({
A: AReducer,
B: BReducer,
});
const store = createStore(allReducers);
The logs:
In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
In A reducer, action: { type: '##redux/PROBE_UNKNOWN_ACTIONn.x.t.b.s.j' }
In B reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
In B reducer, action: { type: '##redux/PROBE_UNKNOWN_ACTIONu.8.f.5.c.h' }
In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
In B reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
Explanation
I use AReducer's logs to explain, Breducer is also the same.
combineReducers function calls assertReducerShape() function internally.
assertReducerShape() function will invoke each reducer passed in the combineReducers function with a init action to check if the reducer has a valid returned value. This is how In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' } log come.
And, it also invokes each reducer with unknown actions to check if the reducer return the current state for any unknown actions unless it is undefined. This is how In A reducer, action: { type: '##redux/PROBE_UNKNOWN_ACTIONn.x.t.b.s.j' } log come.
When calling the createStore function, it will dispatch init action. So that every reducer returns their initial state. This effectively populates the initial state tree. This is how In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' } log come. This process is mentioned in the documentation, See tips.
Also take a look at the INIT and PROBE_UNKNOWN_ACTION action types in utils/actionTypes file.
const randomString = () =>
Math.random().toString(36).substring(7).split('').join('.')
const ActionTypes = {
INIT: `##redux/INIT${/* #__PURE__ */ randomString()}`,
REPLACE: `##redux/REPLACE${/* #__PURE__ */ randomString()}`,
PROBE_UNKNOWN_ACTION: () => `##redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
These action types are private, and used by redux internally, you don't need to handle them. You will see the dispatched INIT action in redux dev tools, don't be surprised.
I am trying to make multi dispatch action in the action phase of redux:
Here is my code:
export const get_data_time_slot_week = (params) => {
return async (dispatch) => {
dispatch({
type: common.CALL_API_REQUEST,
});
const res = await callAPI.post(TIME_SLOT_WEEK + GET, {
params: { ...params },
});
if (res.status === 200 && res.data.code >= 0) {
//Here, I get new state of Selector timeSlotWeek
dispatch({
type: timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS,
payload: {
data: [...res.data.data],
dataPage: { ...res.data.dataPage },
errCode: res.data.code,
},
});
//And here, I lost state of a Selector timeSlotWeek add get new state of Selector common
dispatch({
type: common.GET_FEEDBACK,
payload: {
msg: "__msg_can_not_to_server",
},
});
}
};
};
Why did it happen? And how can i keep the state of timeSlotWeek with same flow in my code ?
This is my result when i check by Redux tool
GET_DATA_TIME_SLOT_WEEK_SUCCESS => data: { 0: {...}, 1{...} }
GET_FEEDBACK => data: {}
msg: "new msg"
This is my store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import rootReducer from "./reducers";
import thunk from "redux-thunk";
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
export default store;
This is my {combineReducers}
import { combineReducers } from "redux";
import feedback from "./feedback.reducer";
import loadingReducer from "./loading.reducer";
import timeSlotWeek from "./timeSlotWeek.reducer";
const rootReducer = combineReducers({
dataTimeSlotWeek: timeSlotWeek,
loading: loadingReducer,
feedback: feedback,
});
export default rootReducer;
Thanks for your help
UPDATE: Problem solve:
Because in my reducer of timeSlotWeek.reducer I have a default case, and when I dispatch another action, this case will run and make the state of timeSlotWeek become initState.
import { common, timeSlotWeek } from "../actions/constants";
const initState = {
data: [],
};
export default (state = initState, action) => {
switch (action.type) {
case timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS:
state = {
// Pass payload to this
};
break;
default:
state = { ...initState };
}
return state;
};
I fix it by this way:
import { common, timeSlotWeek } from "../actions/constants";
const initState = {
data: [],
};
export default (state = initState, action) => {
switch (action.type) {
case timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS:
state = {
// Pass payload to this
};
break;
**case common.CALL_API_FINISH:
state = state;
break;
case common.GET_FEEDBACK:
state = state;
break;**
default:
state = { ...initState };
}
return state;
};
Have any way better than my way ? Thank for cmt
The default reducer case should always return the current state object. I can't think of a single counter-example otherwise (though I have seen some tutorials throw an error here you generally don't want to do that as it adds unnecessary error handling and complicates everything).
You need only define cases for actions your state slice reducer needs to handle, otherwise the let the default case handle it by simply returning the current state.
const initState = {
data: [],
};
export default (state = initState, action) => {
switch (action.type) {
case timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS:
return {
// Pass payload to this
};
default:
return state;
}
};
If you need to reset some state then use another action and case for this, i.e.:
case 'resetTimeSlotWeek':
return initState;
In redux actions, when we want to set a value, we use a type to dispatch like this :
dispatch({
type: SET_LOADER,
payload: true
})
Where the type: SET_LOADER stored in a different file and export it like below.
export const SET_LOADER = 'SET_LOADER'
And in reducer we will do it like this :
function initialState() {
return {
formErr: {},
isLoading: false
}
}
export default function (state = initialState(), action) {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
So in my application, I have this SET_LOADER type used in different actions and reducers. For example, in authentication, in profile update, when I want to load, I will use this type. So I have this type imported in various places.
I'm not sure if it's okay to use a single type for multipurpose because I noticed now that when I do dispatch, the redux state that get updated is not belonged to the target reducer. The state update is happening at different reducer.
But it's working for the first time dispatch. The next update, it's updating the incorrect redux state. After I refresh the page and try to update again, then it work.
first of all you need to separate your reducer into multiple reducers and then combine them in the store , then you can probably get away by using that same action in multiple cases for but then it'll be only a per reeducer solution meaning that let's say you have and Auth reducer this reducer will have its isLoading , and it may interfere with other actions within that reducer , fore example FetchAllProducts will use isLoading but also FetchByIdProduct is using isLoading and same for other actions that will trigger a loading state .
let's consider these reducers which use the same initial state
function initialState() {
return {
formErr: {},
isLoading: false
}
}
export const authReducer=(state = initialState(), action)=> {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
export const productsReducer=(state = initialState(), action)=> {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
export const cartReducer =(state = initialState(), action)=> {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
//this is the store
import {createStore,applyMiddleware,compose,combineReducers} from 'redux'
import thunk from 'redux-thunk'
import {productsReducer} from './reducers/ProductReducer'
import {cartReducer} from './reducers/CartReducer'
import {authReducer } from './reducers/AuthReducer'
const initialState={
products: {
formErr: {},
isLoading: false
},
cart: {
formErr: {},
isLoading: false
},
auth: {
formErr: {},
isLoading: false
}
}
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(combineReducers({
products: productsReducer,
cart : cartReducer ,
auth : authReducer,
}),
initialState,
composeEnhancer(applyMiddleware(thunk))
)
export default store
even though their using the same initial state you , when you will connect a component to the redux store you have access to three different isLoading :
export default connect((state)=>({
isLoading : state.products.isLoading,
isLoading2: state.authReducer.isLoading,
isLoading3: state.cart.isLoading,
}))(Products)
but to be honest I'd rather have make my actions more explicit and case specific something like productsFetchIsLoading , this gives you more control and prevents bugs
I noticed now that when I do dispatch, the redux state that get updated is not belonged to the target reducer. The state update is happening at different reducer.
Every action gets dispatched to every reducer. When you call dispatch({ type: SET_LOADER, payload: true }), the expected behavior is that the isLoading state will get set to true in every reducer which has a case SET_LOADER.
If you want the loading states to be independent then each reducer needs a unique string action type.
If you have multiple similar reducers then you can use a factory function to generate the type names, action creator functions, and reducer cases. Here we are extending the createSlice utility from Redux Toolkit.
We pass in the name which is the prefix for the auto-generated action types, the initialState of just the unique properties for this reducer state, and any unique reducer cases. This will get merged with the standard base state.
Helper:
const createCustomSlice = ({name, initialState = {}, reducers = {}}) => {
return createSlice({
name,
initialState: {
formErr: {},
isLoading: false
...initialState,
},
reducers: {
setLoader: (state, action) => {
state.isLoading = action.payload;
},
setFormErr: (state, action) => {
state.formErr = action.payload;
}
...reducers,
}
});
}
Usage:
const profileSlice = createCustomSlice({
name: "profile",
initialState: {
username: ""
},
reducers: {
setUsername: (state, action) => {
state.username = action.payload;
}
}
});
// reducer function
const profileReducer = profileSlice.reducer;
// action creator functions
export const { setFormErr, setLoader, setUsername } = profileSlice.actions;
These action creators will create actions with a prefixed type like 'profile/setLoader'.
Is it possible to create multiple redux-forms reducers?
for example I would like the application state to look like this:
activities: {
login: {
form: {
// ... all login form data
}
// ... more login data
},
customer: {
form: {
// ... all customer form data
}
// ... more customer data
}
}
so basically, is it possible to connect a form component to a specific reducer or always works with a single reducer and only the form names are dynamic?
I think it's possible but in such case you have to tell given redux-form decorator where corresponding reducer was mounted. There is a getFormState config property in reduxForm which expects function that according to docs:
..takes the entire Redux state and returns the state
slice which corresponds to where the redux-form reducer was mounted.
This functionality is rarely needed, and defaults to assuming that the
reducer is mounted under the form key
So since you can define reducer for given form you can use multiple reducers.
For more details check redux-form reducer and reduxForm decorator,
USE combineReducers or this pattern :
const recipeReducers = (recipes , action) =>{ //recipes --- state.recipes
switch (action.type) {
case action_type.ADD_RECIPE:{
return recipes.concat({name: action.name})
};
default:
return recipes
}
}
const ingredientsReducer = (ingredients, action) =>{
switch (action.type) {
case action_type.ADD_INGREDIENT:{
const newIngredient = {
name: action.name,
recipe: action.recipe,
quantity: action.quantity
};
return ingredients.concat(newIngredient)
};
default:
return ingredients
}
}
const reducer = (state={}, action) => {
return Object.assign({}, state, {
recipes: recipeReducers(state.recipes, action),
ingredients: ingredientsReducer(state.ingredients, action)
});
};
const initialState = {
recipes: [
{
name: 'Omelette'
}
],
ingredients: [
{
recipe: 'Omelette',
name: 'Egg',
quantity: 2
}
]
};
function configureStore(initialState = {}) {
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk)
)
return store;
};
window.store = configureStore(initialState);
store.subscribe(() => console.log(store.getState()))
I am missing a key step/concept in how asyn calls work in react-redux.
Currently, I am able to make api calls (can also see the data being returned via chrome-dev-tools) BUT the response data isn't
reflected in the application state; meaning the state of the quiz object, which by default is an empty object, doesn't get updated.
My expectation is that when the asyn call resolves, I parse through the response in the quizReducer, and return a new state (not a mutated state) that reflects the response data.
Yet, each time the call is made, the updated state returns an empty quiz object (which is the initial state).
I know I am missing something but I can't figure out what; would really appreciate some pointers/explanation. Thank you
The app had an initialState/Preloaded state that looks like this:
export default {
quizzes: [
{id: 0, name: "quiz_name2", completed: false, selected: false},
{id: 1, name: "quiz_name2", completed: false, selected: false}
],
quiz: { questions: {} }
};
Setup for the reducer in question:
import initialState from './initialState';
import quizConstants from '../constants/quizConstants';
const actionTypes = quizConstants.actions
// when app loads, user has not selected any quiz therefore
// initialState is an empty object
// here action is the payload we get when a quiz is selected (see QUIZ_SELETED action creator)
export default function quiz(state=initialState.quiz, action) {
switch(action.type){
// when a quiz is selected, return new state
case actionTypes.SELECT_QUIZ:
return Object.assign(
{},
state,
{
id: action.quiz.id,
name: action.quiz.name,
completed: action.quiz.completed,
selected: !action.quiz.selected,
fetching: action.quiz.fetching,
fetched: action.quiz.fetched,
questions: action.quiz.questions
}
)
case actionTypes.REQUEST_QUIZ:
return Object.assign(
{},
state,
{
id: action.quiz.id,
name: action.quiz.name,
completed: action.quiz.completed,
selected: !action.quiz.selected,
fetching: !action.quiz.fetching,
fetched: action.quiz.fetched,
questions: action.quiz.questions
}
)
case actionTypes.RECEIVE_QUIZ:
return Object.assign(
{},
state,
{
id: action.quiz.id,
name: action.quiz.name,
completed: action.quiz.completed,
selected: !action.quiz.selected,
fetching: action.quiz.fetching,
fetched: !action.quiz.fetched,
quiz: action.quiz.questions
}
)
default:
return state
}
};
index.js (rootreducer):
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import quizzes from './quizzesReducer'
import quiz from './quizReducer';
export default combineReducers({
quizzes,
quiz,
routing: routerReducer
});
QuizActionCreators
import quizConstants from '../constants/quizConstants';
import { quizzesEndPoint } from '../constants/appConstants';
import axios from 'axios';
const actionTypes = quizConstants.actions
// select a quiz
export const selectQuiz = (quiz) => {
return {
type: actionTypes.SELECT_QUIZ,
quiz
}
};
const receiveQuiz = (quiz, data) => {
return {
type: actionTypes.RECEIVE_QUIZ,
quiz,
data
}
};
// call when componentWillMount
export const fetchQuiz = (quiz) => {
console.log("Make an api request here")
const url = quizzesEndPoint.concat(quiz.name)
axios.get(url)
.then(response => response.data)
.then(data => receiveQuiz(quiz, data))
}
export default { selectQuiz, fetchQuiz};
In your QuizActionCreator, your fetchQuiz is calling receiveQuiz and passing quiz and data as your parameter which the latter has the data from the server. I don't see any part in your reducers where you are setting the action.data to the state.
Try adding handler for RECEIVE_QUIZ inside your quizzesReducer and return the action.data to the state.
//quizzesReducer.js
export default function (state = initialState.quizzes, action){
...
if (action.type === actionTypes.RECEIVE_QUIZ) {
return action.data
}
...
};