How to create multiple reducers with multiple modules? - javascript

I still don't have much experience with Redux.
But I have a situation at work where we will have an application with several modules.
Each module will have its own store.
I'm not able to merge all these stores into one store and then create a general store for all the project.
Could you tell me how can i do this?
import { combineReducers, createStore } from "redux";
const rootReducer1 = combineReducers({
reducerA: reducerA,
reducerB: reducerB,
reducerC: reducerC
});
export const store1 = createStore(rootReducer1);
/*****/
import { combineReducers, createStore } from "redux";
const rootReducer2 = combineReducers({
reducerD: reducerD,
reducerE: reducerE,
reducerF: reducerF
});
export const store2 = createStore(rootReducer2);
/*****/
import { combineReducers, createStore } from "redux";
import store1 from "../modules/module1/reducers";
import store2 from "../modules/module2/reducers";
const rootReducer = combineReducers({
store1: store1,
store2: store2
});
export const store = createStore(rootReducer);
/*****/
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider as ReduxProvider } from "react-redux";
import { store } from "./store";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<ReduxProvider store={store}>
<App />
</ReduxProvider>
</StrictMode>,
rootElement
);
I created a test project in codesandbox.io just to demonstrate the idea of what the project will be like.

combineReducers(reducers) helper function turns an object whose values are different reducing functions into a single reducing function. It can't combine multiple stores.
Besides, the best practice is Only One Redux Store Per App
A standard Redux application should only have a single Redux store instance, which will be used by the whole application
You can have each module act as a first-level slice on the redux state tree like this:
store.js:
// module 1
const rootReducer1 = combineReducers({
reducerA: reducerA,
reducerB: reducerB,
reducerC: reducerC
});
// module 2
const rootReducer2 = combineReducers({
reducerD: reducerD,
reducerE: reducerE,
reducerF: reducerF
});
// App
const rootReducer = combineReducers({
module1: rootReducer1,
module2: rootReducer2
});
export const store = createStore(rootReducer);
The structure of the redux state tree will be like this:
{
module1: {
reducerA: {},
reducerB: {},
reducerC: {}
},
module2: {
reducerD: {},
reducerE: {},
reducerF: {}
},
}
If the module becomes large in the future, you can split it into a separate application with its own redux store.

Related

Cant access store with mapStateToProps

