I wrote the dropdown component that passes a selected value back to parent via callback function. From there I would like to simply render the selected value below the dropdown. Instead I have rendered previous state. I have no idea why that works like that, could someone explain me my app's behaviour and maybe give a hint how to fix it? I don't even know where to look for the answers.
index.js
import React, { Component } from 'react';
import './App.css';
import { Dropdown } from './components/dropdown'
class App extends Component {
state = {
response: "",
currA: ""
};
componentDidMount() {
this.callApi()
.then(res => this.setState({ response: res.express }))
.catch(err => console.log(err));
}
callApi = async () => {
const response = await fetch('/main');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
};
calculateRate = (currA) => {
this.setState({currA: currA});
}
render() {
return (
<div className="App">
<div>
<Dropdown callbackFromParent={this.calculateRate}/>
</div>
<p>
{this.state.currA}
</p>
</div>
);
}
}
export default App;
dropdown.js
import React from 'react';
export class Dropdown extends React.Component {
constructor(props){
super(props);
this.state = {
list: [],
selected: ""
};
}
componentDidMount(){
fetch('https://api.fixer.io/latest')
.then(response => response.json())
.then(myJson => {
this.setState({ list: Object.keys(myJson.rates) });
});
}
change(event) {
this.setState({ selected: event.target.value });
this.props.callbackFromParent(this.state.selected);
}
render(){
var selectCurr = (curr) =>
<select
onChange={this.change.bind(this)}
value={this.state.currA}
>
{(this.state.list).map(x => <option>{x}</option>)}
</select>;
return (
<div>
{selectCurr()}
</div>
);
}
}
Since your setState() is not a synchronous call, it might be that your callback is firing before the state of your dropdown is actually modified. You could try using the callback on setState...
change(event) {
this.setState({
selected: event.target.value
}, () => {this.props.callbackFromParent(event.target.value)});
;
}
...Or if your parent component is the only thing that cares about the selected value (my guess from your snip), you don't need to update the dropdown state at all.
change(event) {
this.props.callbackFromParent(event.target.value;)
}
Good luck!
Documentation:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
Related
So, i am trying to pass data from child component to parent component. I was able to do this through using props and is able to print the data using console.log. However, when i try to setState in the parent component using this data from child component. I am stuck in infinite loop of updating state which caused error "Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops."
My code is of below
Parent component
import React, { Component } from 'react'
import TagsPaper from './tagsPaper'
import CurrentTagPaper from './currentTagPaper'
import ApiCall from './Backend/apiCall';
export default class Tags extends Component {
constructor(props) {
super(props);
this.state = {
tags: ["apple"],
toUpdate: true,
};
}
componentDidMount() {
}
getTags = (e) => {
const array = e;
console.log(array)
this.setTags(array)
return array
}
setTags(e) {
this.setState({
tags: e
})
}
render() {
return (
<div >
<p>
Get recommendations for articles that matches your
interests when you follow more tags:
</p>
<b>
You are following
</b>
<TagsPaper></TagsPaper>
<b>
From this article
</b>
{/* pass Tag array name here*/}
<CurrentTagPaper tagArray={this.state.tags} />
<ApiCall getTags={this.getTags} />
</div>
)
}
}
Child component
import React, { Component } from 'react';
import '../App.css';
export default class ApiCall extends Component {
constructor(props) {
super(props);
this.state = {
response: '',
post: '',
responseToPost: '',
tags: this.props.tags
};
this.sendTags = this.sendTags.bind(this);
}
componentDidMount() {
this.getTagApi()
}
getTagApi(){
this.callApi()
.then(res => this.setState({ response: res.express }))
.catch(err => console.log(err));
}
callApi = async () => {
const response = await fetch('http://localhost:5000/api/scrap');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
};
sendTags = (value) => {
this.props.getTags(value);
}
handleSubmit = async e => {
e.preventDefault();
const response = await fetch('http://localhost:5000/api/forms', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ post: this.state.post }),
});
const body = await response.text();
this.setState({ responseToPost: body });
};
render() {
let tagArray = [...this.state.response];
this.sendTags(tagArray);
return (
<div className="App">
{/* {tagArray.map(e => <p key={e}>{e}</p>)} */}
<form onSubmit={this.handleSubmit}>
<p>
<strong>Post to Server:</strong>
</p>
<input
type="text"
value={this.state.post}
onChange={e => this.setState({ post: e.target.value })}
/>
<button type="submit">Submit</button>
</form>
<p>{this.state.responseToPost}</p>
</div>
);
}
}
When the parent changes state, it renders the child. When the child renders, it updates the parent state. There would be a few clever ways to fix this, but your ultimate problem is that copying data into both a parent and child's state isn't very idiomatic.
Generally, you should "hoist" any state used by both parent and child to only exist in the parent, and this "hoisting" should happen right off the API call.
In ApiCall, you could switch to
getTagApi(){
this.callApi()
.then(res => this.sendTags(res.express))
.catch(err => console.log(err));
}
In your parent Tags component, you would need to pass the parents tag state back down.
<ApiCall getTags={this.getTags} tags={this.state.tags} />
Your ApiTags would then just use the parent's tags.
let tagArray = this.props.tags;
You also would not want to copy this.props.tags into this.state during construction.
React expects that there be one source-of-truth for all data. If a higher-level component needs the data as well as a lower-level component, you should always hoist the data, as in, pass it up whenever it changes, and pass it back down as a prop on every render.
I am using ComponentDidMount to call data from my database and render page when data is ready. However, i have noticed the speed of my application has reduced when navigating since i have to wait for the data.
This is happening when i have large data in the database i am retrieving. My question is, is there any way of optimizing this, or i just have to render page before data loads ?
Component.JS
componentDidMount()
{
this.fetchAllItems();
}
fetchAllItems(){
return this.fetchPost().then(([response,json]) => {
console.log('here now ',response);
console.log(localStorage.getItem('user_token'))
if(response.status === 200)
{
}
})
}
fetchPost(){
const URL = 'http://localhost:8000/api/';
return fetch(URL, {method:'GET',headers:new Headers ({
'Accept': 'application/json',
'Content-Type': 'application/json',
})})
.then(response => Promise.all([response, response.json()]));
}
Try to use axios to make call to API asynchronously, after it's done, just update your response data to state. No need to wait your page is finished loading or not, react will render by following changes of state value.
import React from 'react';
import axios from 'axios';
export default class MovieList extends React.Component {
state = {
movies: []
}
componentDidMount() {
axios.get(`http://localhost/movies`)
.then(res => {
const movies = res.data;
this.setState({ movies: movies });
})
}
render() {
const {
movies
} = this.state;
return (
<div>
<ul>
{ movies.map(movie => <li>{movie.name}</li>)}
</ul>
</div>
)
}
}
Have you tried the shouldComponentUpdate lifecycle method? This method accepts nextProps (new or upcoming props) and nextState (new or upcoming State) parameters. You can compare your next props and state (state preferably in your case) to determine if your component should re-render or not. Fewer re-renders equals to better speed and optimization. that means your pages will load faster. The shouldComponentUpdate method returns a boolean to determine if a page should re-render or not. Read more here. Also, Here's an example:
class App extends React.Component {
constructor() {
super();
this.state = {
value: true,
countOfClicks: 0
};
this.pickRandom = this.pickRandom.bind(this);
}
pickRandom() {
this.setState({
value: Math.random() > 0.5, // randomly picks true or false
countOfClicks: this.state.countOfClicks + 1
});
}
// comment out the below to re-render on every click
shouldComponentUpdate(nextProps, nextState) {
return this.state.value != nextState.value;
}
render() {
return (
<div>
shouldComponentUpdate demo
<p><b>{this.state.value.toString()}</b></p>
<p>Count of clicks: <b>{this.state.countOfClicks}</b></p>
<button onClick={this.pickRandom}>
Click to randomly select: true or false
</button>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
In your case all the optimization must be done in the backend.
But if there is something that can be done in React is using Should Component Update as previous comment mentioned.
I need to make a new api request to fetch data for a given dataId.
this value lives in the Context.
import { MyContext } from './Context'
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
dataId: this.context.state.dataId // tried setting state first but didn´t work.
}
this.details = this.details.bind(this)
}
details() {
fetch('https://api.mydomain.com/' + this.context.state.dataId)
.then(response => response.json())
.then(data => this.setState({ data: data }));
}
componentDidMount() {
this.details()
}
render() {
return(
<MyContext.Consumer>
{(context) => (
<div>data: {JSON.stringify(data)} dataId: {context.state.dataId}</div>
)}
</MyContext.Consumer>
)
}
}
MyComponent.contextType = MyContext;
export default MyComponent
from others components I can set new values like
this.context.setDataId(1)
and this will show up correctly but the problem is that is not making a new fetch to get new data for the dataId that changed in the Context.
not sure what´s the correct lifecycle method I can use to detect changes in the context and make a new call to this.details()
I didn´t add the Context code here because it works fine. but if you need to see it please let me know.
In react, you must use life cycle hooks to inspect data such as props or context, to know if the state needs to update for your component. The most common life cycle hook for this purpose is componentDidUpdate(). it gives you the ability to decide whether or not your component needs to update state/rerender based on changes in props that caused the component to update. the following should work for your use case:
import { MyContext } from './Context'
class MyComponent extends Component {
state = {
data:[],
dataId:null
}
details = () => {
// we only want to update if dataId actually changed.
if(this.context.state.dataId !== this.state.dataId){
this.setState({dataId:this.context.state.dataId});
fetch('https://api.mydomain.com/' + this.context.state.dataId)
.then(response => response.json())
.then(data => this.setState({ data: data }));
}
}
componentDidMount() {
this.details()
}
componentDidUpdate() {
this.details();
}
render() {
return(
<MyContext.Consumer>
{(context) => (
<div>data: {JSON.stringify(this.state.data)} dataId: {context.state.dataId}</div>
)}
</MyContext.Consumer>
)
}
}
MyComponent.contextType = MyContext;
export default MyComponent;
I'm learning react and it's great, but i've ran into an issue and i'm not sure what the best practice is to solve it.
I'm fetching data from an API in my componentDidMount(), then i'm setting some states with SetState().
Now the problem is that because the first render happens before my states have been set, im sending the initial state values into my components. Right now i'm setting them to empty arrays or empty Objects ({ type: Object, default: () => ({}) }).
Then i'm using ternary operator to check the .length or if the property has a value.
Is this the best practice or is there some other way that i'm unaware of?
I would love to get some help with this, so that i do the basics correctly right from the start.
Thanks!
I think the best practice is to tell the user that your data is still loading, then populate the fields with the real data. This approach has been advocated in various blog-posts. Robin Wieruch has a great write up on how to fetch data, with a specific example on how to handle loading data and errors and I will go through his example here. This approach is generally done in two parts.
Create an isLoading variable. This is a bolean. We initially set it to false, because nothing is loading, then set it to true when we try to fetch the data, and then back to false once the data is loaded.
We have to tell React what to render given the two isLoading states.
1. Setting the isLoading variable
Since you did not provide any code, I'll just follow Wieruch's example.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: null,
};
}
componentDidMount() {
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ dataFromApi: data.dataFromApi }));
}
...
}
export default App;
Here we are using the browser's native fetch() api to get the data when the component mounts via the use of componentDidMount(). This should be quite similar to what you are doing now. Given that the fetch() method is asynchronous, the rest of the page will render and the state will be up dated once the data is received.
In order to tell the user that we are waiting for data to load, we simply add isLoading to our state. so the state becomes:
this.state = {
dataFromApi: null,
isLoading: false,
};
The state for isLoading is initially false because we haven't called fetch() yet. Right before we call fetch() inside componentDidMount() we set the state of isLoading to true, as such:
this.setState({ isLoading: true });
We then need to add a then() method to our fetch() Promise to set the state of isLoading to false, once the data has finished loading.
.then(data => this.setState({ dataFromAPi: data.dataFromApi, isLoading: false }));
The final code looks like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: [],
isLoading: false,
};
}
componentDidMount() {
this.setState({ isLoading: true });
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ dataFromApi: data.dataFromApi, isLoading: false }));
}
...
}
export default App;
2. Conditional Rendering
React allows for conditional rendering. We can use a simple if statement in our render() method to render the component based on the state of isLoading.
class App extends Component {
...
render() {
const { hits, isLoading } = this.state;
if (isLoading) {
return <p>Loading ...</p>;
}
return (
<ul>
{dataFromApi.map(data =>
<li key={data.objectID}>
<a href={data.url}>{data.title}</a>
</li>
)}
</ul>
);
}
}
Hope this helps.
It Depends.
suppose you are fetching books data from server.
here is how to do that.
state = {
books: null,
}
if, your backend api is correctly setup.
You will get either empty array for no books or array with some length
componentDidMount(){
getBooksFromServer().then(res => {
this.setState({
books: res.data
})
})
}
Now In Your render method
render() {
const { books } = this.state;
let renderData;
if(!books) {
renderData = <Spinner />
} else
if(books.length === 0) {
renderData = <EmptyScreen />
}
else {
renderData = <Books data = { books } />
}
return renderData;
}
If you are using offline data persistence In that case initially you won't have empty array.So This way of handling won't work.
To show the spinner you have to keep a variable loader in state.
and set it true before calling api and make it false when promise resolves or rejects.
finally read upon to state.
const {loader} = this.state;
if(loader) {
renderData = <Spinner />
}
I set initial state in constructor. You can of course set initial state of component as static value - empty array or object. I think better way is to set it using props. Therefore you can use you component like so <App items={[1,2,3]} /> or <App /> (which takes value of items from defaultProps object because you not pass it as prop).
Example:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [], // or items: {...props.items}
};
}
async componentDidMount() {
const res = await this.props.getItems();
this.setState({items: res.data.items})
}
render() {
return <div></div>
}
};
App.defaultProps = {
items: []
}
Can someone help me solve how do I setState inside componentDidUpdate and not have an infinite loop? Some suggestions said to have a conditional statement, but I am not too familiar with how do I set the conditional for my code.
This is what my code looks like:
I have a dashboard component that gets all the companies and projects data from external functions where the fetch happens and then updates the state. The projects are associated with the company's id.
I am able to get the list of all the projects in JSON, but I can't figure out how to update my projects state inside componentDidUpdate once rendered.
CompanyDashboard.js
import { getCompanys } from "../../actions/companyActions";
import { getProjects } from "../../actions/projectActions";
class CompanyDashboard extends Component {
constructor(props) {
super(props);
this.state = {
companies: [],
projects: []
};
}
componentWillMount() {
// get all companies and update state
getCompanys().then(companies => this.setState({ companies }));
}
componentDidUpdate(prevState) {
this.setState({ projects: this.state.projects });
}
render() {
const { companies, projects } = this.state;
{
companies.map(company => {
// get all the projects
return getProjects(company);
});
}
return <div />;
}
}
export default CompanyDashboard;
companyActions.js
import { getUser, getUserToken } from './cognitoActions';
import config from '../../config';
export function getCompanys() {
let url = config.base_url + '/companys';
return fetch(url, {
method: 'GET',
headers: {'token': getUserToken() }
})
.then(res => res.json())
.then(data => { return data })
.catch(err => console.log(err));
}
projectActions.js
import { getUserToken } from './cognitoActions';
import config from '../../config';
export function getProjects(company) {
let url = config.base_url + `/companys/${company._id['$oid']}/projects`;
return fetch(url, {
method: 'GET',
headers: {'token': getUserToken() }
})
.then(res => res.json())
.then(data => { return data })
.catch(err => console.log(err));
}
The following code is not doing anything meaningful. You are setting your state.projects to be equal to your state.projects.
componentDidUpdate() {
this.setState({ projects: this.state.projects })
}
Also, the following code is not doing anything meaningful because you are not saving the result of companies.map anywhere.
{
companies.map((company) => {
return getProjects(company)
})
}
It's hard to tell what you think your code is doing, but my guess is that you think that simply calling "companies.map(....) " inside your render function is going to TRIGGER the componentDidUpdate function. That is not how render works, you should go back to the drawing board on that one. It also looks like you think that using the curly brackets {} inside your render function will display the objects inside your curly brackets. That's also not true, you need to use those curly brackets inside the components. For instance: {projects}
If I had to guess... the following code is how you actually want to write your component
import { getCompanys } from '../../actions/companyActions';
import { getProjects } from '../../actions/projectActions';
class CompanyDashboard extends Component {
constructor(props) {
super(props);
this.state = {
companies: [],
projects: []
}
}
componentWillMount() {
getCompanys().then(companies => {
const projectPromises = companies.map((company) => {
return getProjects(company)
});
Promise.all(projectPromises).then(projects => {
//possibly a flatten operator on projects would go here.
this.setState({ companies, projects });
});
/*
* Alternatively, you could update the state after each project call is returned, and you wouldn't need Promise.all, sometimes redux can be weird about array mutation in the state, so look into forceUpdate if it isn't rerendering with this approach:
* const projectPromises = companies.map((company) => {
* return getProjects(company).then(project => this.setState({projects: this.state.projects.concat(project)}));
* });
*/
)
}
render() {
const { companies, projects } = this.state;
//Not sure how you want to display companies and projects, but you would
// build the display components, below.
return(
<div>
{projects}
</div>
)
}
}
export default CompanyDashboard;
componentDidUpdate has this signature, componentDidUpdate(prevProps, prevState, snapshot)
This means that every time the method gets called you have access to your prevState which you can use to compare to the new data, and then based on that decide if you should update again. As an example it can look something like this.
componentDidUpdate(prevProps, prevState) {
if (!prevState.length){
this.setState({ projects: this.state.projects })
}
}
Of course this is only an example since I don't know your requirements, but this should give you an idea.
When componentDidUpdate() is called, two arguments are passed:
prevProps and prevState. This is the inverse of
componentWillUpdate(). The passed values are what the values were,
and this.props and this.state are the current values.
`componentDidUpdate(prevProps) {
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}`
You must check the state/props if new state/props different from previous one then you can allow to update your component.
You may call setState() immediately in componentDidUpdate() but
note that it must be wrapped in a condition like in the example above,
or you’ll cause an infinite loop. It would also cause an extra
re-rendering which, while not visible to the user, can affect the
component performance. If you’re trying to “mirror” some state to a
prop coming from above, consider using the prop directly instead.
This is because componentDidUpdate is called just after a component takes up somechanges in the state. so when you change state in that method only then it will move to and from from that method and state change process