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
Related
I think I'm missing a concept here about React and Redux. I'm trying to work with objects stored in redux, and I'm having trouble.
REDUX:
I have an action fetchItems, that gets all items from the database. This action works successfully.
REACT:
I have a container, UserProfile, that calls fetchItems in componentDidMount.
class UserProfile extends Component {
componentWillMount() {
console.log('------------ USER PROFILE -------------------');
}
componentDidMount() {
console.log('[ComponentDidMount]: Items: ', this.props.items);
this.props.fetchItems();
}
render() {
let profile = null;
console.log('[Render]: Items: ', this.props.items);
return <Auxillary>{profile}</Auxillary>;
}
}
const mapStateToProps = state => {
return {
items: state.items.items
};
};
const mapDispatchToProps = dispatch => {
return {
fetchItems: () => dispatch(actions.fetchItems())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
The problem I'm seeing is that this.props.items is always null (even though fetchItems is successful). The only way I can detect that items were stored in redux store is if I use componentWillRecieveProps(nextProps). Here, I successfully see the items in nextProps. I feel like using componentWillReceiveProps might be too "messy" though. I guess what I'm asking is, what is the standard way of dealing with updates to redux states in react?
Aseel
The cycle will be :
constructor()
componentWillMount() (will be soon deprecated by the way : https://medium.com/#baphemot/whats-new-in-react-16-3-d2c9b7b6193b)
render() => first render (this.props.items, coming from mapStateToProps will be undefined)
componentDidMount() => launching fetchItems() => changing redux state => changing the this.props.items => launching the second render() where this.props.items will be set.
So :
you should have two console.log('[Render]: Items: ', this.props.items);
you should deal with a "loading" state when the this.props.items is null
If the second console.log is still null, Try to add log in your reducer, in the mapStateToProps, ... perhaps it's not state.items.items ...
In react, we have something called state. if the state of a component is changed the component will re-render. Having said that we can use this.setState() inside componentWillRecieveProps to update the state which in turn will rerender the component. So your code will look like this which is the standard way to handle Redux level state changes in react.
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
items: props.items
}
}
componentWillMount() {
console.log('------------ USER PROFILE -------------------');
}
componentWillRecieveProps({ items }) {
this.setState({ items });
}
componentDidMount() {
console.log('[ComponentDidMount]: Items: ', this.state.items);
this.props.fetchItems();
}
render() {
let profile = null;
console.log('[Render]: Items: ', this.state.items);
return <Auxillary>{profile}</Auxillary>;
}
}
const mapStateToProps = state => {
return {
items: state.items.items
};
};
const mapDispatchToProps = dispatch => {
return {
fetchItems: () => dispatch(actions.fetchItems())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
P.S Just making the API call inside componentWillMount will not help either as API call is async and can take up some time to resolve and till then react will finish rendering the component. so you'll still have to use componentWillRecieveProps
Standard practice is to call this.props.fetchItems() in your constructor or componentWillMount().
componentDidMount is called after render which is why your items do not render - they do not exist until after the initial render.
There are certain ways you can resolve this.
The very first time when render() gets called it was subscribed to the initial props/state that was initialise in redux store through redux connect method. In your case items was null.
Always initialise your redux store with some meaningful data.
In your case if items will be array you can initialise with empty array.
When you dispatch action your store will get updated and the component which was subscribed to items will be re rendered and in this way you donot have to use setState inside componentWillReceiveProps and you can avoid using it.
You need to handle certain cases in render like if array is empty and data is still loading then show some kind of loader and once data is fetched then display it.
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. :)
I am new to React/Redux and have a problem with state.
TrajectContainer.jsx
class TrajectContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
trajects: props.trajects,
onClick: props.onClick
};
}
componentWillReceiveProps(nextProps) {
console.log('componentWillReceiveProps', nextProps);
this.setState(nextProps);
}
render() {
// When the componentWillReceiveProps is not present, the this.state will hold the old state
console.log('rerender', this.state);
return (<div className="col-md-6">
<h2>Trajects</h2>
<button className="btn btn-primary" onClick={this.state.onClick}>Add new Traject</button>
{this.state.trajects.map(traject => <Traject traject={traject} key={traject.name}/>)}
</div>)
}
}
const mapStateToProps = function (store) {
console.log('mapToStateProps', store);
return {
trajects: store.trajects
};
};
const mapDispatchToProps = function (dispatch, ownProps) {
return {
onClick: function () {
dispatch(addTraject());
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TrajectContainer);
When a reducer returns a new state, the component will rerender with the new data.
However: if I remove the componentWillReceiveProps function, the render() function has the old state.
I checked the data received in mapStateToProps, and this is new New State.
So I don't understand why I need the componentWillReceiveProps function in order for the render function to receive the new data.
Am I doing something wrong?
componentWillReceiveProps is required if you want to update the state values with new props values, this method will get called whenever any change happens to props values.
In your case why you need this componentWillReceiveProps method?
Because you are storing the props values in state variable, and using it like this:
this.state.KeyName
That's why you need componentWillReceiveProps lifecycle method to update the state value with new props value, only props values of component will get updated but automatically state will not get updated. If you do not update the state then this.state will always have the initial data.
componentWillReceiveProps will be not required if you do not store the props values in state and directly use:
this.props.keyName
Now react will always use updated props values inside render method, and if any change happen to props, it will re-render the component with new props.
As per DOC:
componentWillReceiveProps() is invoked before a mounted component
receives new props. If you need to update the state in response to
prop changes (for example, to reset it), you may compare this.props
and nextProps and perform state transitions using this.setState() in
this method.
React doesn't call componentWillReceiveProps with initial props during
mounting. It only calls this method if some of component's props may
update.
Suggestion:
Do not store the props values in state, directly use this.props and create the ui components.
Update
componentDidUpdate()
should now be used rather than componentWillReceiveProps
also see an article from gaearon re writing resilient components
There are two potential issues here
Don't reassign your props to state that is what you are using redux for pulling the values from the store and returning them as props to your component
Avoiding state means you no longer need your constructor or life-cycle methods. So your component can be written as a stateless functional component there are performance benefits to writing your component in this way.
You do not need to wrap your action in dispatch is you are passing mapDispatcahToProps. If an object is passed, each function inside it is assumed to be a action creator. An object with the same function names, but with every action creator wrapped into a dispatch will be returned
Below is a code snippet that removes the state from your component and relies on the state that has been returned from the redux store
import React from "react";
import { connect } from "react-redux";
const TrajectContainer = ({ trajects, addTraject }) => (
<div className="col-md-6">
<h2>Trajects</h2>
<button className="btn btn-primary" onClick={addTraject}>Add new Traject</button>
{trajects.map(traject => <Traject traject={traject} key={traject.name} />)}
</div>
);
const mapStateToProps = ({ trajects }) => ({ trajects });
export default connect( mapStateToProps, { addTraject })(TrajectContainer);
In your case you will require componentWillReceiveProps and you have to update the state when you receive new props. Because
In your constructor, you have declared your state as below. As you can see you construct your state using the props that are passed in. (This is why you require componentWillReceiveProps and the logic to update it there)
this.state = {
trajects: props.trajects,
onClick: props.onClick
};
So when your props, changes componentWillReceiveProps is the function that gets called. The constructor does not gets called. So you have to set the state again so that the changes goes into the state of the component.
However your logic should be as below. With a condition so that you can prevent repeated state updates if its called multiple times.
componentWillReceiveProps(nextProps) {
console.log('componentWillReceiveProps', nextProps);
if (this.props !== nextProps) {
this.setState(nextProps);
}
}
One should store the props into state, only if you are going to modify the content of it. But in your case i see that there is no modification. So you can directly use this.props.trajects directly instead of storing it into the state and then using it. This way you can get rid of the componentWillReceiveProps
So your render function will use something like below
{this.props.trajects.map(traject => //what ever is your code.... }
I had similar issue add withRouter() like this:
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(TrajectContainer));
The problem with your implementation is that you are duplicating your Redux store state (comming from the props) into your React state (this.state)
In your example, if store.trajects is updated, then this.props.traject will be updated and a render will be triggered only if this.props.traject is used in your render method (not the case).
Since you are using the state instead of the prop in your render method, you have to manually change the state of you component using this.setState to trigger a render.
This is not a good pattern: I would advise not to use the state, and use directly the props like this:
class TrajectContainer extends React.Component {
render() {
return (<div className="col-md-6">
<h2>Trajects</h2>
<button className="btn btn-primary" onClick={this.props.onClick}>Add new Traject</button>
{this.props.trajects.map(traject => <Traject traject={traject} key={traject.name}/>)}
</div>)
}
}
const mapStateToProps = function (store) {
console.log('mapToStateProps', store);
return {
trajects: store.trajects
};
};
const mapDispatchToProps = function (dispatch, ownProps) {
return {
onClick: function () {
dispatch(addTraject());
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TrajectContainer)
I'm working on a react app with a flux implementation.
I have a store which is bound to a component and in the ctor I set some default (blank) state values. In componentWillMount I populate the state by firing some actions which update the store data.
The store emits a change and the component handles that change by putting bits of the store data into state.
In my render method, I'm wanting the render to depend on the state data.
At the moment I have a couple of issues.
If in my render method I do something like this.state.MyThing.AProperty then the render method is called too early when MyThing hasn't been populated yet. This seems to occur in a lot of places where I want a render to use state data. Is there a sensible guard against this or am I doing this wrong?
I'm using a store to emit a change, and handling that change by getting data from the store and setting it to the state of the component. My thinking here is that if I set it as state then the component will know to re-render when the state changes. Is this correct? or should I be getting the data from the store in the emit handler and using it directly? or setting it to a local var in the component?
The reason I ask is that I seem to encounter issues with setState calls not being immediate and wanting to use state as soon as I set it. With this in mind it seems like I might be doing it wrong.
Any thoughts greatly appreciated.
If you use conditionals in your render, then you can guard against unpopulated data being rendered.
<div>
{typeof this.state.myThing == 'object' ?
<strong>this.state.myThing.aProperty</strong> :
<span>Nothing to see here</span>}
</div>
And with regards to your second question, yeah. That's totally fine and it's the expected way to work with Flux. You can even take inspiration from Redux & Co and make higher order components that map store state to props.
function connect(store, mapStateToProps, Component) {
return React.createClass({
getInitialState() {
const state = store.getState();
return { state };
},
componentWillMount() {
store.listen(state => this.setState({ state }));
},
render() {
const stateProps = mapStateToProps(this.state);
const passedProps = this.props;
const props = Object.assign({}, stateProps, passedProps);
return <Component {...props} />;
}
});
}
This pattern allows you to take an existing component and wrap it in a container that will re-render whenever the store changes, then use the mapStateToProps function to work out which props to pass down to your original component.
const MyStore = { ... };
const MyComponent = React.createClass( ... );
function mapStateToProps(state) {
return { foo: state.bar.foo };
}
export default connect(MyStore, mapStateToProps, MyComponent);
setState is an asychronous method as it needs to be batched to keep React apps from being delaying repaints when they trigger lots of updates. You can reliably wait for the state to change by passing a callback as the second argument.
this.setState({ foo: 'bar' }, () => this.state.foo);
I'm trying to separate a presentational component from a container component. I have a SitesTable and a SitesTableContainer. The container is responsible for triggering redux actions to fetch the appropriate sites based on the current user.
The problem is the current user is fetched asynchronously, after the container component gets rendered initially. This means that the container component doesn't know that it needs to re-execute the code in its componentDidMount function which would update the data to send to the SitesTable. I think I need to re-render the container component when one of its props(user) changes. How do I do this correctly?
class SitesTableContainer extends React.Component {
static get propTypes() {
return {
sites: React.PropTypes.object,
user: React.PropTypes.object,
isManager: React.PropTypes.boolean
}
}
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
render() {
return <SitesTable sites={this.props.sites}/>
}
}
function mapStateToProps(state) {
const user = userUtils.getCurrentUser(state)
return {
sites: state.get('sites'),
user,
isManager: userUtils.isManager(user)
}
}
export default connect(mapStateToProps)(SitesTableContainer);
You have to add a condition in your componentDidUpdate method.
The example is using fast-deep-equal to compare the objects.
import equal from 'fast-deep-equal'
...
constructor(){
this.updateUser = this.updateUser.bind(this);
}
componentDidMount() {
this.updateUser();
}
componentDidUpdate(prevProps) {
if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID (this.props.user.id !== prevProps.user.id)
{
this.updateUser();
}
}
updateUser() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Using Hooks (React 16.8.0+)
import React, { useEffect } from 'react';
const SitesTableContainer = ({
user,
isManager,
dispatch,
sites,
}) => {
useEffect(() => {
if(isManager) {
dispatch(actions.fetchAllSites())
} else {
const currentUserId = user.get('id')
dispatch(actions.fetchUsersSites(currentUserId))
}
}, [user]);
return (
return <SitesTable sites={sites}/>
)
}
If the prop you are comparing is an object or an array, you should use useDeepCompareEffect instead of useEffect.
componentWillReceiveProps() is going to be deprecated in the future due to bugs and inconsistencies. An alternative solution for re-rendering a component on props change is to use componentDidUpdate() and shouldComponentUpdate().
componentDidUpdate() is called whenever the component updates AND if shouldComponentUpdate() returns true (If shouldComponentUpdate() is not defined it returns true by default).
shouldComponentUpdate(nextProps){
return nextProps.changedProp !== this.state.changedProp;
}
componentDidUpdate(props){
// Desired operations: ex setting state
}
This same behavior can be accomplished using only the componentDidUpdate() method by including the conditional statement inside of it.
componentDidUpdate(prevProps){
if(prevProps.changedProp !== this.props.changedProp){
this.setState({
changedProp: this.props.changedProp
});
}
}
If one attempts to set the state without a conditional or without defining shouldComponentUpdate() the component will infinitely re-render
You could use KEY unique key (combination of the data) that changes with props, and that component will be rerendered with updated props.
componentWillReceiveProps(nextProps) { // your code here}
I think that is the event you need. componentWillReceiveProps triggers whenever your component receive something through props. From there you can have your checking then do whatever you want to do.
I would recommend having a look at this answer of mine, and see if it is relevant to what you are doing. If I understand your real problem, it's that your just not using your async action correctly and updating the redux "store", which will automatically update your component with it's new props.
This section of your code:
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Should not be triggering in a component, it should be handled after executing your first request.
Have a look at this example from redux-thunk:
function makeASandwichWithSecretSauce(forPerson) {
// Invert control!
// Return a function that accepts `dispatch` so we can dispatch later.
// Thunk middleware knows how to turn thunk async actions into actions.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
You don't necessarily have to use redux-thunk, but it will help you reason about scenarios like this and write code to match.
A friendly method to use is the following, once prop updates it will automatically rerender component:
render {
let textWhenComponentUpdate = this.props.text
return (
<View>
<Text>{textWhenComponentUpdate}</Text>
</View>
)
}
You could use the getDerivedStateFromProps() lifecyle method in the component that you want to be re-rendered, to set it's state based on an incoming change to the props passed to the component. Updating the state will cause a re-render. It works like this:
static getDerivedStateFromProps(nextProps, prevState) {
return { myStateProperty: nextProps.myProp};
}
This will set the value for myStateProperty in the component state to the value of myProp, and the component will re-render.
Make sure you understand potential implications of using this approach. In particular, you need to avoid overwriting the state of your component unintentionally because the props were updated in the parent component unexpectedly. You can perform checking logic if required by comparing the existing state (represented by prevState), to any incoming props value(s).
Only use an updated prop to update the state in cases where the value from props is the source of truth for the state value. If that's the case, there may also be a simpler way to achieve what you need. See - You Probably Don't Need Derived State – React Blog.