I'm trying to call a function from application startup. The function reads data from JSON via dataVar (set elsewhere) and tries to load it into {items} for further consumption:
const dataVar = JSONStuff;
class Global extends Component {
constructor(props) {
super(props);
this.state = {
query: '',
items: []
}
this.init();
}
// componentDidMount() {
// This doesn't work either!
// this.init();
// }
init() {
let { items } = dataVar;
this.setState({items});
}
render() {
return (
<div className="Global">
<Gallery items={this.state.items}/>
</div>
)
}
}
Then in Gallery.js:
import React, { Component } from 'react';
class Gallery extends Component {
render() {
return (
<div>
<h3>gallery:</h3>
{
this.props.items.map((item, index) => {
let {title} = item.name;
return (
<div key={index}>{title}</div>
)
})
}
</div>
)
}
}
export default Gallery;
Not sure why Global can't call a function inside of itself. I've tried with and without "this." I either get error to where the app won't complile or I get:
"Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op."
First of all, it's a warning, you probably better not call setState in componentDidMount.
My suggestion 1: assign value to state in constructor
constructor(props) {
super(props);
this.state = {
query: '',
items: dataVar.items,
};
}
Suggestion 2:
Do inside the componentWillReceiveProps
componentWillReceiveProps(nextProps) {
const { dataVar: items } = nextProps; // pass dataVar as props
this.setState({
items,
});
}
Plus try to debug your props and pay attention on your console for errors.
Related
I am a little confused on the idea of using props in the context I am using for my React app. In my component, I need to check if the value of a certain prop (props.companyCode) matches a certain string, and only then will it print out a <p> of what I need. Below is what I have for calling the prop in the component:
Components/CompanyContact.jsx
class CompanyContact extends React.Component {
help() {
if (this.props.companyInfoList.companyCode === '1234') {
return <p>something</p>;
}
return <p>somethingelse</p>;
}
render() {
const help = this.help();
return (
<div>
{help};
</div>
)}}
export default CompanyContact;
And this is what I have for the container:
Container/InfoContainer.jsx
class InfoContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
companyInfoList: null,
};
}
async componentWillMount() {
const companyInfoCachedData = CachingService.getData('companyInfoList');
if (companyInfoCachedData) {
this.setState({ companyInfoList: companyInfoCachedData });
return;
}
}
async getCompanyInfo(accessToken) {
try {
const companyProfileResponse = await requestAWSGet('api/company-profile', undefined, accessToken);
CachingService.setData('companyInfoList', companyProfileResponse);
this.setState({ companyInfoList: companyProfileResponse });
} catch (err) {
throw err;
}
}
render() {
return (
<CompanyContact companyInfoList={this.state.companyInfoList} />
);
}
}
export default InfoContainer;
Nothing is returned when I run the application and I believe it's because I'm not calling the prop correctly in my component but I am unsure as to how to go about fixing it. I'm fairly new to working with props so still trying to get my bearings.
I'm assuming you are getting an error somewhere because of this not having props and this.props.companyInfoList.companyCode trying to access a property on a non object. this.props.companyInfoList is initially set to null so accessing a property on it will break.
A few strategies to fix the problem:
Default it to an empty object
this.state = {
companyInfoList: {},
}
Block the rendering of the component until it has a value:
if (this.state.companyInfoList) {
return (
<CompanyContact companyInfoList={this.state.companyInfoList} />
);
} else {
return null;
}
Check that the prop is an object and has the key companyCode on it:
if (this.props.companyInfoList &&
this.props.companyInfoList.companyCode &&
this.props.companyInfoList.companyCode === '1234') {
In addition, this will be in the wrong context and the changes above will most likely no be enough. Try changing to an arrow function like this:
help = () => {
// your code here
}
I would personally refactor that component logic and directly use the prop value inside the render method like:
class CompanyContact extends React.Component {
render() {
const { companyInfoList } = this.props;
return companyInfoList && companyInfoList.companyCode === '1234' ? (
<p>something</p>
) : (
<p>somethingelse</p>
)
}
}
export default CompanyContact;
I want to fetch data from server periodically and refresh rows when data is fetched by setState() but the rows doesn't re-render after setState().
constructor(props) {
super(props);
this.state = {
rows: []
}
this.refreshList = this.refreshList.bind(this);
}
refreshList() {
req.get('/data').end(function (error, res) {
// type of res is array of objects
this.setState({
rows: res
});
});
}
// call this method on button clicked
handleClick() {
this.refreshList();
}
render() {
return(
<div>
<button onClick={this.handleClick}>Refresh List</button>
<Table rows={this.state.rows}/>
</div>
);
}
when call refreshList() new feteched data doesn't render.
My table component is:
// Table component
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Thanks a lot for your help. How can I refresh list on click button?
Your table component never changes its state after the construction. You can fix it easily by updating the state from new props:
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
componentWillReceiveProps(newProps) {
this.setState({
rows: newProps.rows
});
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
However, if your table component is so simple, you can make it stateless and use props directly without setState():
export class Table extends Component {
render() {
return (
<div>
{this.props.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Note there is no need for constructor now. We could actually make it a functional component.
Use an arrow function:
req.get('/data').end((error, res)=> {
// type of res is array of objects
this.setState({
rows: res
});
});
With the ES5 style callback function, the context of this gets lost.
You could also bind this directly to a local variable, i.e., var that = this and stick with the function syntax, but I think most would agree what the ES6 arrow syntax is nicer.
I have a simple react + redux login page. When auth is successful i redirect the user to home page. This initially brought up an issues of "Warning: setState(...): Can only update...". After searching around, a proposed solution was to setTimeout on the redirect function to give a small delay in order for the states to be set.
I am looking for a more elegant way to redirect a user without using setTimeout as it can be a little unpredictable depending on situation and without causing "setState(...)" warning .
Below is a condensed version of the code:
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
//...
};
}
render() {
let {isLoginSuccess } = this.props;
return (
<div><form>
//...
{isLoginSuccess && (
<div>Success {(setTimeout(() => this.goHome()), 1)}</div>
)}
</form></div>
);
}
goHome = function() {
this.props.history.push('/');
};
Since isLoginSuccess is a prop, you could check for it in componentWillReceiveProps function and redirect from there
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
//...
};
}
componentWillReceiveProps(nextProps) {
const {isLoginSuccess} = this.props;
if(!isLoginSuccess && nextProps.isLoginSuccess) {
this.props.history.push('/');
}
}
render() {
let {isLoginSuccess } = this.props;
return (
<div><form>
//...
{isLoginSuccess && (
<div>Success </div>
)}
</form></div>
);
}
If you are not using React-Router, you can rewrite your code to use other life-cycle events, specifically componentDidMount to do this. Do not cause any state changes in render.
Example:
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
//...
};
}
goHome() {
this.props.history.push('/');
}
componentDidMount(){
let {isLoginSuccess } = this.props;
if (isLoginSuccess){
this.goHome();
}
}
render() {
let {isLoginSuccess } = this.props;
return (
<div><form>
//...
{isLoginSuccess && (
<div>Logged-in successfully, please wait while we take you to home...</div>
)}
</form></div>
);
}
}
My parent component is like this:
export default class MobileCompo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
datasets: {}
};
this.get_data = this.get_data.bind(this);
}
componentWillMount() {
this.get_data();
}
async get_data() {
const ret = post_api_and_return_data();
const content={};
ret.result.gsm.forEach((val, index) => {
content[val.city].push()
});
this.setState({data: ret.result.gsm, datasets: content});
}
render() {
console.log(this.state)
// I can see the value of `datasets` object
return (
<div>
<TableElement dict={d} content={this.state.data} />
<BubbleGraph maindata={this.state.datasets} labels="something"/>
</div>
)
}
}
child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
console.log(this.props);
// here I can't get this.props.maindata,it's always null,but I can get labels.It's confusing me!
}
componentWillMount() {
sortDict(this.props.maindata).forEach((val, index) => {
let tmpModel = {
label: '',
data: null
};
this.state.finalData.datasets.push(tmpModel)
});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I tried many times,but still don't work,I thought the reason is about await/async,but TableElement works well,also BubbleGraph can get labels.
I also tried to give a constant to datasets but the child component still can't get it.And I used this:
this.setState({ datasets: a});
BubbleGraph works.So I can't set two states at async method?
It is weird,am I missing something?
Any help would be great appreciate!
Add componentWillReceiveProps inside child componenet, and check do you get data.
componentWillReceiveProps(newProps)
{
console.log(newProps.maindata)
}
If yes, the reason is constructor methos is called only one time. On next setState on parent component,componentWillReceiveProps () method of child component receives new props. This method is not called on initial render.
Few Changes in Child component:
*As per DOC, Never mutate state variable directly by this.state.a='' or this.state.a.push(), always use setState to update the state values.
*use componentwillrecieveprops it will get called on whenever any change happen to props values, so you can avoid the asyn also, whenever you do the changes in state of parent component all the child component will get the updates values.
Use this child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
}
componentWillReceiveProps(newData) {
let data = sortDict(newData.maindata).map((val, index) => {
return {
label: '',
data: null
};
});
let finalData = JSON.parse(JSON.stringify(this.state.finalData));
finalData.datasets = finalData.datasets.concat(data);
this.setState({finalData});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I'm new to React. I'm stuck on this, would really appreciate some help!
A parent component will pass an array into this child component. When I console.log(this.props.repairs) it shows me an array of 4. I am trying to update this.state.sortedDataList whenever the array of repairs is passed in. The console.log(this.state) is still showing sortedDataList as an empty array.
What am I doing wrong? Thanks so much, appreciate any help.
class Repairs extends React.Component {
constructor(props) {
super(props);
this.state = {
sortedDataList: []
};
}
componentWillReceiveProps(nextProps) {
if(this.props != nextProps) {
this.setState({
sortedDataList: this.props.repairs
});
}
}
render() {
console.log(this.props);
console.log(this.state);
return (
<div></div>
);
}
}
Never mind, found my silly mistake! If anyone else gets stuck on this in the future...
componentWillReceiveProps(nextProps) {
if(this.props != nextProps) {
this.setState({
sortedDataList: nextProps.repairs
});
}
}
componentWillReceiveProps isn't called on the first render. That is the reason that you don't see any update in the state
From the React Docs:
"Invoked when a component is receiving new props. This method is not called for the initial render."
If you want to make the change only first time you can make use of componentWillMount lifecycle function and update the state. On subsequent changed you componentWillReceiveProps will be called.
class Repairs extends React.Component {
constructor(props) {
super(props);
this.state = {
sortedDataList: []
};
}
componentWillMount() {
this.setState({
sortedDataList: this.props.repairs
}, () => console.log(this.state.sortedDataList));
}
componentWillReceiveProps(nextProps) {
if(this.props != nextProps) {
this.setState({
sortedDataList: nextProps.repairs
});
}
}
render() {
console.log("r",this.props);
console.log("r",this.state);
return (
<div></div>
);
}
}
class App extends React.Component {
render() {
var arr = ["1", "2", "3"];
return (
<div >
<Repairs repairs={arr}/>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="app"></div>
In your constructor
this.state = {
sortedDataList: []
};
You initially set state to an empty array, so on first render it'll be empty. Then, whenever the props are changed, it'll get updated via componentWillReceiveProps().
As Shubham said, componentWillReceiveProps() isn't called on the first render. If you want the state to reflect the props right from the first render, you'd have to put it in the constructor like so:
this.state = {
sortedDataList: this.props.repair
};