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()))
Related
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'.
For a react application, I'm using Redux to fetch data from an API. In this application, there exists a component that is displayed twice on the same page. The component is connected to an action and reducer.
Both instances of the component should display different data: one displays someone's job and the other displays someone's phone number. Both of these things are requested in separate API calls, causing the problem that the data of the second call, overwrites the data obtained in the first call in the reducer connected to the component. How would it be possible to make two API calls for such a component that is shown twice, such that both instances of it show the data of either one of these api calls?
I tried the following: make one request and fill the reducer. Then make the other request and merge the results of both in the reducer. However, the problem with this approach is that it is also possible to display only one of the components in the application.
This is the react component:
class Display extends React.Component {
componentWillMount() {
const { fetchpayload } = this.props;
fetchpayload(this.props.parameter);
}
render() {
return (
<h1>{this.props.payload}</h1>
);
}
}
const mapStateToProps = (state) => ({
payload: state.DisplayReducer.payload,
});
const mapDispatchToProps = (dispatch) => bindActionCreators(
{
fetchpayload: payloadAction,
},
dispatch,
);
This is the reducer
const initialState = {
payload: [],
};
export function DisplayReducer(state = initialState, action) {
switch (action.type) {
case 'FETCHED_DISPLAY':
return {
...state,
payload: action.payload,
};
return state;
}
}
The action file makes the request, and dispatches 'FETCHED_DISPLAY'.
just don't override your stuff inside reducer
const InitialState = {
person: {
name: '',
job: ''
}
};
export default (state = InitialState, action) => {
switch(action.type) {
case NAME:
return {
...state.person,
person: {
job: ...state.person.job,
name: action.payload
}
}
case JOB:
return {
...state.person,
person: {
name: ...state.person.name,
job: action.payload
}
}
// do that with whatever property of object person you would want to update/fetch
}
}
}
Hope this helps.
I'm using the Redux Thunk example template. When I dispatch an action in getInitialProps, that populates my store, the data is loaded but after the page is rendered, the store is still empty.
static async getInitialProps({ reduxStore }) {
await reduxStore.dispatch(fetchCategories())
const categories = reduxStore.getState().programm.categories;
console.log('STATE!!!', categories)
return { categories }
}
The categories will load correctly but when I inspect my store, the categories state is empty.
Here is my store:
import db from '../../api/db'
// TYPES
export const actionTypes = {
FETCH_PROGRAMMS: 'FETCH_PROGRAMMS',
FETCH_CATEGORIES: 'FETCH_CATEGORIES'
}
// ACTIONS
export const fetchCategories = () => async dispatch => {
const categories = await db.fetchCategories();
console.log('loaded Cate', categories)
return dispatch({
type: actionTypes.FETCH_CATEGORIES,
payload: categories
})
}
// REDUCERS
const initialState = {
programms: [],
categories: []
}
export const programmReducers = (state = initialState, action) => {
switch (action.type) {
case actionTypes.FETCH_PROGRAMMS:
return Object.assign({}, state, {
programms: action.payload
})
case actionTypes.FETCH_CATEGORIES:
console.log('Payload!', action);
return Object.assign({}, state, {
categories: action.payload
})
default:
return state
}
}
How can I make the redux state loaded on the server (in getInitialProps) be carried over to the client?
After hours of searching for solution it seems like I found my problem. It seems like I need to pass an initialState when creating the store. So instead of this:
export function initializeStore() {
return createStore(
rootReducers,
composeWithDevTools(applyMiddleware(...middleware))
)
}
I'm doing this and it works now
const exampleInitialState = {}
export function initializeStore(initialState = exampleInitialState) {
return createStore(
rootReducers,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
)
}
If you do this:
return { categories }
in getInitialProps, categories should be available in component's props in client side.
It should be available in Redux as well, this could cause the problem:
return Object.assign({}, state, {
categories: action.payload
})
Take a look at this Object.assign, the function only takes 2 parameters.
My normal way of doing this:
return {
...state,
categories: action.payload,
};
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'}));
I'm quite new to Redux and from what I understand, a reducer should be created for each type of object. E.g. for user interaction a user reducer should be created. My question is: How do you handle cases where you require the object for different purposes?
Scenario: Imagine having a user reducer which returns the current user. This user would be required in the entire application and needed for general controls on every page.
Now what happens when you need to load another user which is used for different purposes. E.g. profile page: loading a user to display information.
In this case there would be a conflict if the user reducer would be used. What would be the correct way to handle this in Redux? In case a different reducer would have to be created, what would be the naming convention for the new reducer?
First, you've mentioned:
a user reducer which loads the current user
I don't know if I got you correctly, but if this means you want to fetch (from an API, for example) the current user inside the reducer, this is a wrong approach.
Reducers are intended to be pure functions. You can call them with the same arguments multiple times and they will always return the same expected state.
Side effects like that should be handled by action creators, for example:
actions/user.js
export const FETCH_ME = 'FETCH_ME'
export const FETCH_ME_SUCCESS = 'FETCH_ME_SUCCESS'
// it's using redux-thunk (withExtraArgument: api) module to make an async action creator
export const fetchMe = () => (dispatch, getState, api) => {
dispatch({ type: FETCH_ME })
return api.get('/users/me').then(({ data }) => {
dispatch({ type: FETCH_ME_SUCCESS, data })
return data
})
}
Inside your reducer you can simple get the data and set a new state (note that if you send the action with the same data multiple times, the state will always be the same).
reducers/user.js
import { FETCH_ME, FETCH_ME_SUCCESS } from '../actions/user'
const initialState = {
item: null,
loading: false
}
export const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ME:
return {
...state,
loading: true
}
case FETCH_ME_SUCCESS:
return {
...state,
loading: false,
item: action.data
}
default:
return state
}
}
Now, for your scenario:
Now what happens when you need to load another user which is used for different purposes. E.g. profile page: loading a user to display information.
You will just write another action creator for that:
actions/user.js
export const FETCH_ME = 'FETCH_ME'
export const FETCH_ME_SUCCESS = 'FETCH_ME_SUCCESS'
export const FETCH_USER = 'FETCH_USER'
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS'
export const fetchMe = () => (dispatch, getState, api) => {
dispatch({ type: FETCH_ME })
return api.get('/users/me').then(({ data }) => {
dispatch({ type: FETCH_ME_SUCCESS, data })
return data
})
}
export const fetchUser = (id) => (dispatch, getState, api) => {
dispatch({ type: FETCH_USER })
return api.get(`/users/${id}`).then(({ data }) => {
dispatch({ type: FETCH_USER_SUCCESS, data })
return data
})
}
Then you adapt your reducer to manage more sets:
reducers/user.js
import { combineReducers } from 'redux'
import { FETCH_ME, FETCH_ME_SUCCESS, FETCH_USER, FETCH_USER_SUCCESS } from '../actions/user'
const initialState = {
item: null,
loading: false
}
const meReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ME:
case FETCH_ME_SUCCESS:
return userReducer(state, action)
default:
return state
}
}
const activeReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER:
case FETCH_USER_SUCCESS:
return userReducer(state, action)
default:
return state
}
}
const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER:
case FETCH_ME:
return {
...state,
loading: true
}
case FETCH_USER_SUCCESS:
case FETCH_ME_SUCCESS:
return {
...state,
loading: false,
item: action.data
}
default:
return state
}
}
export default combineReducers({
activeUser: activeReducer,
me: meReducer
})
Your final user state should be something like:
{
me: {
item: null,
loading: false
},
active: {
item: null,
loading: false
}
}