update redux reducers after store initialization - redux

I am new to redux architecture, I have this basic doubt can we update the reducers list after creating the store using combinedReducer and createStore methods?

Yes, you can update the reducers and inject a new one asynchronously with replaceReducer api of Redux store.
It is an advanced API. You might need this if your app implements code
splitting, and you want to load some of the reducers dynamically. You
might also need this if you implement a hot reloading mechanism for
Redux.
Take as example this starter-kit
In createStore.js file the reducers passed as arguments to the createStore method are the result of makeRootReducers(). Pay attention to the fact that no one async reducer have been passed to this function.
// extract of src/store/createStore.js
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'react-router-redux'
import thunk from 'redux-thunk'
import makeRootReducer from './reducers'
export default (initialState = {}, history) => {
// ...
// ======================================================
// Store Instantiation and HMR Setup
// ======================================================
const store = createStore(
makeRootReducer(), // <------------- without arguments, it returns only the synchronously reducers
initialState,
compose(
applyMiddleware(...middleware),
...enhancers
)
)
store.asyncReducers = {}
// ...
}
In reducers.js file:
makeRootReducer function calls combineReducers with the default reducers
needed for the startup (like router reducer) and other "asynchronously" reducers passed as arguments
injectReducer is a function called for injecting new reducers on runtime. It call replaceReducer api on the store passing as argument a new list of reducers obtain through makeRootReducer(async) function
see below:
// src/store/reducers.js
import { combineReducers } from 'redux'
import { routerReducer as router } from 'react-router-redux'
export const makeRootReducer = (asyncReducers) => {
return combineReducers({
// Add sync reducers here
router,
...asyncReducers
})
}
export const injectReducer = (store, { key, reducer }) => {
store.asyncReducers[key] = reducer
store.replaceReducer(makeRootReducer(store.asyncReducers))
}
export default makeRootReducer
Finally, in the starter-kit the reducer is injected on route definition, like here:
// src/routes/Counter/index.js
import { injectReducer } from '../../store/reducers'
export default (store) => ({
path: 'counter',
/* Async getComponent is only invoked when route matches */
getComponent (nextState, cb) {
/* Webpack - use 'require.ensure' to create a split point
and embed an async module loader (jsonp) when bundling */
require.ensure([], (require) => {
/* Webpack - use require callback to define
dependencies for bundling */
const Counter = require('./containers/CounterContainer').default
const reducer = require('./modules/counter').default
/* ----> HERE <---- */
/* Add the reducer to the store on key 'counter' */
injectReducer(store, { key: 'counter', reducer }) // <-------
/* Return getComponent */
cb(null, Counter)
/* Webpack named bundle */
}, 'counter')
}
This technique is helpful when you want split a large app and avoid to load all the reducers at the boot.

Related

Redux/thunk Error: Actions must be plain objects. Use custom middleware for async actions

i'm new to Redux and have followed this video https://www.youtube.com/watch?v=93p3LxR9xfM to implement Redux into my MERN template, however it keeps crashing due to "Error: Actions must be plain objects. Use custom middleware for async actions."
I believe the problem might be somewhere in the dispatch function but can't seem to find it.
fetch file:
`export function fetchPosts () {
return function(dispatch) {
fetch('http://localhost:5000/products/5d082bb89501e113334e5c8e')
.then(res => res.json())
.then(posts => dispatch({
type: FETCH_POSTS,
payload: posts
})
);
}
} `
the component:
class MyComponent extends React.Component {
componentWillMount() {
this.props.fetchPosts();
}
render() {
return (
<div></div>
);
}
};
export default connect(null, { fetchPosts })(MyComponent);
You fetchPosts action returns a function, whereas it was expected to return a plain object.
Returning a function is required for async operations in redux. But you need to wire up a middleware called redux-thunk.
https://github.com/reduxjs/redux-thunk
So that, this error will go away.
How to wire up redux-thunk:
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
const store = createStore(
reducers, // your reducers
compose(
applyMiddleware(thunk)
)
)

React Redux Firebase: Error on firebaseConnect - Cannot read property 'ordered' of undefined

I followed the example in the documentation under v2.0.0 > Read Me > Load Data (listeners automatically managed on mount/unmount) (direct link is not possible).
And replaced the connect call with the firestore specific one shown here](http://react-redux-firebase.com/docs/firestore.html#examples) in Example 1.
I copied the Todo example exactly in a new component created for testing purposes.
Todo Component:
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { compose } from 'redux'
import { firebaseConnect,firestoreConnect, isLoaded, isEmpty } from 'react-redux-firebase'
const Todos = ({ todos, firebase }) => {
// Build Todos list if todos exist and are loaded
const todosList = !isLoaded(todos)
? 'Loading'
: isEmpty(todos)
? 'Todo list is empty'
: Object.keys(todos).map(
(key, id) => (
<TodoItem key={key} id={id} todo={todos[key]}/>
)
)
return (
<div>
<h1>Todos</h1>
<ul>
{todosList}
</ul>
<input type="text" ref="newTodo" />
<button onClick={this.handleAdd}>
Add
</button>
</div>
)
}
// export default compose(
// firestoreConnect([
// 'todos' // { path: '/todos' } // object notation
// ]),
// connect((state) => ({
// todos: state.firestore.data.todos,
// profile: state.firestore.profile // load profile
// }))
// )(Todos)
export default compose(
firestoreConnect(['todos']), // or { collection: 'todos' }
connect((state, props) => ({
todos: state.firestore.ordered.todos
}))
)(Todos)
The store configuration was configured as shown here in the docs. The store configuration was adapted to slot into the framework created by react-boilerplate.
/**
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux'
import { fromJS } from 'immutable'
import { routerMiddleware } from 'connected-react-router/immutable'
import createSagaMiddleware from 'redux-saga'
import { reactReduxFirebase, firebaseReducer } from 'react-redux-firebase'
import { reduxFirestore, firestoreReducer } from 'redux-firestore'
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/database'
import 'firebase/firestore'
import createReducer from './reducers'
const sagaMiddleware = createSagaMiddleware()
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.AUTH_DOMAIN,
databaseURL: process.env.DATABASE_URL,
projectId: process.env.PROJECT_ID,
storageBucket: process.env.STORAGE_BUCKET,
messagingSenderId: process.env.MESSAGING_SENDER_ID,
}
const rrfConfig = {
userProfile: 'users',
// useFirestoreForProfile: true, // Firestore for Profile instead of Realtime DB
// attachAuthIsReady: true
}
// Initialize Cloud Firestore through Firebase
export default function configureStore(initialState = {}, history) {
firebase.initializeApp(firebaseConfig)
// Initialize Firestore with timeshot settings
firebase.firestore()
// firebase.firestore().settings({ timestampsInSnapshots: true })
// 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),
// reactReduxFirebase(config), // enhancing our store with these packages
// reduxFirestore(config)
]
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle, indent */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose
/* eslint-enable */
const createStoreWithFirebase = compose(
reactReduxFirebase(firebase, rrfConfig), // firebase instance as first argument
reduxFirestore(firebase),
)(createStore)
const store = createStoreWithFirebase(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers),
)
// Extensions
store.runSaga = sagaMiddleware.run
store.injectedReducers = {} // Reducer registry
store.injectedSagas = {} // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers))
})
}
return store
}
I traced and verified my store configuration exactly to make sure all steps present in the documentation are configured correctly in my configuration.
My createReducer funciton is in a seperate file and you can see that I added the firebaseReducer and firebaseReducer correctly.
import { combineReducers } from 'redux-immutable'
import { connectRouter } from 'connected-react-router/immutable'
import { firebaseReducer } from 'react-redux-firebase'
import { firestoreReducer } from 'redux-firestore'
import history from 'utils/history'
import languageProviderReducer from 'containers/LanguageProvider/reducer'
export default function createReducer(injectedReducers = {}) {
const rootReducer = combineReducers({
firebase: firebaseReducer,
firestore: firestoreReducer,
language: languageProviderReducer,
...injectedReducers,
})
// Wrap the root reducer and return a new root reducer with router state
const mergeWithRouterState = connectRouter(history)
return mergeWithRouterState(rootReducer)
}
My redux store contains the firestore and firebase and it is injected into the component props.
What does not work is the use of connectFirestore HoC to automatically retrieve and inject a list of documents in to the component.
This is the error message:
react-dom.development.js?61bb:20266 Uncaught TypeError: Cannot read property 'ordered' of undefined
at Function.eval [as mapToProps] (index.js?d834:49)
at mapToPropsProxy (wrapMapToProps.js?1817:54)
at Function.detectFactoryAndVerify (wrapMapToProps.js?1817:63)
at mapToPropsProxy (wrapMapToProps.js?1817:54)
at handleFirstCall (selectorFactory.js?805c:37)
at pureFinalPropsSelector (selectorFactory.js?805c:85)
at Object.runComponentSelector [as run] (connectAdvanced.js?48b8:43)
at Connect.initSelector (connectAdvanced.js?48b8:195)
at new Connect (connectAdvanced.js?48b8:136)
at constructClassInstance (react-dom.development.js?61bb:11315)
(Snipped from my code which is the example 1 in documentation):
export default compose(
firestoreConnect(['todos']), // or { collection: 'todos' }
connect((state, props) => ({
todos: state.firestore.ordered.todos
}))
)(Todos)
I inspected the state variable and it does contain the firestore attribute. This attribute contains a number of functions, as expected, but it is missing the query results under "ordered", which is undefined.
I have tried all different ways to use firestoreconnect e.g. using a Class-based component, using a query with parameters, etc. and all give the same error.
My Firebase project is configured correct as I am able to create documents inside collections. A todos collection for testing purposes is present as well containing 2 documents.
I have come across this post, which mentions the following:
If you just upgraded to React-Redux v6, it's because react-redux-firebase is not compatible with v6.
See https://github.com/prescottprue/react-redux-firebase/issues/581 for details.
This does not apply to me because I am using react-redux version 5. Here are the versions I am using:
"firebase": "^5.10.1",
"react-redux": "^5.0.7",
"react-redux-firebase": "^2.2.6",
"redux": "^4.0.1",
"redux-firestore": "^0.7.3",
I have spent a significant amount of time on this. Like I said, using firestore to add new data to collections works fine. It is just this HoC business that is failing no matter how i approach the solution.
any help would be appreciated.
Never solved this. I guess it is related to incompatible versions. What I ended up doing is download v4 of react-boilerplate and set up v3 react-redux-firebase which uses the Context API as opposed to store enhancers. Now works very well.

