Redux newbie here.
I have an reducer ProductListReducer that takes asynchronous action (its action creator fetch data from API), whose initial state is InitialProductListStates. I have another synchronous reducer CartReducer, whose initial state is InitialCartListStates. I want to have them work on one single store store. How should I do this?
My attempt: I want to first combine the two reducers into one reducer with combineReducers, and then combine their initial states as InitialState = {ProductListReducer: InitialProductListStates, CartReducer:InitialCartListStates}. Next, I want to use createStore():
store = createStore(reducer, InitialState , applyMiddleware(thunk));
However, I am stuck at the first test-of-concept: After combining ProductListReducer with itself and setting InitialState={ProductListReducer: InitialProductListStates} , I re-run the app and get this error:
Uncaught Error: Minified Redux error #12; visit https://redux.js.org/Errors?code=12 for the full message or use the non-minified dev environment for full errors.
at redux.js:441:13
at Array.forEach (<anonymous>)
at redux.js:434:25
at _l.ProductListReducer (redux.js:499:5)
at main.ts:4:17
at index.tsx:13:27
at index.tsx:13:27
My codes (that doesn't work):
import reducer from './reducers/main';
import { ProductListReducer, InitialProductListStates } from './reducers/reducers'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const InitialStates = {
ProductListReducer: InitialProductListStates
};
export const store = createStore(reducer, InitialStates, applyMiddleware(thunk));
My code (that did work):
import reducer from './reducers/main';
import { ProductListReducer, InitialProductListStates } from './reducers/reducers'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const InitialStates = {
ProductListReducer: InitialProductListStates
};
export const store = createStore(ProductListReducer, InitialProductListStates, applyMiddleware(thunk));
Related
I have two different reducers in my app => nodesReducer and controlsReducer
If I pass them to the store once at a time, they work perfectly fine but whenever I use combineReducers, they stop working => as in everything I pull from the state becomes undefined in my components but everything still looks normal in the toolkit extension.
This is the code.
This works totally fine (single reducer directly put there):
import { configureStore } from "#reduxjs/toolkit";
import nodesReducer from "./Node";
const store = configureStore({
reducer: nodesReducer,
middleware: [],
});
export default store;
And this one does not work when I add one more reducer with combineReducers:
import { configureStore } from "#reduxjs/toolkit";
import { combineReducers } from "redux";
import nodesReducer from "./Node";
import controlsReducer from "./Controls";
const rootReducer = combineReducers({
nodesReducer,
controlsReducer,
});
const store = configureStore({
reducer: rootReducer,
middleware: [],
});
export default store;
Can anyone see what is going wrong?
Bit of an educated guess here, but nothing seems obviously wrong in what you've provided, so given you've only changed the reducer code I'd imagine it's because by combining your reducers, your state will change from:
{
...stuffFromNodesReducer
}
Into
{
nodesReducer: ...stuffFromNodesReducer,
controlsReducer: ...stuffFromControlsReducer
}
You should be able to examine your redux state using redux devtools to check how what it looks like.
After adding rootReducer you should change your useSelectors from
useSelector(state => state.someData)
to
useSelector(state => state.nodesReducer.someData)
useSelector(state => state.controlsReducer.someData)
I have made a simple react app with the sample code from the following blogpost, which I leave only as a citation. I am otherwise new to the javascript ecosystem and am trying to fit together several unfamiliar tools (in order to learn them).
https://medium.com/#notrab/getting-started-with-create-react-app-redux-react-router-redux-thunk-d6a19259f71f
Relevantly, my store.js looks like this:
import { createStore, applyMiddleware, compose, } from 'redux';
import { connectRouter, routerMiddleware, } from 'connected-react-router';
import thunk from 'redux-thunk';
import { install, } from 'redux-loop';
import createHistory from 'history/createBrowserHistory';
import rootReducer from './modules';
export const history = createHistory();
const initialState = {};
const middleWare = [thunk, routerMiddleware(history),];
const composedEnhancers = compose(
applyMiddleware(...middleWare),
install()
);
const store = createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
);
export default store;
This seems to work fine, and the Link/Route triggers on my page work fine. Note that install from redux-loop is being called as part of an enhancer (?) and this is fine. I do not have any loop calls in my reducers, I just inserted the install command as an enhancer with the hope that I will be able to add some.
Here is my main reducer code:
import { combineReducers, } from 'redux';
import counter from './counter';
import todo from './todo';
export default combineReducers({
counter,
todo,
});
Again, this works great. However, if I insert loop anywhere in my reducers, it dies. According to the docs, this is because we need to use the combineReducers from redux-loop. Fine. If I replace the import at the top to import { combineReducers, } from 'redux-loop'; (not altering my reducers at all, there are no nonstandard returns yet) then I get some completely nonsensical errors in library code:
ConnectedRouter.js:58 Uncaught TypeError: Cannot read property 'pathname' of undefined
at ConnectedRouter.js:58
at Object.dispatch (redux.js:221)
at dispatch (install.js:66)
at middleware.js:25
at index.js:11
at Object.onLocationChanged (ConnectedRouter.js:154)
at handleLocationChange (ConnectedRouter.js:85)
at new ConnectedRouter (ConnectedRouter.js:94)
at constructClassInstance (react-dom.development.js:11769)
at updateClassComponent (react-dom.development.js:13491)
at beginWork (react-dom.development.js:14090)
at performUnitOfWork (react-dom.development.js:16416)
at workLoop (react-dom.development.js:16454)
at renderRoot (react-dom.development.js:16533)
at performWorkOnRoot (react-dom.development.js:17387)
at performWork (react-dom.development.js:17295)
at performSyncWork (react-dom.development.js:17267)
at requestWork (react-dom.development.js:17155)
at scheduleWork (react-dom.development.js:16949)
at scheduleRootUpdate (react-dom.development.js:17637)
at updateContainerAtExpirationTime (react-dom.development.js:17664)
at updateContainer (react-dom.development.js:17691)
at ReactRoot../node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render (react-dom.de...
It goes on for many pages, but the issue seems to be in ConnectedRouter; I assume this is because combineReducers in redux-loop changes the response type of the main reducer into something that's not compatible with the connectRouter(history)(rootReducer) in the createStore call.
Is this the right issue? Is this fixable? Can these two libraries be used together?
There's an open issue that would address this, but until that is done it requires a hack. I called combineReducers with something like this (I am using immutable js. but if you're not it's simple to convert to that)
import { connectRouter } from 'connected-react-router/immutable';
import { Map } from 'immutable';
//....
const routerReducer = connectRouter(history)(() => fromJS({}));
return combineReducers(
{
foo: fooReducer,
blah: blahReducer,
router: (state, action) => {
const routerStateWrapper = Map({router: state});
const result = routerReducer(routerStateWrapper, action);
return result.get('router');
}
}
)
It's already supported in v5+.
Release note: https://github.com/supasate/connected-react-router/releases/tag/v5.0.0
PR: https://github.com/supasate/connected-react-router/pull/150
I'm reading a code of a react project I found on GitHub and I found a different use of redux thunk, by seeing the official documentation at GitHub they use it this way:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
but in the code I found the guy used:
import { AsyncStorage } from 'react-native';
import { applyMiddleware, createStore } from 'redux';
import { autoRehydrate, persistStore } from 'redux-persist'
import thunk from 'redux-thunk';
import reducers from '../reducers';
const middleWare = [thunk];
const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore);
export default configureStore = (onComplete) => {
const store = autoRehydrate()(createStoreWithMiddleware)(reducers);
persistStore(store, { storage: AsyncStorage }, onComplete);
return store;
};
what is the difference between using it as a list or as an object?
Let's look at the function signature (source):
function applyMiddleware(...middlewares) { /* logic */ }
applyMiddleware uses the rest parameter syntax which allows representing an indefinite number of arguments as an array.
Because of this, both
applyMiddleware(thunk, otherMiddleware);
and
const middlewares = [thunk, otherMiddleware];
applyMiddleware(...middlewares);
are equally valid.
I have a function that check the role of a user. I want to base on that pass different reducer into a store, is that possible?
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { getUserRole } from './utils'
import { composeWithDevTools } from 'redux-devtools-extension'
import userReducers from './reducers'
import adminReducers from './reducers/admin'
//share reducer btw member and admin
let reducers
if (getUserRole() === 'member') {
reducers = userReducers
} else {
reducers = adminReducers
}
console.log('reducers', reducers) //undefined unless I refresh the page
const store = createStore(
reducers,
composeWithDevTools(
applyMiddleware(thunk)
)
)
export default store
I found a problem, reducers is undefined for the first time (like I login) unless I refresh it.
I can't understand why my function returnSlidesReducer() executes twice.
I'm using Redux.
My reducer file slides.js is (reads json file and returns data to a store):
import jsonFile from '../sliderContent.json';
const returnSlidesReducer = (slidesContent) => {
console.log(slidesContent);
return slidesContent;
}
returnSlidesReducer(jsonFile);
export default returnSlidesReducer;
And my index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {
createStore
} from 'redux';
//import allReducers from './reducers';
import SlidesReducer from './reducers/slides';
const store = createStore(SlidesReducer);
ReactDOM.render(
<App />,
document.getElementById('root')
);
In console I get:
{slider:Array(3)}
undefined
And because of this in a store I get undefined.
See the reducer from the example todo app in Redux doc:
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text])
default:
return state
}
}
let store = createStore(todos, ['Use Redux'])
You don't need to explicitly call your reducer function like you do.
If you want to use the json object as the initial state, you can pass it as the second argument to createStore(..)
createStore(returnSlidesReducer, jsonFile);
On a side note, your reducer function isn't of the standard redux reducer form. I recommend following the official Redux example.