I have React app with Redux that has following structure:
<ComponentParent>
<ComponentA></ComponentA>
<ComponentB></ComponentB>
</ComponentParent>
In component A an ComponentDidMount, a fetch is called and data is return async-ly. Reducer is then called to add data to the store.
Component B then accesses the store to access data added by A to the store.
Predictably Component B accesses the data before Component A had a change to write data to the store (because data is coming from aync fetch).
Question:
what is a proper way to design such interaction?
Do I need use
approach similar to
react redux with asynchronous fetch
? Note that in Reducer I just store data returned async-ly by
Component A, unlike in the link
Thanks
Set a default state to your componentB for it to load while awaiting results from your fetch.
In your fetch action, assuming you use redux-thunk:
let fetchData = () => async dispatch => {
let res = await fetchFromDataSource();
dispatch({
type: UPDATE_STATE,
payload: res
})
};
Your component B should be linked up to the store. Upon dispatch update, it should trigger your componentB to reload via ComponentDidUpdate.
I like the pattern of creating an initial state for the object in the reducer, so that any component accessing it gets that initial state first, and can later update based on a post-fetch state.
xReducer.js
const initState = {
// Initial state of object
};
export default function xReducer(state=initState, action) {
switch (action.type) {
case actionTypes.MY_POST_FETCH_ACTION_TYPE:
return {
...state,
// state override
};
default:
return state;
}
}
Related
I'm building a react native app and using redux to handle the state. I am running into a situation where one of my containers is not updating immediately when the redux state is changed.
Container:
...
class ContainerClass extends Component<Props, State> {
...
componentWillReceiveProps(nextProps: Object) {
console.log('WILL RECEIVE PROPS:', nextProps);
}
...
render() {
const { data } = this.props;
return <SubComponent data={data} />
}
}
const mapStateToProps = (state) => ({
data: state.data
};
export default connect(mapStateToProps)(ContainerClass);
Reducer:
...
export default function reducer(state = initalState, action) => {
switch(action.type) {
case getType(actions.actionOne):
console.log('SETTING THE STATE');
return { ...state, data: action.payload };
...
...
...
In a different random component, I am dispatching a call with the actionOne action, which I confirm prints out the relevant console.log. However, the console.log in the componentWillReceiveProps in the container is not printed.
The component that dispatches the call is a modal that has appeared over the Container, and closes automatically after the call is dispatched and the state is updated. What is weird is that although the Container isn't updated immediately, if I navigate to a different page and then back to the Container page, the state is in fact updated.
EDIT: Initial state is:
const initialState: Store = {
data: []
}
And the way I dispatch is in a different component which gets called as a new modal (using react-native-navigation) from Container:
fnc() {
...
setData(data.concat(newDatum));
...
}
Where setData and data are the redux dispatch action and the part of the store respectively that is passed in on props from the Container (which has setData and data through mapStateToProps shown above and a mapDispatchToProps which I didn't show).
I solved my problem by updating from react-native v0.56 to v0.57. Apparently there was a problem with react-redux v6 working properly in the react-native v0.56 environment.
Assuming you're using a recent version of React, componentWillReceiveProps is actually deprecated:
Using this lifecycle method often leads to bugs and inconsistencies
You can't really rely on that lifecycle hook in a number of situations. You may want to look at a slightly different approach with componentDidUpdate instead.
I think more important is to get the value after changing in state of redux rather than in which lifecycle you are getting the value . so for getting the value you can use subscribe method of redux in componentDidMount
store.subscribe( ()=> {
var updatedStoreState = store.getState();
})
I believe that getDerivedStateForProps would solve your problem.
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.data !== prevState.data) {
//Do something
} else {
//Do something else
}
}
You would check the state from the redux against the state from your component and then act accordingly.
Also, some info from the documentation that you might consider before using this method:
1. getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates.
2. This method exists for rare use cases where the state depends on changes in props over time.
3. If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
You can read more at: https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
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);
My questions is a conceptual one and based on the issue outlined in this post: React Redux capture updated store state after updating database. I don't think any code is needed to understand or be able to answer it. But if not it is at the link above.
I think I might have missed a small detail about the react/redux state update process following an action that changes the back-end data that a state variable reflects. My question is: When I dispatch a save action, should I then also be dispatching a request to update any state that depends on that underlying data?
So for example, right now the way I'm thinking about it and implementing my code is as follows:
app starts and ParentComponent loads and dispatches GET_DATA on componentDidMount which initializes state variable data which is reflected on ParentComponent in a table
when a link is clicked on ParentComponent, ParentComponent renders ChildComponent which is a react-modal popup that displays elements of data so it can be updated
there is and Save and Close button on ChildComponent; when you click the button, SAVE_DATA is dispatched and the changes to data that are made on ChildComponent get saved to the database
THIS is where my question arises... at this point should I also be calling GET_DATA to dispatch the process of "refreshing" data in my state? Would this be the right way to handle saving data to a database when using redux so that all components that rely on data get updated?
Note: What I'm currently doing is that after step 3, I am simply triggering a refresh function in ParentComponent so that it rerenders and hence reflects data in state. The epiphany I just had is that there is no way for data in state to reflect the new saved data because GET_DATA has not been dispatched after saving and rerendering the component does not trigger GET_DATA.
Are my assumptions correct? Should I be calling GET_DATA somewhere else in my ParentComponent like ComponentWillReceiveProps? The issue I had here is that maybe I'm doing something wrong, but it triggers an endless loop. Somehow though I feel that is the only place where I can address my need to dispatch GET_DATA after the local ParentComponent state is changed by setting refresh (a ParentComponent state variable) to true.
I think it would benefit you to refactor your actions a bit to take advantage of the action/middleware/reducer pattern.
You would have an action GET_TRANSACTIONS, that would take your year param. Your transactionsMiddleware would respond to the GET_TRANSACTIONS action by making your fetch request and would dispatch GET_TRANSACTIONS_SUCCESS with the respond data on success. You transactions reducer would then process the data into your store.
actions
export const getTransactions = year => {
return {
type: "GET_TRANSACTIONS",
year
};
};
export const getTransactionsSuccess = payload => {
return {
type: "GET_TRANSACTIONS_SUCCESS",
payload
};
};
middleware
function getTransactions(year) {
fetch().then(response => dispatch(actions.getTransactionsSuccess(response.data));
}
reducer
const getTransactionsSuccess = (state, action) => {
return Object.assign({}, state, newStuffFromActionPayload);
}
You would also have an action SAVE_TRANSACTIONS, which would be what your button would dispatch, along with the data to save. Your transactionsMiddleware would respond to the action by dispatching the update request. Your API would return the data from the updated record.
This is where you would have the middleware dispatch a follow-up action. It could be your getTransactions action, but it'd be even better to dispatch an action that your reducer would respond to by merging in the new data to your store.
actions
export const updateTransaction = payload => {
return {
type: "UPDATE_TRANSACTION",
payload
};
};
export const updateTransactionSuccess = payload => {
return {
type: "UPDATE_TRANSACTION_SUCCESS",
payload
};
};
middleware
function updateTransaction(transUpdate) {
fetch().then(response => dispatch(actions.updateTransactionSuccess(response.data))
}
reducer
const updateTransactionSuccess = (state, action) => {
find the record in the state, update it with data from action.payload
return Object.assign({}, state, updatedRecord);
}
If everything is set up correctly, it should trigger an update on your parent when it detects the change in the store. You avoid making two API calls for every save as well.
I have a React app that does some simple recording. I have a Component Recorder which connects to my redux store like this:
export default connect(
state => ({
recordings: state.recordings,
recordingSelector: selectRecordingBufferWithID(this.recordingID)
}),
dispatch =>
bindActionCreators({
startNewRecordingAction,
stopNewRecordingAction
},
dispatch
)
)(SampleRecorder);
The problem I'm having is that selectRecordingBufferWithID in my redux code is firing too often. Part of my reducer code looks like this:
function samplesReducer(state = [], action) {
switch (action.type) {
case MORE_SAMPLES:
return [...action.samples];
default:
return state
}
}
function recordingsReducer(state = [], action) {
switch (action.type) {
case NEW_RECORDING:
return newRecording(state, action.recordingID);
case STOP_RECORDING:
return stopRecording(state, action.recordingID);
default:
return state
}
}
const rootReducer = combineReducers({
samplesReducer,
recordingsReducer
})
const store = createStore(rootReducer);
export { store };
So, while I want selectRecordingBufferWithID to be utilized only when a START/STOP_RECORDING action occurs, it is called for each time MORE_SAMPLES is called.
My understanding of react-redux is that the selector is part of the mapStateToProps function that the connect function accepts. And somehow, connect cause my component to render and for its props to be updated with the mapped state from the redux store. the selectRecordingBufferWithID selector will also be called each time this happens so I can do a refined getter into the store.
So to summarize, my recordingSelector is firing more often than I expect. My only theory is that my reducers are somehow mutating the state of state.recordings each time it tries to reduce state.samples which makes react-redux render my component with it mapped to state.recording.
But otherwise, I'm stuck.
connect does not work the way you think it does. What it really does is:
Subscribe to the store. This subscription will be triggered after every dispatched action.
Execute your mapStateToProps to inject the initial set of props to your Sample Recorder component.
When any action dispatches, the subscription kicks in, and connect applies again your mapStateToProps to new global state.
If your selector returns the same props as before, it won't render your SampleRecorder again.
So the misunderstanding is that your selector shouldn't be called. But the fact is that connect needs to call your selector to decide when to re-render and when not.
The summary of this is that your selector should be either simple, or memoizable using reselect to avoid expensive calculations. You didn't show you selector code so we can't tell from here. :)
We are storing the data for the particular component in the redux
store by using creatStore() and where combining all the component's
state by using combineReducers().Now Once we came out of the page we
need to clear the state stored using redux. This is not the dublicate
question as written in
How should I clear state in componentWillUnmount?
because in this question they want to clear the state of the page that
they save by using this.state{}.In our scenario we have to clean from
global state (redux-stored-state) for the particular component.We want
a global solution so that we can apply to all our component.Please
assist me.
You could dispatch a reset action in componentWillUnmount which would be handled by a corresponding reducer. The reducer would clear the redux state.
To make it global, you might create a higher-order component that would add dispatching of the reset action to the component it's applied to. And you could have one reducer for the whole app to handle reset actions.
you need to import actions of component global store:
import {
fetchCountries,
fetchResellers,
selectCountry
} from "../points-of-sale/actions";
I use #Container decorator,
#Container({
props: state => ({
error: state.product.error,
product: state.product.product,
fetched: state.product.fetched,
locale: state.i18n.locale,
countries: state.pointsOfSale.countries,
resellers: state.pointsOfSale.resellers,
availableCountries: state.pointsOfSale.availableCountries,
selectedCountry: state.pointsOfSale.selectedCountry,
selectedResellerType: state.pointsOfSale.selectedResellerType,
selectedResellers: state.pointsOfSale.selectedResellers,
menuFixed: state.layout.menuFixed
}),
actions: {
...actions,
fetchCountries,
fetchResellers,
selectCountry
}
})
here imports actions or stores for another components...
and later only you need use de action to call the apropiate reducer
async componentDidMount() {
if (
!this.props.product ||
this.props.product.slug != this.props.params.product
) {
await this.props.fetchProduct(this.props.params.product);
}
if (!this.props.countries.fetched) {
await this.props.fetchCountries();
}
if (!this.props.resellers.fetched) {
await this.props.fetchResellers();
}
this.props.getMenuFixed();
}
and if dou you want clean when the component is unmount you can call to global action that change state of anyone of your components
async componentWillUnmount() {
console.log("componente se esta desmontando");
await this.props.setErrorDefaultValue();
}