What is the correct way to combine redux-thunk and redux-batched-actions?

What is the correct way to plug redux-batched-actions into my existing Redux store? I am completely confused by the Redux middleware API.
Currently I am using redux-thunk and redux-little-router.
Here is the code source that creates my Redux store:
import { createStore, applyMiddleware, compose, combineReducers } from 'redux'
import thunk from 'redux-thunk'
import { routerForBrowser } from 'redux-little-router'
import reducers from './store'
import routes from './routes'
const { reducer, middleware, enhancer } = routerForBrowser({ routes })
// Combine all reducers and instantiate the app-wide store instance
const allReducers = combineReducers({ ...reducers, router: reducer })
// Build middleware (if devTools is installed, connect to it)
const allEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION__
? compose(
enhancer,
applyMiddleware(thunk, middleware),
window.__REDUX_DEVTOOLS_EXTENSION__())
: compose(
enhancer,
applyMiddleware(thunk, middleware)))
// Instantiate the app-wide store instance
const initialState = window.initialReduxState
const store = createStore(
allReducers,
initialState,
allEnhancers
)
The redux-batched-actions documentation exposes two usages: enableBatching and batchDispatchMiddleware. Which one should I use in my case?
Answering my own question after the return of my expedition into the fabulous source code of redux, redux-thunk, redux-batched-actions, ...
The correct way to do it seems to be using batchDispatchMiddleware, like this:
import { batchDispatchMiddleware } from 'redux-batched-actions'
// ...
const allEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION__
? compose(
enhancer,
applyMiddleware(batchDispatchMiddleware, thunk, middleware),
window.__REDUX_DEVTOOLS_EXTENSION__())
: compose(
enhancer,
applyMiddleware(batchDispatchMiddleware, thunk, middleware)))
Note: I don't know if I could dispatch batched thunks, though. I don't do that in my current application. Use at your own risk!

