How to trigger action on mount, but after redux storage load - reactjs

I use redux-storage for persisting the redux state tree across refreshes, etc.
redux-storage-engine-localstorage as the storage engine.
My problem has to do with the order of
1) the action which loads this persisted state, and
2) some other action I set off in a componentDidMount lifecycle function.
Because the storage load happens after some action I trigger, the action I trigger might as well not have happened, since the old state overwrites it.
Printing out the action type at the top of my reducer, on page load I see:
action.type ##redux/INIT
action.type HIDE_MODAL
action.type REDUX_STORAGE_LOAD
action.type REDUX_STORAGE_SAVE
action.type FETCH_USER_FAILURE
action.type REDUX_STORAGE_SAVE
The HIDE_MODAL action is triggered in one of my components' componentDidMount method.
The storage load happens before mounting my root node (right after creating my redux store):
import mainReducer from './reducers';
const reducer = storage.reducer(mainReducer);
const storageEngine = createEngine(some key);
const storageMiddleware = storage.createMiddleware(storageEngine);
const middleware = applyMiddleware(
storageMiddleware,
);
const storageLoad = storage.createLoader(storageEngine);
const store = createStore(
reducer,
middleware,
);
storageLoad(store);
render(
<Provider store={store}>
...
</Provider>,
document.getElementById('root')
);
Why does the REDUX_STORAGE_LOAD not happen before everything else, and is there a way to make sure it occurs before everything else?

