I have two reducers, the first one fetches data and the second one is used to filter the data based on changes to the original data. I have read that using combineReducers is not an option.
My postReducer
import {FETCH_POST} from '../actions/types';
const initialState = {
items: [],
item: {}
};
export default function(state = initialState, action){
switch (action.type){
case FETCH_POST:
return Object.assign({}, state, {items: action.payload});
default:
return state;
}
}
My pinReducer
import {PIN_POST} from '../actions/types';
const initialState = {
items: [],
item: {}
};
export default function(state = initialState, action){
switch (action.type){
case PIN_POST:
console.log(state.items);
const item = state.items.map(value => value.data.id === action.id ?
{data: Object.assign({}, value.data, {pinned: action.val})} : value
);
//console.log(item);
return Object.assign({}, state, {items: item });
default:
return state;
}
}
Main
import {combineReducers} from 'redux';
import postReducer from './postReducer';
import pinReducer from './pinReducer';
export default combineReducers({
post: postReducer,
pin: pinReducer
});
How can I share the state between the two reducers, as state.items in the pinReducer is empty
You should not have a reducers that has state derived on the basis of other reducers. What you need in your case is a selector(more efficiently a memoizedSelector), for which you can use reselect library
My postReducer
import {FETCH_POST} from '../actions/types';
const initialState = {
items: [],
item: {}
};
export default function(state = initialState, action){
switch (action.type){
case FETCH_POST:
return Object.assign({}, state, {items: action.payload});
default:
return state;
}
}
Main
import {combineReducers} from 'redux';
import postReducer from './postReducer';
export default combineReducers({
post: postReducer,
});
and then where you want to use pinItem, you can do that in mapStateToProps
getPinItem = (state, props) => {
const item = state.items.map(value => value.data.id === action.id ?
{data: Object.assign({}, value.data, {pinned: action.val})} : value
);
return item;
}
const mapStateToProps = (state, props) => {
const pinItem = getPinItem(state, props);
return {
pinItem
}
}
Reducer does not access to store's state, nor other reducers' previous state. Only to its own previous state and dispatched action. You can think of it as a single brick in the wall of redux' state.
In your case, we have to tackle the logic and decide what is an actual state here.
Requirements:
fetch a list of posts: state is an array of posts;
pin one of these posts: state is an id of currently pinned post;
get currently pinned post: find a post in an array by pinned post id.
Solution:
1 - initial posts reducer:
const initialState = { items: [] }
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_POSTS:
return { items: action.payload }
default:
return state
}
}
2 - add info about pinned post:
const initialState = { items: [], pinnedId: null }
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_POSTS:
return { ...state, items: action.payload.posts }
case PIN_POST:
return { ...state, pinnedId: action.payload.id }
default:
return state
}
}
3 - get a currently pinned post:
// assuming that store is configured...
export default combineReducers({ posts: postsReducer })
// ...
// somewhere in `connect`
const mapStateToProps = (state, ownProps) => {
return {
pinnedPost: state.posts.find(post => post.id === state.posts.pinnedId),
}
}
So now we store a minimum information about a pinned post (only its id) and it is enough to get an actual pinned post.
Also, as mentioned in another answer by #shubham-khatri, you can use optimized selectors. With current shape of the state storing pinned id separately, it is even more efficient since your selector will depend on two inputs: posts array (is re-created on fetch, so shallow comparable) and pinned id (is a primitive number or string, also easy to compare).
I'd encourage you to store as minimum information in store as possible. Also, store it in simple and flat shape. Move everything that's can be calculated to selectors.
Related
I have a product filter array which i stored in redux, initially its an empty array and i want to dispatching an object in it one by one .
The issue is i want to check if the object_id is already is in the array then i don't want to dispatch it and as well as delete that particular object in redux state.
Likewise,
if user added blue color filter so its accept but if user again added blue color filter so its shouldn't dispatch this object and also remove this object in redux.
reducer:
filterd :[]
const filterreducer = (state = initialState, action) => {
switch (action.type) {
case ADD_FILTER:
return {
...state,
filterd :[...state.filterd,action.payload]
};
default:
return state;
}
}
export default filterreducer ;
initialState ={
filterd :[]
}
const filterreducer = (state = initialState, action) => { switch (action.type) {
case ADD_FILTER:
return {
...state,
filterd :action.paylod
};
default:
return state;
}
}
export default filterreducer ;
you have to store response data in payload while dispatching that action
such as:
dispatch({
type:ADD_FILTER,
payload: response.data
)
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'.
I have 2 reducers:
const initialState = {
data:[],
};
export const first = (state = initialState, action) => {
switch (action.type) {
case DATA: {
return {
...state,
data: action.payload
}
}
}
return state;
};
and second:
const initialState = {
data2:[],
};
export const second = (state = initialState, action) => {
switch (action.type) {
case DATA2: {
return {
...state,
data2: action.payload
}
}
}
return state;
};
I decided tu use combineReducers to merge them. Fot this, i used:
const rootReducer = combineReducers({first, second});
My store:
import rootReducer from "./reducer/rootReducer";
export const store = createStore(rootReducer);
console.log(store.getState())
When i check the state of the store with store.getState(), i get an object something like this:
{first: {…}, second: {…}},
but i need to use destructuring of these 2 objects inside combine reducers.
For this i made:
const rootReducer = combineReducers({...first, ...second});but now, i got an emty object, plus this error: Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
Question: Why i can't destructure the reducers like i wrote above? How to do this?
The reason you cannot destructure the reducers like above is because they are functions and not objects
If you carefully look you are creating an object for combineReducers like
combineReducers({
first: (state, action) => {...},
second: (state, action) => { ...}
})
Now combineReducer returns you a single function by combining the reducers which you can pass to createStore,
You can destructure the individual state post store.getState()
const {first, second} = store.getState();
const data = {...first, ...second};
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,
};
Currently I have to call three different api end points to get the information for some dropdown in a redux form.
The question I have is atm to get the end points into separate sets of state. I currently have three different reduces being imported into the index reducer so I can have them under three state terms. I have nearly the same code in three files:
//reducer/job-board/job_tools
import {FETCH_JOB_TOOLS} from "../../actions/job_list_actions";
export default function(state = {}, action) {
switch(action.type) {
case FETCH_JOB_TOOLS:
return action.payload.data;
default:
return state;
}
}
////reducer/job-board/job_roles
import {FETCH_JOB_ROLES} from "../../actions/job_list_actions";
export default function(state = {}, action) {
switch(action.type) {
case FETCH_JOB_ROLES:
return action.payload.data;
default:
return state;
}
}
Seeing as this code is nearly identical is there a way for me to dry this up and still keep three separate pieces of s
Pleas help.
Thank you.
You can do a FETCH_JOB action and pass job_type from action parameter. So you'll have one unique fetch function in reducer and you'll provide what you're fetching, this should be the key in state for what you're fetching.
store = { roles: [], tools: [] }
your reducer will looks like:
import {FETCH_JOB} from "../../actions/job_list";
export default function(state = {}, action) {
switch(action.type) {
case FETCH_JOB:
return {
...state,
[action.job_type]: action.payload.data
}
default:
return {...state};
}
}
note: in this case, it's important to generate a new state with { ...state, [...] } instead of just mutate the previous one.
By the way, I am using an array like syntax instead of switch case.
you can use a function to create reducers and provide array containing handlers.
import createReducer from './createReducer';
const loginReceived = (state, action) => ({
...state,
authData: action.auth
});
// [...]
const ACTION_HANDLERS = {
[AUTH_LOGIN_START]: loginStarted,
[AUTH_LOGIN_COMPLETE]: loginReceived,
[AUTH_LOGIN_FAIL]: loginFail
};
export default createReducer(initialState, ACTION_HANDLERS);
and use this as createReducer function
/**
* Creates a reducer.
* #param {string} initialState - The initial state for this reducer.
* #param {object} handlers - Keys are action types (strings), values are reducers (functions).
* #return {object} A reducer object.
*/
export default (initialState = null, handlers = {}) => (state = initialState, action) => {
if (!action && !action.type) return state;
const handler = handlers[action.type];
return handler && handler(state, action) || state;
};