Im new in ReactJS and I'm having problem with the concept of state and re-render of component.
In my parent component, I have this state in constructor:
this.state = {
locale : 'en',
messages : ENGLISH.messages,
softwareInfo : {
software_name : 'Test Default 1.0',
default_dash : 1,
color_theme: '#009900'
}
}
In the Render, I load a component called DashAppBar passing the softwareInfo:
<DashAppBar softwareInfo = { this.state.softwareInfo} />
In DashAppBar construct, I read that state as property:
this.state = {
software_settings: props.softwareInfo,
[...]
}
And then I print the software name in the bar with this:
const software_name = (
<FormattedMessage
id="software_name"
defaultMessage="{software_name}"
values={{software_name:this.state.software_settings.software_name}}
/>
);
This work fine.
Unfortunally, parent component make an AJAX call that change softwareInfo:
success: function(result){
//window.software_settings = $.parseJSON(result);
//window.software_settings = result;
var settings = {
software_name:result.software_name,
default_dash:result.default_dash,
color_theme:result.color_theme
};
this.setState({softwareInfo : settings});
}
Calling setState should re-render the component but the child value remain the same as original and the new value got from AjaxCall are not displayed.
Where is the error? Change the parent state should update the child.
Thanks!
The issue is that you're passing the parent's state only in the child's constructor. If the parent updates props you either need to do componentWillReceiveProps and update the child state or just use the software_settings as prop directly.
For example, in the child component:
const software_name = (
<FormattedMessage
id="software_name"
defaultMessage="{software_name}"
values={{software_name:this.props.software_settings.software_name}}
/>
);
or with componentWillReceiveProps:
componentWillReceiveProps(nextProps) {
this.setState({ software_settings: nextProps.software_settings});
}
you need to set a componentWillReceiveProps hook on your child component to updated it. https://reactjs.org/docs/react-component.html#componentwillreceiveprops
Related
I want to update the state of a main component from the "A" component then push it to "B" component and then use it render to dynamically populate boxes.
Main Component :
constructor() {
super();
this.state = {
events:[],
alerts:[],
};
}
addEvent = newEvent => this.setState(state => {
const {events} = this.state
return [...events, newEvent]
});
addAlert = newAlert => this.setState(state =>{
const {alerts} = this.state
return [...alerts, newAlert]
});
render(){
const {events} = this.state
const {alerts} = this.state
console.log(events) // events are empty even after I pass and store it
// in SearchFlight Component
return(
<div >
<SearchFlight events={events} alerts={alerts} addAlert={this.addAlert} addEvent={this.addEvent} />
<Events events={events}/>
<Alerts />
</div>
);
}
SearchFlight Component(A component) :
handleSubmit= event =>{
const {addEvent} = this.props
const {addAlert} = this.props
event.preventDefault();
var newEvents=[];
var newAlerts=[];
var singleEvent={
event_name:'home',
date_time:'12-08-18 12:45 AM',
};
newEvents.push(singleEvent);
newAlerts.push("Remove the luggage tag");
addAlert(newAlerts);
addEvent(newEvents);
}
Then I have Event Component(B Component) which right now just have render method. I want to get the updated events here.
Problem : Getting empty events when I did console.log(events) in render method of Main Component
You aren't using the setState correctly in addEvents and addAlerts method. the callback pattern of setState needs to return an object
addEvent = newEvent => this.setState(state => {
const {events} = state
return { events: [...events, ...newEvent]}
});
addAlert = newAlert => this.setState(state =>{
const {alerts} = state
return {alerts: [...alerts, ...newAlert]
});
Also since events is an array of objects your need to iterate on them to render. Refer How to render an array of objects in React? answer for more details on how to do that
The State is private to the component.
If the state is passed to the child component using props, again props should not be mutated as per the reactjs guidelines.
You can only read the value in Component A from the props and modify that value to pass it further to the nested components. But can't modify the state of the Main Component from A.
Check this out: https://reactjs.org/docs/state-and-lifecycle.html
As pointed out by OneJeet React is all about top-down approach and the child component should not be aware if a parent component is stateless or stateful.
Passing the setState as a function and allowing the child component to call it, is bad practice. One way is to use redux and eliminate the need to update parent state from child component or re-structure the whole thing in a different way.
I'd like to know, how to handle the PropTypes Error when passing a component as a child:
Failed prop type: The prop `value` is marked as required in `ChildComponent`, but its value is `undefined`.
The render works as expected and it's passing the value prop correctly.
I suppose this happens because I am putting the component in the App component's render function without any props.
I am only passing those props to the ChildComponent when the ParentComponent maps over its children (which is the ChildComponent).
See the code: https://codesandbox.io/embed/r70r5z3j9q
Is there a way to prevent this from happening?
How should I be structuring my components?
Am I not supposed to passed components as children?
EDITED: Changed prop "name" to "value". To give it a more generic feel.
I tried to simplify the problem in the code.
I know I could pass the prop directly in App.
The use case would be when the parent is doing calculations and those calculations are supposed to be passed to the child. Without explicitly knowing what these children are.
That's why I'm using it as child in the first place.
You're using cloneElement and you're passing prop to it, not to original element. To fix it, pass props directly:
const App = () => (
<div>
<ParentComponent>
<ChildComponent name="bob" />
</ParentComponent>
</div>
);
You could easily pass component as a prop (not children) to you ParentComponent and render it only after it takes some heavy calculations:
const App = () => (
<div>
<ParentComponent component={ChildrenComponent} />
</div>
);
const ParentComponent extends React.Component {
state = { heavyComputationFinished: false } // initial state
componentDidMount() {
runYourHeavyComputations
.then(() => { this.setState({ heavyComputationsFinished: true }) })
}
render() {
const { component } = this.props
const { heavyComputationsFinished, name } = this.state
// return nothing if heavy computations hasn't been finished
if (!heavyComputationsFinished) { return null }
// we're getting this component (not its rendering call) as a prop
return React.render(component, { name })
}
}
essentially I have a form component for users to fill out. When they complete an action: onChange={onChange} it returns a value (child component);
function onChange(value) {
console.log("This is: ", value);
}
I want to pass the value to a state in the parent component (so that the from can be processed and form data sent). How can I do this? My constructor looks something like this (parent component);
constructor(props) {
super(props);
this.state = {
form: {
name: '',
age: '',
value: ''
}
};
}
Trying to learn react, please go easy as I'm unsure of how to do this. Would love any feedback! Thanks!
Please read the official documentation start guide carefully and patiently when you are a starter.
In react component, state is the internal state and props is the state passed from outside.
You could only modify the state by setState method.
As for the question you ask, you could define a callback function which call setState in parent component, then pass the callback function as props into the child component.
As Zhili said, you should define a function in your parent component that is passed as prop to the child component.
Here's a brief example:
const Child = ({ onSubmit }) => (
<form onSubmit={onSubmit}>
{ /* your <input/>'s here ... */}
</form>
);
class Parent extends React.Component {
state = {
value: null,
};
onChildSubmit = (value) => {
this.setState({
value,
});
}
render() {
return (
<div class="Something">
<Child onSubmit={} />
</div>
)
}
}
I'm having the following class that renders users based on a sort dropdown. Users will be listed in alphabetical order if i choose "alphabetical" and in group order when i choose "group".
render(){
return(
const {members, sort} = this.state
{ sort === "alphabetical" && <SortByAlphabet members={members} /> }
{ sort === "group" && <SortByGroup members={members}/> }
)
)
In <SortByAlphabet /> component I am setting a component state object from props.members in componentWillReceiveProps() function.
componentWillReceiveProps = props => {
this.setState({ members : props.members });
}
When I choose "group" sort, <SortByAlphabet /> component is unmounting and <SortByGroup /> is mounting in the DOM. Again when i switch back to "alphabetical" sort, the state variable (members) that was set previosly in <SortByAlphabet /> component becomes NULL because the component was removed from the DOM.
componentWillReceiveProps function is not triggering the second time when re-rendering <SortByAlphabet /> b'coz the props didn't change. But i want to update the state variable like i did it for the first time in componentWillReceiveProps function.
How to do that?
componentWillMount is called only once in the component lifecycle, immediately before the component is rendered. It is usually used to perform any state changes needed before the initial render, because calling this.setState in this method will not trigger an additional render
So you can update your staate using
componentWillMount ()
{
this.setState({ members : props.members });
}
As #Vikram also said, componentWillReceiveProps is not called for the first time, so when your component is initially mounted your state is not getting set, so you need to set the state with props in the componentWillMount/componentDidMount function(which are called only on the first render) also along with the componentWillReceiveProps function
componentWillReceiveProps = props => {
if(props.members !== this.props.members) {
this.setState({ members : props.members });
}
}
componentWillMount() {
this.setState({ members : this.props.members });
}
From version 16.3.0 onwards, you would make use of getDerivedStateFromProps method to update the state in response to props change,
getDerivedStateFromProps is invoked after a component is instantiated
as well as when it receives new props. It should return an object to
update state, or null to indicate that the new props do not require
any state updates.
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.members !== prevState.memebers) {
return { members: nextProps.members };
}
return null;
}
EDIT:
There has been a change in getDerivedStateFromProps API from v16.4 where it receives props, state as arguments and is called on every update along with initial render. In such a case, you can either trigger a new mount of the component by changing the key
<SortByAlphabet key={members} />
and in SortByAlphabet have
componentWillMount() {
this.setState({ members : this.props.members });
}
or use getDerivedStateFromProps like
static getDerivedStateFromProps(props, state) {
if(state.prevMembers !== props.members) {
return { members: nextProps.members, prevMembers: props.members };
}
return { prevMembers: props.members };
}
I have the following function:
update() {
this.currentItem = [];
//...Populate currentItem
this.setState({
currentItem
});
}
Which renders on the page like this;
render() {
const { currentItem } = this.state;
return {currentItem}
}
I then pass this function into a child component, like this:
<Component
update={this.update.bind(this)}
/>
and then call it like this in my child component:
let { update } = this.props;
if (typeof update === "function")
update();
The problem is that the update function does not re render the content I am updating on the parent page. As far as I understand this, whenever setState is called, render also gets called. render() does seem to be getting called, but it does not seem to display the updated value - why is this, and how can I resolve it?
I guess it could be to do with the fact that it is coming from a child component?
I have tried forceUpdate, but it does not re render the component either - what am I missing?
Try avoiding this.forceUpdate() because this will not fire shouldComponentUpdate() which is a performance hook for your Component in React. As, I saw that you are passing your state to child component and trying to update the parents state object from there, which against the law. You should create a method in parent and pass that method as a prop to the child component. It should look like this
constructor(props) {
super(props);
this.state = { loading: false };
this.update = this.update.bind(this);
}
update(newState) {
this.setState({loading: newState })
}
render() {
return <ChildComponent update={this.update} />
}
I am just guessing here but i think you set the initial value for the child component in the constructor and the value you want it to reflect points to its own state instead of the parents state
I needed to set the state to loading first, then when I set it to loading = false, it would re render that page
this.setState({
loading:true
});
//...Do the work
this.setState({
loading:false
});