using bindActionCreators, this.props.dispatch in react-redux disptach vs redux

I've read about bindActionCreators, i've compiled a resumen here:
import { addTodo,deleteTodo } from './actionCreators'
import { bindActionCreators } from 'redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addTodo, deleteTodo }, dispatch)
}
*short way
const mapDispatchToProps = {
addTodo,
deleteTodo
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
another code use like this:
function mapDispatchToProps(dispatch) {
let actions = bindActionCreators({ getApplications });
return { ...actions, dispatch };
}
why previous code with bindActionCreators , don't need disptach parameter?
i've tried this way to get dispatch on this.props (but not working):
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({ appSubmitStart, appSubmitStop}, dispatch );
};
const withState = connect(
null ,
mapDispatchToProps,
)(withGraphqlandRouter);
why I had to change my old short way:
const withState = connect(
null ,
{ appSubmitStart, appSubmitStop}
)(withGraphqlandRouter);
in order to get this.props.dispatch()? because i neede to use dispatch for an isolated action creator inside a library with js functions. I mean before I don't needed use "bindActionCreators", reading this doc:
https://redux.js.org/api-reference/bindactioncreators
"The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it."
I'm importing:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
what is the difference using redux pure, and react-redux?
really I need "bindActionCreators" in my new code? because without this i can't see this.props.dispatch()
UPDATE:
I've found this solutions to get this.props.dispatch working:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({ appSubmitStart, appSubmitStop, dispatch }, dispatch ); // to set this.props.dispatch
};
does anyone can explain me? how i can send same distpach like a creator ?
First let's clear our minds regarding some of the key concepts here:
bindActionCreators is a util provided by Redux. It wraps each action creators to a dispatch call so they may be invoked directly.
dispatch is a function of the Redux store. It is used to dispatch actions to store.
When you use the object shorthand for mapState, React-Redux wraps them with the store's dispatch using Redux's bindActionCreators.
connect is a function provided by React-Redux. It is used to connect your component to the Redux store. When you connect your component:
It injects dispatch to your component only if you do not provide your customized mapDispatchToProps parameter.
Regarding what happened above to your code:
Component will not receive dispatch with customized mapDispatchToProps
In the code here:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ appSubmitStart, appSubmitStop, dispatch }, // a bit problematic here, explained later
dispatch
); // to set this.props.dispatch
};
You are providing your own mapDispatch, therefore your component will not receive dispatch. Instead, it will rely on your returned object to contain the action creators wrapped around by dispatch.
As you may feel it is easy to make mistake here. It is suggested that you use the object shorthand directly, feeding in all the action creators your component will need. React-Redux binds each one of those with dispatch for you, and do not give dispatch anymore. (See this issue for more discussion.)
Writing customized mapState and inject dispatch manually
However, if you do need dispatch specifically alongside other action dispatchers, you will need to define your mapDispatch this way:
const mapDispatchToProps = (dispatch) => {
return {
appSubmitStart: () => dispatch(appSubmitStart),
appSubmitStop: () => dispatch(appSubmitStop),
dispatch,
};
};
Using bindActionCreators
This is exactly what bindActionCreators does. Therefore, you can simplify a bit by using Redux's bindActionCreators:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ appSubmitStart, appSubmitStop }, // do not include dispatch here
dispatch
);
};
As mentioned above, the problem to include dispatch in the first argument is that it essentially gets it wrapped around by dispatch. You will be calling dispatch(dispatch) when you call this.props.dispatch.
However, bindActionCreators does not return the object with dispatch. It's passed in for it to be called internally, it does not give it back to you. So you will need to include that by yourself:
const mapDispatchToProps = (dispatch) => {
return {
...bindActionCreators({appSubmitStart, appSubmitStop}, dispatch),
dispatch
};
};
Hope it helped! And please let me know if anything here is unclear :)
I have made some changes to your code please try this
import * as Actions from './actionCreators'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
const mapStateToProps = (state)=>(
{
todos: state.todos
}
)
const mapDispatchToProps = (dispatch)=> (
bindActionCreators(Actions, dispatch)
)
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

