How to update React component? - reactjs

I have a child object (element of list) which is rendered inside(?) the parent one. The component has the following properties (from JSON):
contract
{
id,
name,
}
But I need to add another one additional property which is filled in after an HTTP request with an external function to the API (for example, uuid) using one of the existing properties of an object.
My current React code looks the following way (only child component is provided):
class Contract extends Component {
constructor(props){
super(props);
this.state = {data: this.props.contract};
getUuidByName(this.state.data.name).then(val => {
this.state.data.uuid = val;
});
}
componentDidUpdate(){ }
render() {
return <tr>
<td>{this.state.data.id}</td>
<td>{this.state.data.name}</td>
<td>{this.state.data.uuid}</td>
</tr>
}
}
Everything rendered good except an additional property: uuid. Of course I do something wrong or don't do some important thing, but I have no idea what to do.

You are mutating state in the constructor. Never mutate state directly. If you are needing to set/initialize some state after it's been constructed, or mounted, then you should use the componentDidMount lifecycle method. Ensure you enqueue the state update via the this.setState method.
class Contract extends Component {
constructor(props){
super(props);
this.state = {
data: props.contract,
};
}
componentDidMount() {
getUuidByName(this.state.data.name).then(val => {
this.setState(prevState => ({
data: {
...prevState.data,
uuid: val,
},
}));
});
}
componentDidUpdate(){ }
render() {
return (
<tr>
<td>{this.state.data.id}</td>
<td>{this.state.data.name}</td>
<td>{this.state.data.uuid}</td>
</tr>
);
}
}

Do not modify state directly.
Because you're directly modifying the state, React isn't triggering a re-render.
Try the following in your constructor instead:
constructor(props){
super(props);
this.state = {data: this.props.contract};
getUuidByName(this.state.data.name).then(val => {
this.setState({
data: {
...this.state.data,
uuid: val
}
});
});
}

Related

Where to set state when I need that state in render?

I am getting this error below:
react_devtools_backend.js:2430 Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
From the error, I know I am getting it because I am setting state in the render.
But I am not sure where to set the state because I need that state element, developerTitle further down inside the render method.
Where can I put it if not in render?
Thanks!
Here is my code:
export default class Game extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
developerTitle: ''
}
}
render() {
const { indieDeveloperId } = this.props;
this.setState({ developerTitle: this.getDeveloperTitle(game.indieDeveloperId) });
<div>
<h3>{this.state.developerTitle}</h3>
...
...
</div>
}
//by-indie-developer/{indieDeveloperId
async getDeveloperTitle(indieDeveloperId) {
const r = await axios.get(`/api/developer/by-indie-developer/${indieDeveloperId}`);
const developerTitle = r.data;
this.setState({
...this.state, ...{
developerTitle: developerTitle
}
});
}
}
You can't set a state in render(). But you can set a state when the component is loaded using the componentDidMount() function.
Add a function with that name like this to your component:
componentDidMount() {
this.setState({ developerTitle: this.getDeveloperTitle(game.indieDeveloperId) });
}
You dont have to call the function. The state will automatically be set.

React: how to use setState and render component when prop changes