I added a new state field reduxStorageLoaded, initially false, and added a case to my reducer:
import { LOAD } from 'redux-storage';
...
const mainReducer = (state = initialState(), action) => {
switch (action.type) {
...
case LOAD:
return {
...state,
reduxStorageLoaded: true,
};
...
and wrapped my whole app in a conditional based on this new state variable reduxStorageLoaded so it only mounted once it becomes true, which is when redux-storage has fully loaded and dispatched the LOAD action.

Related

Where should I put a common code in Redux?

I have an application with Reactjs and Redux. There is an action which resets the state of the reducers. My question is: where is the best place to perform that? I am considering two options:
each reducer handle the action and resets its state
const reducer1 = (state = defaultState, action) => {
switch (action.type) {
case 'reset': {
// ...
}
// ...
}
the root reducer resets the global state
const appReducer = combineReducers({
reducer1,
reducer2,
reducer3
})
const rootReducer = (state, action) => {
if ( (action.type === 'reset') ) {
state = {}
}
return appReducer(state, action)
}
The best practice is to have an action for reseting each reducer, this helps for extensibility in the future. Dispatch the clearState action, but do not set an empty object. Set it to the initial state, because if you put an empty object you can introduce bugs
The first thing we must have clear is that reducers don't have states, stores have states, so you shouldn't say "the state of the reducer".
A reducer is a function that performs some change in the state of a store, and there is no specific limitation in the scope of such change, besides the scope of the store, so many reducers have overlapping scopes over the state of the store.
After that, I see no reason why you can not reset the whole state of the store with a single reducer, and when you need to make other changes with different scope, you can create other reducers to manage it.

react redux mapStateToProps - correct props not making it to the component

I have a component OnePayrunGridContainer which is wrapped in react-redux's connect Component.
There are two distinct async actions that update the redux store -
FETCH_PAYRUN_TS_DETAILS - to retrieve all the data after the component has loaded
FETCH_TIMESLIP_DATA - that updates the state.timeslipInModal
When there is an object in timeslipInModal, my render function displays it in a modal.
In the react tools in chrome and firefox, I can see the console.log output demonstrating that the redux store has updated, the mapStateToProps has been called, but when I inspect the component in the react console, the prop timeslipInModal remains false - it's initial value when the component was first rendered.
The react-redux documentation for mapStateToProps says -
The new component will subscribe to Redux store updates. This means that any time the store is updated, mapStateToProps will be called.
which IS happening (I know this from a console.log in the mapStateToProps).
The documentation also says -
The results of mapStateToProps must be a plain object, which will be merged into the component's props.
This part is what is NOT happening. I console.log the redux store newState just before I return it - and I can see the same output in the console when I have the axios debug feature turned on.
The result of the mapStateToProps function is a plain object, but this object is NOT being merged into the child component props - this is illustrated by this screen shot:
Here is the code to show that I console.log(props.timeslipInModal), which is non-false in the console but the props of the wrapped component does not change.
const mapStateToProps = (state) => {
let props = {
userIdBy: false,
apiToken: false
};
props.timeslipInModal = state.timeslipInModal ? state.timeslipInModal : false;
if (state && state.myBus) {
if (state.myBus.payrun) {
// state.myBus.payrun.loading, loaded and errors copied by this
props = Object.assign(state.myBus.payrun, props);
}
if (state.myBus.common && state.myBus.common.auth) {
props.userIdBy = state.myBus.common.auth.userId;
props.apiToken = state.myBus.common.auth.apiToken;
}
}
console.log('timeslipInModal at the end of mapStateToProps:', props.timeslipInModal);
return props;
};
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatch,
fetchPayrunDetails:
dispatch(fetchPayrunDetails(ownProps.userIdBy, ownProps.apiToken, ownProps.entityId))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(OnePayrunGridContainer);
My Reducer for the fetchPayrunDetails looks like this:
case 'FETCH_PAYRUN_TS_DETAILS_FULFILLED': {
const newState = Object.assign({}, state);
newState.myBus = Object.assign({}, state.myBus);
newState.myBus.payrun = action.payload.data.data;
newState.myBus.payrun.loading = false;
newState.myBus.payrun.loaded = true;
newState.myBus.payrun.errors = false;
return newState;
}
My Reducer to update the reactStore.timeslipInModal is a separate case in the reducer.js:
case 'FETCH_TIMESLIP_DATA_FULFILLED': {
if (action.payload.data.errors) {
// caputure errors - not relevant here
}
const newState = Object.assign({}, state, {
modalFetchErrors: false,
modalFetched: true,
modalFetching: false,
modalValidating: false,
timeslipInModal: action.payload.data.timeslip
});
return newState;
}
One thing that is a little 'different' and hence may be a problem - in my entry.js script, I have a loop where I look for html entities by their ID, and based on their id, I call the correct component to render.
This allows me to have one entry.js script called at the end of my common template view.
import OnePayrunGridContainer from './components/payrun/OnePayrunGridContainer.js';
import {Provider} from 'react-redux';
import store from './store.js';
// put these in here so they match code searches - these container components
// are included below by Alias so explicitly list them here: <OnePayrunGridContainer>
let componentNamesByRootElementIds = {
onePayrunGridMyBus: OnePayrunGridContainer
};
let htmlElement = false;
for (let onereactElementId in componentNamesByRootElementIds) {
htmlElement = document.getElementById(onereactElementId);
if (htmlElement) {
let Component = componentNamesByRootElementIds[onereactElementId];
ReactDOM.render(
<Provider store={store}>
<Component
userIdBy={userIdBy}
apiToken={apiToken}
entityId={entityId}
onereactElementId={onereactElementId}
/>
</Provider>,
htmlElement
);
}
}
Also, I do export the connect in my OnePayrunGridContainer.js:
export default connect(
mapStateToProps,
mapDispatchToProps
)(OnePayrunGridContainer)
The other props all update when the FETCH_PAYRUN_TS_DETAILS_FULFILLED calls are made that update the redux store - what am I missing? How do I make the props on the wrapped component update correctly when the redux state is updated by the FETCH_TIMESLIP_DATA_FULFILLED action?

Redux pass up/refresh state from current page

I have a react app (repo) that I want to use redux to store the state universally, so the root app can access it.
For example: one page has a GET API call that populates the page. That works fine and all, but I'm confused as to how to do a couple things.
How can I use variables in the redux action, to give the action say the ID of the model and have it return the model (API returns json).
How can I then pass that state up so that a higher ordered component (such as the base App.js) can access the state, so that I can use variables from the current page in the navigation.
What/when is the best way/time to update the redux state so that the changes reflect across anywhere using the redux state?
Specifically (in this project): If you are on localhost/spells/X with X being the model ID, how can I pass the state up from that page's container component (in this case LayoutSpellView) up to MaterialUIApp
index.js
|--App.js
|--MaterialUiApp
|--Router
|--LayoutSpellView (pass state up to MaterialUiApp)
With Redux you don't pass the state up or down. You update the global state with your action creators and reducers. Wherever you need to reach the state you connect your components to the state and use it. You have a store and it includes a global state. That global state may contain multiple different states.
You can use payload or any other name, variable with your action creator. In your reducer you can get those with action.payload, action.id, etc.
As I explained in the first paragraph, you update your state whenever you need. After that you connect any component to your state wherever you need.
There is no best time or best way to do that. This is up to your code and app logic.
Of course there are some best practices but we can't talk about them so broad. After you are getting involved with Redux you will see some of them around. For example I said "we don't pass up or down the state with Redux". This is true but sometimes to avoid so many connects around components we use container apps, connect that app to store (you reach state via store actually) and then pass the related state parts to the related components.
I recommend Redux's own documentation as starting point: https://redux.js.org/
To help you see the data flow, here's a sketch of how everything ties together. In my example code below, this is the data flow:
Clicking the "Load Comments" button dispatches a thunk with the parameter userId. (A thunk is an async action.)
The thunk uses the userId to make its async call, and then dispatches an action setComments(comments) with the received comments as its payload.
The Comments reducer catches that action and updates the Redux state with the comments array.
The Container to updates comments in mapStateToProps
The Component receives the updated comments, and displays them in the <ul>
// actions.js
export const SET_COMMENTS = "MyApp/setComments";
export const setComments = comments => ({
type: SET_COMMENTS,
payload: comments
});
// thunks.js
import { setComments } from './actions';
export const getCommentsAsync = id => dispatch => {
return axios
.get(`http://localhost:5000/comments/${id}`)
.then(comments => dispatch(setComments(comments)));
};
// reducer.js
import { SET_COMMENTS } from './actions';
const initialState = {
comments: []
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_COMMENTS:
const comments = action.payload;
return {
...state,
comments
};
default:
return state;
}
};
// components.js
export default function CommentsList({ comments, loadComments, userId }) {
return (
<div>
<ul>
{comments.map(comment => <li key={comment.id}>{comment.body}</li>)}
</ul>
<button onClick={() => loadComments(userId)}>Load Comments</button>
</div>
);
}
// containers.js
import { connect } from "react-redux";
import { getCommentsAsync } from "./thunks";
import CommentsList from "./components";
mapStateToProps = state => ({
comments: state.comments,
userId: state.user.id
});
mapDispatchToProps = {
loadComments: getCommentsAsync
};
export default connect(mapStateToProps, mapDispatchToProps)(CommentsList);

How to stop redux-form's "form" state from auto-rehydrated by redux-persit

I'm using redux-form and it provides a built-in reducer, called "formReducer" that need to be registered with the combined reducers to manage the form state with redux's store.
I'm also using redux-persist to persist the redux store.
The problem raised when I don't want to have my form automatically re-populate the data entered by the user on page reloading or page refreshing. In a normal reducer written by my own, I can simply add an switch case for action of type "REHYDRATE" (dispatched by redux-persit) to prevent the state slice from auto-rehydrating by just returning its initial state or an empty state. But redux-form's formReducer is built-in provided by redux-form, so I cannot change. So, is there any way to "customize" the redux-form reducer to add that switch case? Or, is there any way I can config redux-persist to not auto-rehydrate a specific state slice, or is there any way I can config redux-form to not being auto-populated by page reloading or page refreshing?
I have a "perfect" solution based on suggestion by #jpdelatorre from this thread How to handle redux-form/CHANGE in reducer
Basically it's to "extend" the formReducer provided by redux-form, then add switch case for the event "REHYDRATE":
import { reducer as reduxFormReducer } from 'redux-form'
import { REHYDRATE } from 'redux-persist/constants'
const formPlugin = {
my_redux_form_name: (state, action) => {
switch (action.type) {
case REHYDRATE:
return {}
default:
return state
}
}
}
const formReducer = reduxFormReducer.plugin(formPlugin)
export default formReducer
then have the extended reducer to register with the root reducer.
import formReducer from './form.reducer'
const rootReducer = combineReducers({
...other reducers,
form: formReducer
})
If you are using the latest (v5) redux-persist version, in the persistConfig option there's a whitelist key-option where you whitelist which reducers should be persisted/rehydrated. You should use that, e.g:
const persistConfig = {
key: 'root_key_in_localstorage',
storage,
whitelist: ['session'],
}
You can use a Middleware that will handle this specific action type and prevent it from being passed to the reducers.
const myMiddleWare = store => next => action => {
if(action.type != 'REHYDRATE'){
next(action); // pass the action forward to the reducers
} else{
// do your logic here, you can use store.dispatch to dispatch other actions
// when your not invoking next(action) this action won't pass through to all the reducers
}
}

Reusable component using React Redux mapStateToProps

A React component OilBarrel connected my redux store to create a container OilBarrelContainer:
// ---- component
class OilBarrel extends Component {
render() {
let data = this.props.data;
...
}
}
// ---- container
function mapStateToProps(state) {
let data = state.oilbarrel.data;
...
}
const OilBarrelContainer = connect(mapStateToProps)(OilBarrel)
// ---- reducer
const oilbarrel = (state = {}, action) => {
let data = state.data;
}
const storeFactory = (server = false, initialState = {}) => {
return applyMiddleware(...middleware(server))(createStore)(
combineReducers({oilbarrel, otherReducer1, otherReducer2}),
initialState
)
}
I find it strange that mapStateToProps() receives the top level state object (the entire state of the application), requiring me to traverse state.oilbarrel.data, when the reducer (conveniently) only receives the branch of the state that belongs to this component.
This limits the ability to reuse this container without knowing where it fits into the state hierarchy. Am I doing something wrong that my mapStateToProps() is receiving the full state?
That is the mapStateToProps behavior. You have to think redux state as a single source of truth (by the way, that is what it really is) independently of the components you have in project. There is no way out, you have to know the exactly hierarchy of you especific data in the state to pass it to your container component.
No this is intentional, because you may want to use other parts of the state inside your component. One option is to keep the selector (mapStateToProps) in a separate file from your component, which will help you reuse the selector, if you app is very large and complex you can also checkout libraries such as reselect which helps you make your selectors more efficient.
Dan Abramov offers a solution for this in his advanced redux course under Colocating Selectors with Reducers.
The idea is that for every reducer, there is a selector, and the selector is only aware of it's reducer structure. The selectors for higher level reducers, wrap the lower level reducer, with their part of the state, and so on.
The example was taken from the course's github:
In the todos reducer file:
export const getVisibleTodos = (state, filter) => {
switch (filter) {
case 'all':
return state;
case 'completed':
return state.filter(t => t.completed);
case 'active':
return state.filter(t => !t.completed);
default:
throw new Error(`Unknown filter: ${filter}.`);
}
};
In the main reducer file:
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
Now you can get every part of your state without knowing the structure. However, it adds a lot of boilerplate.

Resources