Redux: Shared actions

I have a few actions in my app that need to be shared by a few different react components. For example, I have a fetchAPIData action creator which accepts some params and fires off actions to to make a fetch (using a custom fetch middleware i've written):
export function fetchAPIData(params) {
const actions =
[
types.API_CALL,
types.RECEIVE_API_DATA,
types.API_ERROR
];
const params = {
method: 'params.method',
params
};
return fetch(actions, params);
};
This action and others like this needs to be called by various different parts of the app so i've created a common-actions directory where these actions live. This feels like the wrong approach so I am wondering if there is a more accepted way of doing this?
I would recommend you to use connect function react-redux
You can then import your actions to the top level component, and bind them with the state you want. then pass the props to any children you want. quick example:
import React, { Component } from 'react';
// Import actions below
import * as myActions from '/redux/actions/myActions';
// Make sure redux, react-redux are installed
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
class App extends Component {
render() {
const { redux, actions } = this.props; // the redux state.
const { actionName } = actions;
<div>
<button onClick={() => actionName()}> {/* Dispatching redux action... */}
Dispatch action
</button>
<ChildComponent {...this.props} />, {/* Now you can pass the props to any children */}
</div>;
}
}
// take any state from redux you want and return it within redux object
const mapStateToProps = state => ({
redux: {
myState: state.myState,
},
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators({
...myActions,
// import actions below...
}, dispatch),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App); // The component name you want to bind the state and the actions to.
I left notes so you can understand what's going on. This is ES6.

Resources