This app is supposed to filter words by a specific input. I want to call a function with setState() when rendering a component and technically it's working but there is warning in the console.
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
I guess that this is because I'm calling the function in the render function which I shouldn't, but what should I do instead?
class UsersList extends React.Component {
constructor(props) {
super(props);
this.state = {
allUsers: ["Michał", "Ania", "Kasia", "Tomek", "Hubert", "Jan", "Martyna", "Rafał", "Bartłomiej"],
filteredUsers: [],
input: null
}
}
filter() {
if (this.state.input !== this.props.inputValue) {
const filtered = this.state.allUsers.filter(user => user.toLowerCase().includes(this.props.inputValue));
this.setState({
filteredUsers: filtered.map(user => <li key={user}>{user}</li>),
input: this.props.inputValue
})
}
return this.state.filteredUsers;
}
render() {
this.filter()
return (
<ul>
{this.state.filteredUsers}
</ul>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = {input: ""};
this.handleInput = this.handleInput.bind(this);
}
handleInput(e) {
this.setState({input: e.target.value})
}
render() {
return (
<div>
<input onChange={this.handleInput} type="search"/>
<UsersList inputValue={this.state.input} />
</div>
);
}
}
The issue here is caused by changes being made to your component's state during rendering.
You should avoid setting component state directly during a components render() function (this is happening when you call filter() during your component's render() function).
Instead, consider updating the state of your component only as needed (ie when the inputValue prop changes). The recommended way to update state when prop values change is via the getDerivedStateFromProps() component life cycle hook.
Here's an example of how you could make use of this hook for your component:
class UsersList extends React.Component {
constructor(props) {
super(props);
this.state = {
allUsers: ["Michał", "Ania", "Kasia", "Tomek",
"Hubert", "Jan", "Martyna", "Rafał",
"Bartłomiej"],
filteredUsers: [],
input: null
}
}
/* Add this life cycle hook, it replaces filter(). Props are updated/incoming
props, state is current state of component instance */
static getDerivedStateFromProps(props, state) {
// The condition for prop changes that trigger an update
if(state.input !== props.inputValue) {
const filtered = state.allUsers.filter(user => user.toLowerCase().includes(props.inputValue));
/* Return the new state object seeing props triggered an update */
return {
allUsers: state.allUsers
filteredUsers: filtered.map(user => <li key={user}>{user}</li>),
input: props.inputValue
}
}
/* No update needed */
return null;
}
render() {
return (<ul>{this.state.filteredUsers}</ul>)
}
}
Hope this helps
The error is coming up as it could create an endless loop inside the component. As render method is executed whenever the state is updated and your function this.filter is doing a state update. Now as the state updates, your render method triggers the function again.
Best way to do that would be in lifecycle methods or maintain the uses in the App and make UserList a dumb component by always passing the list of filtered users for it to display.

Is this React and Axios then promise usage correct?

I am using axios for a React project, and I was wondering if the usage of then promise is correct in this case.
Basically, I use axios to fetch data from the database when the component renders.
class Participants extends React.Component{
constructor(props){
super(props);
this.state = {
databaseUsers: [],
}
this.getUsers = this.getUsers.bind(this);
}
getUsers(){
var users = axios.get('/users/get-users').then((response) => {
this.setState({databaseUsers: response.data});
});
}
componentWillMount(){
this.getUsers();
}
render(){
console.log(this.state.databaseUsers);
return(** html tree **);
}
}
What I observe is that the state of the component is set twice, once when the rendering occurs, and the then promise fires, and a second time when the promise is done fetching the data from the database and sets the state.
How do I get more control over this? Like actually wait for the data on the database, and then render?
Any tips are welcome.
There are other ways to implement what you did with several components.
But let's stick to this example.
There is nothing wrong to rendering twice, as you don't want to wait for the response and then display output.
You can have a loading flag so you could show a "loading" code and when loaded show the output.
Or you can have 1 parent component that manages the work:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
data: []
}
}
componentDidMount() {
this.setState({loading: true})
axios.get('/users/get-users').then((response) => {
this.setState({
loading: false,
data: response.data
})
});
}
render() {
if (this.state.loading) {
return <LoadingComponent />;
}
return <DataComponent data={this.state.data} />
}
}

React child component can't get props.object

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>
);
}
}

react immutability helper to render only changed subset of data

Please see the example here http://jsfiddle.net/8xzxkteu/1/
I'm trying to only render part of the data which is changed. In this example, state of component Main, data, is indexed by id and I am using react immutability helper to set only the changed one. But, if you click on the output, it renders all the children, as indicated by the counter. I though using immutability helper react can detect only part of the data changed hence only render it. I probably could use shouldComponentUpdate and compare object values for each child, but is there a better way doing this with immutability helper.
class Child extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this)
this.state = {
count: 0
};
}
componentWillReceiveProps(nextProps) {
var count = this.state.count + 1;
this.setState({ count: count });
}
onClick() {
this.props.onClick(this.props.name);
}
render() {
return <p onClick={this.onClick}>{this.props.name}: {this.props.value} {this.state.count}</p>;
}
}
class Main extends React.Component{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
this.state = {
data: {
"a" : "a",
"b" : "b",
}
};
}
handleChange(id) {
this.setState({
data: React.addons.update(this.state.data, { [id]: { $set: 'x' } })
});
}
render() {
const keys = Object.keys(this.state.data);
const children = keys.map(k => {
return <Child name={k} value={this.state.data[k]} onClick={this.handleChange}/>
})
return <div>
{children}
</div>;
}
}
React.render(<Main />, document.getElementById('container'));
When you change state of component react call shouldComponentUpdate of this component and if it is return true react call render of this component.
After that react call componentWillReceiveProps, then shouldComponentUpdate, then render (if shouldComponentUpdate return true) of all child component.
By default, if there no shouldComponentUpdate method, it is considered that it has returned true. It does not matter whether you use immutable data or not - react does not know about it.
If you have immutable data you want avoid rerender, you should use shouldComponentUpdate. You can use pure-render-decorator, for example – it's check component state and props.
But if you change your state in componentWillReceiveProps you still get rerender because componentWillReceiveProps is called before shouldComponentUpdate.

Resources