I want to ask why the child ( ComponentWillMount() ) component is only once rendered, once I am passing props to it everytime on onClick.
Once I click some button that is passing props to the child, the ComponentWillMount() of child is not triggering again, only in the first click only.
Parent Component:
render(){
return(
<div>
<AppModuleForm
editID = {this.state.editID}
editURL = {this.state.editURL}
editConf = {this.state.editConf}
editDesc = {this.state.editDesc}
editIcon = {this.state.editIcon}
editParent = {this.state.editParent}
editOrder= {this.state.editOrder}
status={this.state.status}
moduleList={this.state.moduleList}
updateAppModuleTree={this.updateAppModuleTree.bind(this)}/>
</div>
)
}
Child Component:
constructor(props){
super(props)
console.log(this.props.editDesc)
this.state={
url:'',
description:'',
parentID:'',
order:'',
configuration:'',
icon:'',
parentIDList:[],
urlDuplicate: false,
isSuccess: false,
errorMessage: '',
}
}
componentWillMount(){
if(this.props.status==='edit'){
let {
editURL,
editDesc,
editParent,
editConf,
editIcon,
editOrder} = this.props
this.setState({
url:editURL,
description:editDesc,
parentID:editParent,
order:editOrder,
configuration:editConf,
icon:editIcon,
})
}
}
componentWillReceiveProps(nextProps){
if(nextProps.status != this.props.status){
if(this.props.status==='edit'){
let {
editURL,
editDesc,
editParent,
editConf,
editIcon,
editOrder} = this.props
this.setState({
url:editURL,
description:editDesc,
parentID:editParent,
order:editOrder,
configuration:editConf,
icon:editIcon,
})
}
}
}
ComponentWillMount is mounting lifecycle method which will be called before mounting your component hence initialisation can be done in that while ComponentWillReceiveProps will be called once props are changed and you will get changes in nextProps parameter.
First you need to read https://reactjs.org/docs/state-and-lifecycle.html
and understand where to use props and why you need to pass something into component state.
From http://lucybain.com/blog/2016/react-state-vs-pros/
So when would you use state?
When a component needs to keep track of information between renderings
the component itself can create, update, and use state.
So you shouldn't transfer to state anything that will not change internally during component live cycle. As I can see all props those you pass to component are most likely will not be changed from within the component, all callbacks and icons you should take from props in component jsx.
If you have some editable data that you pass into its props from parent, on component mount (use componentWillMount()) you can copy that data to component state.That means all data will be stored in component internally and will not being overwritten on every render() call from passed props.
If you need to check if new props contains changes you can use componentWillReceiveProps(newProps) and there you can compare newProps with this.props and and process changes if needed.
Also i can suggest you to rename component callbacks handlers with respect to naming best practices:
<div>
<AppModuleForm
handleEditID = {this.onEditID}
handleEditURL = {this.onEditURL}
handleEditConf = {this.onEditConf}
handleEditDesc = {this.onEditDesc}
handleEditIcon = {this.onEditIcon}
handleEditParent = {this.onEditParent}
handleEditOrder= {this.onEditOrder}
status={this.state.status}
moduleList={this.state.moduleList}
updateAppModuleTree={this.updateAppModuleTree.bind(this)}/>
</div>
And I dont see any reasonable purpose to declare or to store functions in components state. So you can consider to move your handlers this.state.editID
etc. to parent component this scope. Like that
onEditId = () => { /* function code */ }
If you use arrow function = () => it automatically binds to component this and you don't need to bind them manually like you do in
{this.updateAppModuleTree.bind(this)}
After all that may be you will understand more clearly how you should manage your components life cycle and your problem will no longer be relevant.
Related
Curently I have this structure.
Child1 <---- Parent ----> Child2
Parent component has this state
this.state = {
authenticationChecked: true,
isAuthenticated: true,
sideBarRetracted: false
}
this.retract = this.retract.bind(this);
this.unretract = this.unretract.bind(this);
and the following functions
retract() {
this.setState({
sideBarRetracted: true
}, () => {
console.log("retracted")
console.log(this.state.sideBarRetracted) //I can see this changing to true, but the child wont take it into effect, even tho if i hardcode it , it will
});
}
unretract() {
this.setState({
sideBarRetracted: false
})
console.log("unretracted")
}
Which is pased to both childs
<SideBar retract={this.retract} unretract={this.unretract} retracted={this.state.sideBarRetracted} {...this.props}/>
<PrivateRoute authed={this.state.isAuthenticated} path="/skadi" render={(props) => <Skadi retracted={this.state.sideBarRetracted} {...props}/>} />
Now my question is. I supposedly should receive that as a prop in a child component, for example lets say in the sidebar component which is a child. Whose constructor is this, and im supposed to receive the prop there and assign it to the state
constructor(props) {
super(props);
this.state = {
retracted: this.props.retracted
}
}
It works properly so far, but now I have the following issue. This same component has a function onClick() which will change that specific state
<FontAwesomeIcon className="registerIcon" icon={faArrowAltCircleLeft} onClick={this.props.retract} />
<FontAwesomeIcon className="expandicon-retracted" icon={faArrowAltCircleRight} onClick={this.props.unretract} />
But point is, this is not working, the state is not being updated in the parent component. I can change it manually (hardcoding) and I see the child component reacting to the change, but i cant make it react with the onclick() update
At the same time, I have my Child2 component, who has been passed the parent state as well. Will its state update if I update the parent component using CHILD1 ?
I have seen that i should use something like this in the child2 , but well it wont work so far (mainly because parent component isnt updated afaik)
constructor(props) {
super(props)
this.state = {
sideretracted: false
}
console.log("props in skadi")
}
componentWillReceiveProps(nextProps) {
this.setState({ sideretracted: nextProps.sideBarRetracted});
}
You shouldn't set the child state from the props received from the parent component.
Since you are updating the state of child component from the methods bound to the child components it isn't reflected in the parent component.
One thing you could do is pass a method from the parent to update the state of the parent component and the child state gets updated by itself since it will be passed the new state variable from parent as a prop.
You could have the toggleRetract method defined in the parent to set the parent state and pass it as a prop to the child component which would trigger it and set the parent state for you which will be passed down to the children components as props (like how you are passing the state props).
For example
function Toggle() {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(o => !o)
return <Switch on={on} onToggle={toggle} />
}
function Switch({on, onToggle}) {
return (
<div>
<div>The button is {on ? 'on' : 'off'}</div>
<button onClick={onToggle}>Toggle</button>
</div>
)
}
Here Toggle is the parent component and Switch is the child component and the parent component passes the on state as well as onToggle method which the child component uses to display and update the state respectively.
More info here
I have a parent component doing an AJAX call to get a JSON object. I've done a few console.log's to make sure that the data is correct in the parent component, but then when I pass through props, I get a value of:
ƒ data() {
return _this.state.data;
}
What I've done to this point seems simple enough so I can't find what the issue is.
Parent Component:
class InfoBox extends Component {
state = {
data: []
};
componentDidMount = () => {
this.loadDonationsFromServer();
setInterval(this.loadDonationsFromServer, this.props.pollInterval);
};
loadDonationsFromServer = () => {
$.ajax({
url: "https://jsonplaceholder.typicode.com/comments",
dataType: "json",
cache: false,
success: data => {
this.setState({ data });
},
error: (xhr, status, err) => {
console.error(status, err.toString());
}
});
};
render = () => {
return (
<React.Fragment>
<h1>Information</h1>
<InfoList
data={() => this.state.data}
/>
</React.Fragment>
);
};
}
export default DonationBox;
Child Component:
class InfoList extends Component {
constructor(props) {
super(props);
this.state = {
data: this.props.data
};
}
componentDidMount() {
console.log(this.state.data);
//logs: ƒ data() {
// return _this.state.data;
// }
}
render() {
return <div> Placeholder </div>;
}
}
export default InfoList;
I tried using bind in the child component but still got the same thing:
constructor(props) {
super(props);
this.state = {
data: this.props.data
};
this.checkData = this.checkData.bind(this);
}
componentDidMount() {
this.checkData();
}
checkData = () => {
console.log(this.state.data);
};
First, yes, you should change the data prop that you send to InfoList to be this.state.data rather than an anonymous function. So: <InfoList data={this.state.data} />
But, the main issue is in using componentDidMount in the child component, when really you should be using componentWillReceiveProps instead.
componentDidMount is only called once, and it doesn't wait for your AJAX
The componentDidMount lifecycle hook is invoked one time, before the initial render.
In your child component, at componentDidMount you are trying to log this.state.data - but this state is based on what was set in the constructor which was what was passed in as the data prop when you first mounted InfoList. That was [], because InfoBox had yet to receive back data from its Ajax call. To put it another way:
InfoList.componentDidMount() fired before InfoBox.loadDonationsFromServer() got back its response. And InfoList.componentDidMount() does not get fired again.
componentWillReceiveProps is called whenever props change
Instead, your child component should be using the componentWillReceiveProps lifecycle hook. This is invoked every time a component receives new props. Once the parent's state changes (after load donations returns), then it passes new props to the child. In componentWillReceiveProps, the child can take these new props and updates his state.
I have created a code sandbox that shows you through a bunch of log statements what happens when, along with what your props and state look like at various points in the lifecycle. Instead of actually doing an ajax fetch, I'm just doing a 2-second wait to simulate the fetch. In InfoList.js, the code for componentWillReceiveProps is currently commented out; this way, you can see how things work as they stand. Once you remove the comments and start using componentWillReceiveProps, you'll see how they get fixed.
Additional resources
This is a helpful article that pretty much describes the exact same issue you're facing.
An excellent quick reference for React lifecycle hooks is the React Cheat Sheet)
That is because the data prop that is being passed in is a function.
Change
<InfoList data={() => this.state.data} />
to
<InfoList data={this.state.data} />
Just a nit, you don't really need the constructor in your child component to define the state. Just define it the way you have in your parent component.
I have the following code that is not letting me set the state ever after getting mounted.
Here is the code
import React, { Component } from 'react';
import Messages from '../locale/en/Messages';
import '../styles/base.css';
class AlertService extends Component {
state = {
message: '',
classType: 'alert-info',
isMessageSet: false
}
Messages = new Messages();
componentDidMount = () => {
console.log('This has mounted'); // This is working
}
componentWillUnmount = () => {
console.log('Is this getting unounted ?'); // This is working, the component is not getting unmounted
}
setAlert = (key, type, isMessage, readMore) => {
let message = isMessage ? key : this.Messages[key];
let classType = 'alert-info';
if (type === 0) {
classType = 'alert-danger';
} else if (type === 1) {
classType = 'alert-success';
}
this.openMessage(message,classType);
}
openMessage = (message,classType) =>{
this.setState({
message: message,
classType: classType,
isMessageSet: true
});
}
closeMessage = () => {
this.setState({
message: '',
classType: 'info',
isMessageSet: false
});
}
render() {
let classes = this.state.classType + ' ' + 'alertBox';
return (this.state.isMessageSet ?
<div className={classes}>
<div className="col-md-11"> {this.state.message} </div>
<div className="col-md-1 closeAlert" onClick={this.closeMessage}> x </div>
</div>
: null
)
}
}
export default AlertService;
I am geting the following error when trying to call the function setAlert from outside this component.
However if I set the isMessageSet property to true then on clicking the X and calling the closeAlert method, it works fine.
componentDidMount indicates that the component is getting mounted and componentWillUnmount is never getting executed , I am not sure what is wrong here
Error Message
Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the AlertService component.
setState should not be called from outside the component. If you want to change the state from outside, use props.
And as the error message says, the component is not mounted. You could mount it by adding <AlertService /> to the Layout.
As you have mentioned in one of the comments that you are trying to call the setAlert function after instantiating the AlertService class, I suggest you to please take a look as to how you are doing that. The right way is:
this.AlertService = React.render(React.createElement(AlertService), mountnode) //mounts the component
this.AlertService.setAlert() // now you can call the function
Depending upon your usecase you can do like above.
The thing with react is that the child component's methods cannot be called by the parent component. Since those are meant to be private to the child and should therefore be handled by itself. As a hack though we can make use of refs to call the child component's methods but that is not a recommended usecase for refs. Thid can result in bugs in you application.
The best way to achieve the prupose as #vijayst suggested is to change state of your parent component on an alert(whenever message is received) and pass it as a prop to the child. Now update the state for the child under componentWillReceiveProps().
If I understand you correctly you said you tried calling setAlert from another component, it doesn't work but when you called closeMessage it works as expected, but then I noticed you called closeMessage in this same component which would work as expected, if you want to call a function that belongs to this component in another component then you need to import the component into this component then pass the function to it so you would be able to call the function in the component. For example:
import AnotherComponent from '../AnotherComponenet'
<AnotherComponent setAlert={this.setAlert} />
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
this.setState=this.setState.bind(this)
}
render() {
return (
<div>
<Hello name={this.state.name} />
<p>
Start editing to see some magic happen :)
</p>
<Child {...this}/>
</div>
);
}
}
child Component
var Child=(self)=>{
return(
<button onClick={()=>{
self .setState({
name:"viswa"
})
}}>Click </button>
)
here I am binding the setState function and send this as props to child component.This will change state of parent from child.Is this proper way?
But this is simple than passing function.If I want change many state change. this will be simple and faster way compare to passing function right ?
The correct way to do this is as simple as yours and does not violate best practices:
class App extends Component {
state = {
name: 'React',
};
render() {
return (
<div>
<Hello name={this.state.name} />
<p>
Start editing to see some magic happen :)
</p>
<Child onClick={() => this.setState({name: 'viswa'})}/>
</div>
);
}
}
const Child=({onClick})=>(
<button onClick={onClick}>Click</button>
);
You shouldn't pass the setState directly, as the setState function will not change the state immediately.
as the documents said:
Think of setState() as a request rather than an immediate command to
update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may batch or defer the update until later. So you'd better to manage the calling of setState function together as there may have competing of mutating parent's state. It is not recommended to pass this function to another component.
Encapsulating the calling of setState function in one class makes your code stronger.
As #yBrodsky said, you should rather pass down a function which does mutate the state. Here is an example:
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
this.update=this.update.bind(this);
}
update(nextState) {
this.setState(nextState);
}
render() {
return (
<Child updateParent={this.update} />
);
}
}
const Child = ({updateParent}) => (
<button onClick={() => updateParent({name: 'Foo bar'})}>Click</button>
);
You now have full control over the state of the Parent in the child. Just pass the object to be shallowly merged into the parent state. In this example, name in App will change to Foo bar on button click.
i guess in functional components its possible to send your setState to childrens and change parent state in them but not in class base components
This may seem as a bit of a redundant question but I'm trying to access the final state of a child in React, after it has updated. I've been looking into the React LifeCycle docs (I think that might be the issue, but not sure), searched high and low, and can't quite figure it out.
I've got a component which needs to access the (final) value of the state of a child, once that child has done some updating (AJAX request which then does a few this.setStates).
So far, I'm able to access the entire state of that child, accessing through a ref (Inside componentDidMount), but when I try to access a specific value of said state, it returns null or undefined.
Here's some example code to explain (I'll try to spare you as much useless code as possible):
class Layout extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
// This gives me the updated State where pageTitle = "Whatever"
console.log(this.refs.child1);
// However this gives me the initial State where pageTitle = null
console.log(this.refs.child1.state.pageTitle);
}
render(){
return (<div>
{React.cloneElement(
this.props.children,
{ref: 'child1'}
)}
</div>);
}
}
And here's the child component for reference (note: i'm using axios for my ajax requests):
export class ChildComponent extends Component {
constructor(props) {
super(props);
this.state = {
resultData: result,
pageTitle: null
}
}
componentDidMount(){
this.serverRequest = axios.get(apiUrl)
.then(function(result){
this.setState({
resultData: result,
pageTitle: result.pageTitle
});
}.bind(this))
}
render(){
return(<div>
{use of different this.state.resultData values works fine here}
</div>)
}
}
Appreciate any help that comes this way
To use a callback, add this code to the parent element:
handleAsyncDone(data) {
// Do whatever it is people do with data
}
And then pass that function to the child component, and in the childcomponent, add
this.props.handleAsyncDone(this.state);
Which will pass the child state back up to the parent.