Im new to react, I'm trying to setup the login system with redux. In my Login component I'm using mapStateToProps with the connect method that react-redux offers.
When I tried to get what I needed from the store It kept saying that it was undefined. This is a snippet of my Login component:
function mapStateToProps(state) {
return {
loggingIn: state.authentication.loggedIn,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(userActions, dispatch),
alertActions: bindActionCreators(alertActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Login)
Here's how I tried to combine reducers:
import { combineReducers } from 'redux';
import authentication from './userReducer';
import alert from './alertReducer'
const rootReducer = () => combineReducers({
authentication,
alert
});
export default rootReducer;
However I couldn't access the logginIn props in the Login component. After trouble shooting for some frustrating hours I got it to work by removing the arrow function to this:
const rootReducer = combineReducers({
Can someone tell me why the arrow function didn't work? Thanks
Update: Here's how I imported the root reducer in the index.js file
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
import rootReducer from './reducers'
const store = createStore(rootReducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'))
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();
The docs tells you to call combineReducers to provide the root reducer.
The createStore method expects a reducer, not a function to call to get this reducer.
In addition to Vinicius' answer, you can refactor your combineReducers() call even more.
Instead of:
const rootReducer = () => combineReducers({
authentication,
alert
});
export default rootReducer;
which I have seen others do before, I guess personally the less code I need to write the cleaner, as long as it doesn't become esoteric looking, anyway just write it like this:
export default combineReducers({
authentication,
alert
});
You export and call the combineReducers() all in one go. Oh and to import it, since we removed rootReducer just do:
import reducers from "./reducers";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducers,
composeEnhancers(applyMiddleware(reduxThunk))
);

Uncaught TypeError: Providing your root Epic to createEpicMiddleware(rootEpic)

I am getting this error
Uncaught TypeError: Providing your root Epic to
createEpicMiddleware(rootEpic) is no longer supported, instead use
epicMiddleware.run(rootEpic)
When simply using
import 'rxjs'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { reducer as formReducer } from 'redux-form'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import { createEpicMiddleware, combineEpics } from 'redux-observable'
import app from './app'
// Bundling Epics
const rootEpic = combineEpics(
)
// Creating Bundled Epic
const epicMiddleware = createEpicMiddleware(rootEpic)
// Define Middleware
const middleware = [
thunk,
promise(),
epicMiddleware
]
// Define Reducers
const reducers = combineReducers({
form: formReducer
})
// Create Store
export default createStore(reducers,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), applyMiddleware(...middleware))
Kindly help to resolve this
First, take a look at this document: Official Redux-Observable Document because we're using the newest version of Redux-Observable, then reviewing its document is quite helpful.
After reviewing the document, let's see a small example project (Counter app):
This is the root.js file which contains my Epics and Reducers after bundling.
// This is a sample reducers and epics for a Counter app.
import { combineEpics } from 'redux-observable';
import { combineReducers } from 'redux';
import counter, {
setCounterEpic,
incrementEpic,
decrementEpic
} from './reducers/counter';
// bundling Epics
export const rootEpic = combineEpics(
setCounterEpic,
incrementEpic,
decrementEpic
);
// bundling Reducers
export const rootReducer = combineReducers({
counter
});
And this is store.js where I define my store before using it.
import { createStore, applyMiddleware } from 'redux';
import { createEpicMiddleware } from 'redux-observable';
import { rootEpic, rootReducer } from './root';
import { composeWithDevTools } from 'redux-devtools-extension';
const epicMiddleware = createEpicMiddleware();
const middlewares = [
epicMiddleware
]
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(middlewares))
);
epicMiddleware.run(rootEpic);
export default store;
In order to successfully implement redux-observable, we have to obey this order:
Creating epicMiddleware using createEpicMiddleware() method
Using the applyMiddleware() method to register the epicMiddleware (the redux-devtools-extension is optional)
Calling the epicMiddleware.run() with the rootEpic we created earlier.
This is the instruction from the Redux-Observable Document
For more information, you could find it here: : Setting Up The Middleware:
Try this
import 'rxjs'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { reducer as formReducer } from 'redux-form'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import { createEpicMiddleware, combineEpics } from 'redux-observable'
import app from './app'
// Bundling Epics
const rootEpic = combineEpics(
)
// Creating Bundled Epic
const epicMiddleware = createEpicMiddleware();
// Define Middleware
const middleware = [
thunk,
promise(),
epicMiddleware
]
// Define Reducers
const reducers = combineReducers({
form: formReducer
})
// Create Store
const store = createStore(reducers,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), applyMiddleware(...middleware))
epicMiddleware.run(rootEpic);
export default store
official documentation createEpicMiddleware.
Well, have you tried:
import { epicMiddleware, combineEpics } from 'redux-observable'
const epicMiddleware = epicMiddleware.run(rootEpic)
?

Is this the correct way to implement middleware in redux?

