I have a component that triggers some methods that depend on async API request. I use componentWillmount to check some props. If this prop is true I trigger some function otherwise false. But the problem is, that first time the prop is undefined, only after some time it will become false or true. How to check it and wait until request resolved?
componentWillMount = () => {
this.props.init(parseInt(this.props.tagId, 16), this.props.accessToken);
if (!this.props.notFound && !this.props.person) this.props.onFreeTag();
};
Use componentWillReceiveProps lifecycle function, something like:
componentWillReceiveProps = (nextProps) => {
if (!nextProps.notFound && !nextProps.person)
nextProps.onFreeTag()
}
}
It appears that first time when the component loads or get called your are passing it some value which is undefined initially and later becomes available. For example lets say you have a parent component as following
class Parent extends React.Component {
constructor() {
this.state = {0}
}
render() {
<Child value={this.state.value} />
}
}
As you can see, initially the state doesn't have a property value so the Child will receive undefined for this.props.value. It will only receive not undefined when some parent function changes it like this,
class Parent extends React.Component {
constructor() {
this.state = {0}
}
onAction() {
this.setState({value: true})
}
render() {
<Child value={this.state.value} />
}
}
So if on some event parent calls its OnAction it will change the state and Child will get this.props.value as true but as Child will be already rendered componentWillMount hook will not get triggered but componentWillReceiveProps will. So if you want to use the prop in componentWillMount make sure its passed at the first render of the child and if that is not possible use componentWillReceiveProps to process the props
Related
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.
After component gets rendered, I am calling updateResults method which is calling setState, after which getDerivedState is called and returning null, still state is getting updated and render and componentDidUpdate are called.
According to the docs, it shouldn't happen. Could anybody explain then why is it happening?
class Results extends React.Component{
constructor(props){
super(props);
this.state = {"query":props.data.web_query,"other_fields":props.other_fields};
}
static getDerivedStateFromProps(props, state) {
if (state.query != props.data.web_query) {
console.log("Returning new state as expected");
state.query = props.data.web_query;
return state;
}
console.log("Returning null, shouldn't change state, but changing");
return null;
}
componentDidUpdate() {
console.log("Componenent Did update");
}
updateResults(){
let results = someAjaxCall();
this.setState({"query":results.data.webquery,
"other_fields":results.other_fields});
}
render(){
<SomeComponent updateCall={this.updateResults}/>
}
}
Also, do explain how setState is related to getDerivedStateFromProps & shouldComponentUpdate?
I think you didn't understand the purpose of getDerivedStateFromProps()
This method is used to update the state if the props change.
Even if the method is called after setState, if getDerivedStateFromProps return null the state will be updated with the data inside the setState but getDerivedStateFromProps can override some properties.
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.
I have no clue why this.role is undefined in render.
export default class Dashboard extends Component {
componentDidMount() {
this.role = window.localStorage.getItem('role')
console.log('role', this.role) //return admin
}
render(){
console.log('role', this.role) //return undefined
return(
<div>
Component
</div>
)
}
}
I checked the localStorage of my app and it has value.
what happens is that at the initial render, render() method is called (before componentDidMount() is called), so it shows 'undefined'.
changing the value of 'this.role' won't re-render the page.
You will have to use state for this.
Below code should work I believe.
export default class Dashboard extends Component {
constructor(){
super()
this.state = {
role : undefined
}
}
componentDidMount() {
this.setState({role: window.localStorage.getItem('role')})
console.log('role', this.role) //return admin
}
render(){
console.log('role', this.state.role) //return undefined
return(
<div>
Component
</div>
)
}
}
It's returning undefined because you're setting this.role after the component is mount (componentDidMount). So the first render doesn't have this.role.
After componentDidMount is run you're not changing the state and the render is not running again (and therefore not getting the new info).
Try with componentWillMount instead, it should probably work.
Here's the React Lifecycle documentation.
Edit: Added code.
export default class Dashboard extends Component {
componentWillMount() {
this.role = window.localStorage.getItem('role')
console.log('role', this.role) // return admin
}
render(){
console.log('role', this.role) // now returning admin because this.role is set before the 1st render
return(
<div>
Component
</div>
)
}
}
As other users have pointed out, you can also use setState instead and it would also work (In that case, when the state changes the render is run again and your role is displayed accordingly).
You see undefined in the view because by the time the component has rendered there was nothing in role because componentDidMount is called after the initial render. Moreover, the component doesn't rerender after you have set role value from localStorage because it is not on the state. If you place role on the state and do this:
componentDidMount() {
this.setState({ role: window.localStorage.getItem('role')});
}
render(){
console.log('role', this.state.role)
return(
<div>
Component
</div>
)
}
then you will be able to see value of role in the view, but it will cause extra rerender of the component since you will change its state, according to react docs about componentDidMount:
Calling setState() in this method will trigger an extra rendering, but
it will happen before the browser updates the screen.
You can read more about componentDidMount here.
Update:
In your case you don't have to put role on the state, but then you can fetch its value from the constructor:
constructor(props) {
super(props);
this.role = window.localStorage.getItem('role');
}
and it will be available in the view.
I want to use the state of my child Component in my parent component in this example . what i want exactly to be able to do is this:
<Application onChange={(myVar)=>{console.log(myVar)}}/>
You're onChange is just a normal prop which you pass into your Application component. So if it's a function you can call it whenever you want. In this case, it makes more sense to call it when Application's state is updated. You can use the second parameter of setState function which is an optional callback function for this. But just make sure you onChange prop is defined and it's a function before you call it with any parameter which you want to pass.
class Application extends React.Component {
constructor(props){
super(props);
this.state = {myVar:0};
setInterval(()=>{
this.setState( { myVar:this.state.myVar + 1 }, () => {
if(this.props.onChange && typeof this.props.onChange === "function")
this.props.onChange(this.state.myVar)
} );
}, 3000);
}
//.....
}
Updated Codepen: https://codepen.io/anon/pen/gWvKxa