Redux - how do i access the app state from sub-components? - redux

Let's say i have the following components structure:
App -> Comp1 -> Comp2 -> Comp3 -> Comp4 -> Comp5
Where App is the parent component of Comp1 which is the parent component of Comp2 and so on.
I create the store in App and connect it to Comp1 with the "connect" function from the "react-redux" library.
Now, what do i do if i want to access the app state or dispatch an action from Comp5?
The way i'm aware of is to pass the props (state and dispatch) all the way from App too Comp5 which doesn't seem to intuitive.
What is the best practice for this?

With Redux forgot the component hierarchy you can connect the store directly to your component comp5:
import { connect } from "react-redux";
const mapStateToProps = (state, ownProps) => {
return {
...state.getExampleReducer,
...ownProps
};
};
const mapDispatchToProps = {
ActionsFunctions
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(comp5);

Related

Redux tookit access store values outside of component

How can I access redux-toolkit store variables outside of React component? In React components I could access it with
import { useSelector } from 'react-redux';
const isAuthenticated: boolean = useSelector(
(state: RootState) => state.user.isAuthenticated,
);
Since useSelector isn't allowed to access outside of React. Or should I pass variables when I dispatch in React Component? I need access variable in createAsyncThunk API call
createAsyncThunk will pass in a thunkApi object as the second argument to the payload creator callback and on that object, you can call the getState method.
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (someArg, thunkApi) => {
const state = thunkApi.getState()
// whatever logic you need.
const response = await userAPI.fetchById(someArg)
return response.data
}
)

Confusion with how createStore works in redux

I was learning Redux and came across createStore function. So, as I understood createStore receives 3 parameters:
reducer
initial state
enhancers (for simplicity we will use only middlewares)
But when we use createStore in action we do not pass initial state as the second argument BUT pass reducer with default state like this:
const initialState = {counter:0}
const reducer =(state=initialState, action)=>...
The question is why don't we put the initial state as the second argument but pass initialState to reducer?
I think you are confusing the initial state of a reducer to that of the global state of your app.
Global state simply means that combined state of all the reducers in your app.
For simplicity let's just assume you only have one reducer in your app.
Reducer :
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text])
default:
return state
}
}
So this simple function todos is our reducer which will give us the current state tree, whenever it is run.
So this is our first parameter to createStore.
Initial State :
['Understanding Store']
Let's assume our initial state as an array which contains 1 item as shown above.
This will be our second parameter to createStore.
Now we create our store like this:
import { createStore } from 'redux'
//... code
.
.
.
const store = createStore(todos, ['Understanding Store'])
Now our store is created.
Nothing fancy, store is basically an object, which has few methods on it.
One of those methods is dispatch.
This method helps in dispatching an action, which will run through our reducer and then update the state.
So when we do this
store.dispatch({
type: 'ADD_TODO',
text: 'Learn methods on Store'
})
This will update our state as below:
['Understanding Store','Learn methods on Store']
But when your app grows big, you might want to create different functions (reducers) to manage different parts of your global state.
If we have one more reducer, say counter.js :
export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
Then to combine our 1st reducer todos and this counter reducer, we have an utility called combineReducer.
rootReducer.js
import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'
export default combineReducers({
todos,
counter
})
Then using createStore, you just do this:
import { createStore } from 'redux'
import rootReducer from './rootReducer.js;
const store = createStore(rootReducer);
There are certain rules that you need to follow while using combineReducers.
Read the rules here
The use case for passing an initial state as the second argument in createStore is intended for use cases where you get this initial state from the outside when loading your app. Examples could be state generated on the server for server-side rendered applications that are hydrated on the client, or an application that restores the redux state form local-storage when loaded.
The initial value of a single reducer should be returned when the reducer function is called with undefined state, the easiest way is to use a default argument for state:
const reducer = (state = initialState, action) => ...
This allows you to define the initialState close to where the reducer is defined and it scales nicely with combineReducers when you have a larger number of reducers. If you would put all the initialState of all reducers into one object that is passed to createStore this would become difficult to keep in sync.
I think you have actually confused createStore with a reducer. Think of createStore as a function which returns you a collection of reducers by adding in a certain middleWares and other functionalities like dispatch
More often than not you have multiple reducers in your app and you actually combine them using combineReducers
Say for example you combineReducers is
import userReducer from 'reducers/user';
import authReducer from 'reducers/auth';
import countReducer from 'reducers/count';
const reducers = combineReducers({
userReducer,
authReducer,
countReducer,
});
Now the initialState to createStore must be of the format of an object with keys as userReducer, authReducer, countReducer and then the individual reducers state. For example
{
userReducer: { id: 1, name: 'Test'},
authReducer: { isLoading: true, isAuthenticated: false},
countReducer: {counter: 0}
}
Now thing of the second keys as equivalent to initialState each individual reducer
For example: reducer/count.js
const initialState = {counter:0}
const reducer =(state=initialState, action)=>...
The way it works is that createStore would actually call the reducer with the action each time action is invoked like
reducer(state, action);
In case of combineReducer it works like below
const combineReducers = (reducers) => {
return (state, action) => {
const tempState = { ...state };
Object.keys(reducers).forEach((key) => {
tempState[key] = reducers[key](tempState[key], action);
});
return tempState;
};
};
and for the initial time it invokes it with
reducer(initialState, {type: "##redux/INIT"});
so that each reducer's initial state is populated
P.S. If you do not pass the initialState to createStore, each reducer takes in the default argument passed to to it like const reducer =(state=initialState, action)=> and returns state for default switch clause causing the initialState from each reducer to be used

Redux - how to access the store "dispatch" after providing it with Provider?

I have the following files:
index.js
const store = createStore(...)
ReactDOM.render(<Provider store={store}><BrowserRouter><App/></BrowserRouter></Provider>, document.getElementById('root'));
The App component: (App.js)
const App = withRouter(connect(mapStateToProps, mapDispatchToProps)(Main))
export default App
So then how i can access store.dispatch inside of the Main component?
If i try to do it by store.dispatch({...}) i get:
'store' is not defined no-undef
If mapDispatchToProps looks like:
const mapDispatchToProps = dispatch => ({
myAction1: () => dispatch(myAction1())
});
connect(mapStateToProps, mapDispatchToProps)(Main)
...then in the component, you can call this.props.myAction1()
If mapDispatchToProps uses bindActionCreators:
const actions = { myAction1, myAction2 };
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
...then in the component, you can call this.props.myAction1() and this.props.myAction2()
If mapDispatchToProps is undefined:
connect(mapStateToProps)(Main)
then in the component, you can access this.props.dispatch
dispatch function should be available through this.props
this.props.dispatch()
Your component shouldn't access the store directly - connect abstracts that away.
Please see our new React-Redux docs page on connect: Dispatching Actions with mapDispatchToProps for a complete description of how to handle dispatching actions.

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!

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