I am attempting to debug a redux store for async actions. But I am failing to pass dispatch as a function so I will be posting series of questions to help myself find my issue. The first thing I need to ensure is that I am applying redux-thunk properly. So is this the correct way to implement redux middleware?
import { createStore,applyMiddleware,combineReducers,compose } from 'redux';
import thunk from 'redux-thunk';
import {createLogger} from 'redux-logger';
import {inventoryFilter,availableAttributes} from '../reducers/reducer';
const logger=createLogger()
const Store = createStore(
///combine imported reducers
combineReducers({
activeFilter:inventoryFilter,
availableAttributes:availableAttributes
},{},applyMiddleware(thunk,logger)
));
export default Store;
No. You're passing the middleware enhancer as an argument to combineReducers, when it should actually be an argument to createStore.
Here's how I would write it:
import { createStore,applyMiddleware,combineReducers,compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import {createLogger} from 'redux-logger';
import {inventoryFilter,availableAttributes} from '../reducers/reducer';
const rootReducer = combineReducers({
activeFilter:inventoryFilter,
availableAttributes:availableAttributes
});
const loggerMiddleware = createLogger();
const middlewareEnhancer = applyMiddleware(thunkMiddleware, loggerMiddleware);
const store = createStore(rootReducer, middlewareEnhancer);
export default store;

React-Redux - No reducer provided for key "coins"

Not sure why I'm getting the following errors.
I'm just setting up my store, actions and reducers, I haven't called dispatch on anything yet.
Expected
App runs fine, Redux state is not updated
Results
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware, compose } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import reducer from './reducer'
import App from './App'
import css from './coinhover.scss'
const element = document.getElementById('coinhover');
const store = createStore(reducer, compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>, element);
src/reducer/index.js
import { combineReducers } from 'redux'
import { coins } from './coins'
export default combineReducers({
coins
});
src/reducer/actions/coins.js
import * as api from '../../services/api'
import { storage, addToPortfolio } from '../../services/coinFactory'
export const ADD_COIN = 'ADD_COIN'
export function getCoin(coin) {
return dispatch => {
api.getCoin(coin)
.then((res_coin) => addToPortfolio(res_coin))
.then((portfolio) => dispatch(updatePortfolio(portfolio)));
}
}
export function updatePortfolio(portfolio) {
return {
type: ADD_COIN,
portfolio
}
}
finally src/reducer/coins/index.js
import { ADD_COIN } from './actions'
const initialState = [];
export default (state = initialState, action) => {
switch(action.type) {
case ADD_COIN:
return action.portfolio;
default:
return state;
}
}
Your issue lies with how you're importing your coins reducer:
import { coins } from './coins'
The latter tries to obtain a named export returned from the file in ./coins.
You are not using any named exports only export default, therefore you just need to import the file as follows:
import coins from './coins';
Using the latter will result with the fact that coins will then contain the value of export default; which will be the coins reducer.
Even when all your imports are correctly imported, this can still happen for one other reason. Circular Dependency!
In my case, this happeded because of a circular dependency in a file. I had two circular dependecy in the project that I created accedentally. Example: rootReducer.ts -> authSlice.ts -> rootReducer.ts.
These dependencies are often not as easy to debug. I used this package to check for circular dependencies. Once the circular dependency was removed, all was well.
Ah just found it, I was importing my coins reducer incorrectly...
import { combineReducers } from 'redux'
import coins from './coins' // because I have coins/index.js
export default combineReducers({
coins
});
instead of
import { coins } from './coins'
This was my fix:
import { combineReducers } from 'redux'
import { coins } from './coins'
export default combineReducers({
coinsState: coins || (() => null) // By adding this I resolved it.
});
If nothing worked for you this might be a circular dependency. I had a project like that requiring store state in the slice. Instead of using the store there was a thunk available that can provide you with the state without importing the store itself. If you have this edge case you can get state from thunk.
thunkAPI.getState()
In my case, I was not adding a default property to my reducers. When I added it, it worked.
here is my code;
const counterReducer = (state = 0, action) => {
switch(action.type){
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
export default counterReducer;
and combinde file;
import counter from './counter';
import { combineReducers } from 'redux';
const allReducers = combineReducers({
counter: counter,
});
export default allReducers;

Mixing two stores into single, common one - ReactJS

I have my app (a boilterplate with few, my own features). It has a global store (build-in, came from the boilerplate), which looks like:
import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; //combine reducers from redux form
import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
import { reducer as reduxFormReducer } from 'redux-form'; //registration form
const sagaMiddleware = createSagaMiddleware();
export default function configureStore(initialState = {}, history) {
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
];
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
/* eslint-enable */
const store = createStore(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers)
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.asyncReducers = {}; // Async reducer registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
import('./reducers').then((reducerModule) => {
const createReducers = reducerModule.default;
const nextReducers = createReducers(store.asyncReducers);
store.replaceReducer(nextReducers);
});
});
}
return store;
}
Few days ago I've implemented a redux-form (it works great), but unfortunately it has an own, local store, which is not compatibile with the global one and looks like:
import { createStore, combineReducers } from 'redux';
import { reducer as reduxFormReducer } from 'redux-form';
const reducer = combineReducers({
form: reduxFormReducer
});
const store = (window.devToolsExtension
? window.devToolsExtension()(createStore)
: createStore)(reducer);
export default store;
As far as I know, the store has to be global - the first one is, but the second one (for redux form) isn't.
I would like to ask you
How to mix these two stores into a single, common, global one?
Thank you for any answers!
Edit: Redux form comes from: https://codesandbox.io/s/qx95rm7gG
React redux comes from: https://github.com/react-boilerplate/react-boilerplate
Edit2: Files hierarchy:
-app
--index.js
--store.js (first one, global)
--containers
---UserRegistration
----index.js
----store.js (the second one, local)
but unfortunately it has an own, local store, which is not compatibile with the global one and looks like
This is not correct. The redux-form reducer is a standard reducer like any other and just needs to be combined into your reducer (from import createReducer from './reducers';).
If you are already combining reducers in createReducer (I assume you are because of the store.asyncReducers in the hot reloading), then you just need to include the reduxFormReducer with form as the key, something like:
import { combineReducers } from 'redux';
import { reducer as reduxFormReducer } from 'redux-form';
import someReducer from './someReducer';
import someOtherReducer from './someOtherReducer';
export default function createReducer(asyncReducers = {}) {
return combineReducers({
form: reduxFormReducer,
someReducer,
someOtherReducer,
...asyncReducers
});
}
If you share ./reducers/index.js I can make this answer more specific.

Categories