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)
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 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.
`
import React, {Component} from 'react';
import {connect} from 'react-redux';
import Chart from '../components/chart.js';
import GoogleMap from '../containers/googlemap';
import {removeCityMap} from '../actions/index';
import { bindActionCreators } from "redux";
import AttributeSelector from './attributeselector'
class WeatherBoard extends Component{
constructor(props){
super(props);
this.state = {
chartData1: []
}
}
weatherAttrMapper(attrName){
//Setting the state which is based on the data received from action dispatched.
this.setState({
chartData1:this.props.weather[0].list.map(weather => weather.main[attrName])
})
}
renderMapList(cityData){
//Based on the weather prop received when action is dispached I want to set the state before rendering my <chart> element.
this.weatherAttrMapper('temp');
return(
<tr key = {cityName}>
<td><Chart data = {this.state.chartData1} color = "red" units="K"/></td>
</tr>
)
}
render(){
return(
<table className="table table-hover">
<tbody>
{this.props.weather.map(this.renderMapList.bind(this))}
</tbody>
</table>
)
}
}
//This is the weather state coming from my reducer which is being used here.
function mapStateToProps({ weather }) {
return { weather };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ removeCityMap }, dispatch);
}
export default connect(mapStateToProps,mapDispatchToProps)(WeatherBoard);
`I have a doubt regarding state management.
Problem statement: I have a container abc.JS file which is mapped to redux state by mapStateToProps.
I have a action handler on button click which fetches data from API. When my actionhandler is dispatched it hits my render method in abc.js. Now my ask is I am maintaining state in abc.js as well which is also being used in render method and this need to be modified when action is dispatched. So how can I setstate my abc.js state which could also be rendered.
I have also added the exact code snippet of my abc.js. So basically I enter my render method when action is dispatched and I here I need to setstate somehow.
Thanks.
It depends what you need to do, if you just need to dispatch an action and set the state "at the same time" then something like this would work:
...
handleOnClick = () => {
this.props.actions.myAction();
this.setState({ myStateField: "myNewValue" });
}
render() {
return (
<div>
<h1>MyContainer</h1>
<button onClick={this.handleOnClick}>Click me</button>
</div>
);
}
If instead you need to dispatch an action then based on how the state in your manager is updated you need to also update the state, then you can use a simple lifecycle method like componentWillReceiveProps there you can read the new props and even compare the to the old props and act upon it, meaning that based on that you can then update your state. Let me know if this helps otherwise I can update my answer ;)
EDIT:
After reading your comment, this is what you want to do:
...
componentWillReceiveProps(nextProps) {
// In here you can also use this.props to compare the current props
// to the new ones and do something about it
this.setState({ myStateField: nextProps.propFromRedux });
}
...
Ideally, if you are dependent on state value which is being set from using props. Use componentWillReceiveProps lifecycle hook to setState . it will do the job. It gets triggered whenever there is a props change in update phase
before dispatching your action you can just say this.setState{} and it will modify your component state and once the action dispatches it again cause a re-render because this action may change some of the store's state.
I'm trying to learn from reading this app's code, and I am confused how you get dispatch from the props in this line of code:
_handleSubmit(e) {
e.preventDefault();
const { email, password } = this.refs;
const { dispatch } = this.props;
dispatch(Actions.signIn(email.value, password.value));
}
https://github.com/bigardone/phoenix-trello/blob/master/web/static/js/views/sessions/new.js#L17
Hoping someone can explain how calling this.props will return a dispatch?
react-redux is a library that helps components get values from the Redux store in a predictable and performant way. The main tool it provides is a function called connect, which wraps Redux components providing them with store values as props. The key part of the code you link to is at the bottom: https://github.com/bigardone/phoenix-trello/blob/master/web/static/js/views/sessions/new.js#L70-L74.
Say you have a value in the Redux store named counter. You want your component CounterDisplay to know about this value, and update when it changes:
class CounterDisplay extends Component {
render () {
const { counter, dispatch } = this.props
return (
<div>{counter}</div>
)
}
}
Those variables are going to be undefined unless you've explicitly put the values into props the 'old fashioned way':
<CounterDisplay counter={1} dispatch={() => {}} />
That's where connect comes in. It knows about the Redux store (often using another component called Provider) and can place values from it into the props of the component it's wrapping. It returns what's called a Higher Order Component (HOC): one that wraps another to perform a specific function, in this case connection to the store.
Here's how we'd get the counter value into props:
function mapStateToProps (state) {
// Slightly confusingly, here `state` means the entire application
// state being tracked by Redux... *not* CounterDisplay's state
return {
counter: state.counter
}
}
export default connect(mapStateToProps)(CounterDisplay)
So instead of exporting CounterDisplay itself, we export the HOC. In addition to counter, connect will also automatically insert the dispatch function into props so we can make use of it in the component. That's the behaviour you're seeing in the source you're reviewing.
const { dispatch } = this.props; is just deconstructing this.props.dispatch into a dispatch variable so it's used from props and where do they come to props? From react-redux connect:
export default connect(mapStateToProps)(SessionsNew);
connect is just Higher Order Component which basically connects your component with the store. As part of this process it puts dispatch into component's props
Edit:
The main idea is that connect is a function that takes whatever components and extends it's props with dispatch property (it returns another react components that wraps your component). You can also map some properties from state to your component and bind actions with dispatch using mapDispatchToProps and mapStateToProps
Just an example of destructuring assignment. See more here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
I am struggling to get working on a React-redux application to pass a value via the state from one component on to another. I do not wish to use the reducer and/or dispatch because I am not calling a webservice, I just want to take a value from a textbox form to enable another control on the order component.
Firstly, I can grab the value from control, but when mapstatetoprops is called the variable I set and wish to add to the state is undefined. This also possibly explains why my other problem. On my other component the function to use props is never called because of the state-componentWillReceiveProps
Here is the relevant code snippet :
<ListItemContent>
<Control component={Textfield} model="somemodel" label="MyLabel" onBlur={this.onChangeOfValue}/>
</ListItemContent>
onChangeOfValue = (event) => {
this.setState({
newValueToPassAlong: event.target.value
}); //newValueToPassAlong is set in constructor
};
let mapStateToProps = (state) => {
return {
newValueToGive: state.newValueToPassAlong
} // This is undefined
};
export default connect(mapStateToProps)(form)
So, my question is how do add a new variable to a state using React-redux without the need of reducers, etc and with this can I access this variable in my other component?
Ok action creator preferences aside, if you want to sync something between two components which share a common parent, you have to bring that state into the parent.
class ParentComponent {
onItemChanged = (newData) => {
this.setState({ thing: newData });
}
render() {
return (
<div>
<ChildA onItemChanged={ this.onItemChanged } />
<ChildB thing={ this.state.thing } />
</div>
);
}
}
when you change the value in ChildA, use this.props.onItemChanged with the new value. In ChildB, that value will be synchronised in this.props.thing. React will handle all the Component/prop updates.
That all being said, I genuinely think there's nothing wrong with using an action creator/reducer.
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
Refer documentation for more about setState()