componentDidUpdate() working mostly, but not rendering new data - javascript

I am using the componentDidUpdate() method and for the most part, it is doing what it should. It runs the function to get the data from the API as well as logs it to the console. The problem is that it does not render the new data on the front end. The only time it renders the new data is if the component actually mounts (if the page is refreshed). I feel like I'm very close, but have hit a dead end. Here is my code:
import React from 'react';
import Nav from './Nav';
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
APIData: []
}
}
getAPIData() {
const url = `http://localhost:3001${this.props.location.pathname}`;
return fetch(url, {
method: 'GET',
mode: 'CORS',
headers: {
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
console.log(data);
return data;
}).catch(err => { console.log('Error: ', err) });
};
dataList() {
return (
<div>
{this.state.APIData.map((APIData) => (
<p> And the data returned is -> {APIData.firstName}
{APIData.lastName} !</p>
)
)}
</div>
)
}
componentDidMount() {
console.log(this.props.location.pathname);
this.getAPIData()
.then(data => {
console.log('in List.js ', data);
this.setState({
APIData: data
});
});
}
componentDidUpdate(prevProps, prevState) {
console.log(this.props.location.pathname);
// only update if the data has changed
this.getAPIData()
.then(data => {
if (prevProps.data !== this.props.data) {
this.setState({
APIData: data
});
}
console.log(data);
});
}
render() {
return (
<div>
<Nav />
<br />
<br />
<br />
<br />
<div>
{/* {this.state.APIData.map((APIData) => (
<p> And the data returned is -> {APIData.firstName}
{APIData.lastName} !</p>
)
)} */}
{this.dataList()}
</div>
</div>
);
}
}
export default List;

I think it may be this block:
if (prevProps.data !== this.props.data) {
this.setState({
APIData: data
});
}
Are you actually passing a data prop to this component?
If not, then it would be checking if undefined !== undefined and never executing.
If you are, then you might check if the data reference is actually changing, or you're just mutating the inside of the object.

Related

How to render new data (or GET new) after making a POST request with axios?

I'm using mockapi.io to practice with Axios API call
After I make a POST request, which create a new data, I want to render FlatList with the updated data. I'm thinking of making a new GET request to do that, but I'm not succeeded with it.
I need help
Here is where I call GET request, which already have mock data, and use FlatList to view it
ListScreen.js
class ListScreen extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
axios.get('insert url')
.then(res => {
this.setState({
data: res && res.data ? res.data : []
})
})
.catch(err => {
console.log('Run into problem')
})
}
render() {
const { data } = this.state;
return (
<View>
<FlatList
data={data}
renderItem={({ item }) => {
return (
<Item
name={item.lastName}
phone={item.phoneNumber}
/>
);
}}
keyExtractor={(item) => item.id}
/>
</View>
And here is where I call POST request
class Create extends Component {
handleSubmitData = (value) => {
console.log('check value: ', value)
axios.post('insert url', {
lastName: `${value.Name}`,
phoneNumber: `${value.Phone}`,
})
.then((response) => {
console.log('here is what you upload: ', response.data)
})
.catch((err) => {
console.log('Run into problem')
})
}
render() {
return (
<CreateForm
handleSubmitData={this.handleSubmitData}
/>
)
}
}
The CreateForm component looks something like this
class CreateForm extends Component {
render() {
const { handleSubmit } = this.props;
return (
<View>
<View>
<Field
name="Name"
component={}
/>
<Field
name="Phone"
component={}
/>
</View>
<View>
<Button
title='Save'
onPress={handleSubmit(this.props.handleSubmitData)}
/>
</View>
</View>
)
This can be done by lifting the state up, from ListScreen to ListScreen's parent.
In ListScreen, take out the state and move into its parent:
this.state = {
data: [],
}
Pass the state down to ListScreen as a prop, also pass the fetch function (see below):
<ListScreen data={this.state.data} fetchListData={this.fetchListData} />
Change ListScreen's render function to access the data from props:
const { data } = this.props;
Move the axios.get() out of ListScreen and into a method on the parent component.
class App extends Component {
fetchListData() {
axios.get('insert url')
.then(res => {
this.setState({
data: res && res.data ? res.data : []
})
})
.catch(err => {
console.log('Run into problem')
})
}
}
In ListScreen, call the function in componentDidMount:
componentDidMount() {
this.props.fetchListData();
}
Alternatively, you could call it in the parent component's componentDidMount(). This might be preferred if you render multiple ListScreen's for example.
Finally, in the Create component, pass the fetch function:
<Create fetchListData={this.fetchListData} />
Call it on successful creation:
.then((response) => {
this.props.fetchListData();
console.log('here is what you upload: ', response.data)
})
As I see in your code, when you are making a POST request it wont display an updated data to your screen because there is no GET request callback upon the success result.
Suggesting you are not using redux , in ListScreen.js you could wrap the GET request into a function and call it in componentDidMount(). It should be look like:
const getData () => {
axios.get('insert url')
.then(res => {
this.setState({
data: res && res.data ? res.data : []
})
})
.catch(err => {
console.log('Run into problem')
})
}
componentDidMount() {
this.getData()
}
Therefore, you need to pass or drill the GET request function into your child component as a props and use it in a POST request callback. The final POST request method should be look like:
handleSubmitData = (value) => {
console.log('check value: ', value)
axios.post('insert url', {
lastName: `${value.Name}`,
phoneNumber: `${value.Phone}`,
})
.then((response) => {
console.log('here is what you upload: ', response.data)
getData()
})
.catch((err) => {
console.log('Run into problem')
})
}
As you can see, after your POST request is finished, it will trigger a GET request to update your parent state, resulting a screen with an updated data. However, you have to make sure to pass your parameters correctly based on how your component structure is.

React js fetch API with componentDidMount()

I am learning React.js and trying to fetch API with fetch() and I tried to use componentDidMount() but I have a problem, you can see the pic at the end of the post.
import React, { Component } from 'react'
export default class App extends Component {
state = {
weather: []
};
fetchData() {
fetch('prevision-meteo.ch/services/json/rochelle-17')
.then((response) => response.json())
.then((obj) => {
console.log('javascript object: ', obj)
this.setState({ weather: obj.results});
})
.catch((error) => {
console.error(error)
})
}
componentDidMount() {
console.log('Le composant App est monté sur le DOM !')
this.fetchData();
}
render() {
return (
<div>
{this.state.weather&& this.state.weather.map((weatherRecord) => (
<div key={weatherRecord.city_info.name}>{weatherRecord.city_info.name}</div>
))}
Hello World !
<button /*onClick={() => this.fetchData()}*/> Click me</button>
</div>
)
}
}
I want to get the name of city_info in my page but didn't work!
This is the results in the console, can anyone help me?
Setting state is asynchronous so React is rendering before the state has been set. What you can do is put in a short circuit condition this.state.weather && to check if the weather array exists and only when it is available will the map operation be performed and you shouldn't get any errors.
import React, { Component } from 'react'
export default class App extends Component {
state = {
weather: []
};
fetchData() {
fetch('http://localhost:3000/rochelle-17.json')
.then((response) => response.json())
.then((obj) => {
//console.log('javascript object: ', obj)
this.setState({ weather: obj.results});
})
}
componentDidMount() {
console.log('Le composant App est monté sur le DOM !')
this.fetchData();
}
render() {
return (
<div>
{this.state.weather&& this.state.weather.map((weatherRecord) => (
<div key={weatherRecord.city_info.name}></div>
))}
Hello World !
<button /*onClick={() => this.fetchData()}*/> Click me</button>
</div>
)
}
}
Some Notes:
The newer versions of React support setting initial state like this, which is a bit cleaner:
state = {
weather: []
}
It's also good practice to catch errors in case the API call fails. You can simply use .catch like this after the last .then():
.catch((error) => {
console.error(error)
})
Since ES6 you don't need to use return for the <div> you are rendering. You can simply use map with curved brackets () instead of curly brackets {} to implicitly return
{this.state.weather&& this.state.weather.map((weatherRecord) => (
<div key={weatherRecord.city_info.name}></div>
))}
Try this
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
wheather: null
};
}
fetchData() {
fetch("http://localhost:3000/rochelle-17.json")
.then((response) => {
return response.json();
})
.then((obj) => {
//console.log('javascript object: ', obj)
this.setState({ wheather: obj });
});
}
componentDidMount() {
console.log("Le composant App est monté sur le DOM !");
this.fetchData();
}
render() {
return (
<div>
{this.state.wheather
&& <div key={this.state.wheather.city_info.name}>{this.state.wheather.city_info.name}</div>
}
Hello World !
<button /*onClick={() => this.fetchData()}*/> Click me !</button>
</div>
)
}
}

React Initiating component re-render after onChange event received data from Paste (ctrl+v)

I'm trying to initiate an API request upon paste of a URL into an input field and then show the result on the page.
According to documentation and this link on SOF, setState is the way to initiate re-render, I know and it seems I did it the right way myself, but something is off, I get the url state only when I do onChange again, React doesn't seem to show me my pasted data anywhere in any of the available lifecycle events.
Using create-react-app:
import React from "react";
import ReactDOM from "react-dom";
const UserInput = props => {
return (
<div>
<label>Enter URL:</label>
<input onChange={props.handleChange} type="text" value={props.value} />
</div>
);
};
class Fetch extends React.Component {
constructor() {
super();
this.state = {
url: null,
userData: null,
fetching: false,
error: null
};
}
componentDidUpdate() {
this.fetchData();
}
fetchData() {
fetch(this.state.url)
.then(result => result.json())
.then(json => this.setState({ userData: json }))
.error(error => console.log(error));
}
render() {
return this.props.render();
}
}
const UserProfile = ({ name, gender }) => {
return (
<div>
Hey {name}, you are {gender}!
</div>
);
};
class App extends React.Component {
constructor() {
super();
this.state = {
url: null
};
}
handleChange(e) {
this.setState({
url: e.target.value
});
}
render() {
return (
<div>
<UserInput
value={this.state.url}
handleChange={this.handleChange.bind(this)}
/>
<Fetch url={this.state.url} render={data => <UserProfile />} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
If you paste any URL in the field, you won't have it in state, so when fetchData is triggered its
this.state.url
is actually still null.
Thanks
Your Fetch component and App component are using two separate copies of the url state which causes the issue, you have to use the url you pass as prop to the Fetch component instead.
class Fetch extends React.Component {
constructor(props) {
super(props);
this.state = {
// url: null, remove this
userData: null,
fetching: false,
error: null
};
}
componentDidUpdate() {
this.fetchData();
}
fetchData() {
fetch(this.props.url) // update here
.then(result => result.json())
.then(json => this.setState({ userData: json }))
.error(error => console.log(error));
}
render() {
return this.props.render(userData); // the render prop is a function in your case that expects data
}
}
update the below line too so that the UserProfile gets the data that has been obtained from API. I am not sure about the keys
<Fetch url={this.state.url} render={data => <UserProfile name={data.name} gender={data.gender}/>} />

React app: Undefined value after fetch

I have the following code where i fetch data from twitter API feed. I use a call back function and setState the values to my state properties. When I use it in render to just console and see the value then it shows
"Cannot read property 'created_at' of undefined".
I think it is trying to fetch before it is even available. I don't know what to do here. Can someone please help.
When i use console.log(this.state.twitterfeed.techcrunch[0]) I do not get any error.
I got the object
But when I use console.log(this.state.twitterfeed.techcrunch[0].created_at) then I got error
class Columns extends Component {
constructor() {
super();
this.state = {
twitterfeed: {
techcrunch: [],
laughingsquid: [],
appdirect: []
}
};
}
updateTwitterFeed = (data, user) => {
var twitterfeed = { ...this.state.twitterfeed };
if (user === "appdirect") {
twitterfeed.appdirect = data;
} else if (user === "laughingsquid") {
twitterfeed.laughingsquid = data;
} else {
twitterfeed.techcrunch = data;
}
this.setState({ twitterfeed });
};
componentDidMount() {
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=techcrunch"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "techcrunch"));
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=laughingsquid"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "laughingsquid"));
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=appdirect"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "appdirect"));
}
render() {
return (
<div className="container mx-0">
<div className="row">
<div className="col-4 col-md-4">
{console.log(this.state.twitterfeed.techcrunch[0].created_at)}
<Column tweet={this.state.twitterfeed.techcrunch} />
</div>
</div>
</div>
);
}
}
this.state.twitterfeed.techcrunch[0] will be undefined before your fetch is complete, so trying to access created_at on that will give rise to your error.
You could e.g. render null until the techcrunch array has been filled after the request.
class Columns extends Component {
// ...
render() {
const { techcrunch } = this.state.twitterfeed;
if (techcrunch.length === 0) {
return null;
}
return (
<div className="container mx-0">
<div className="row">
<div className="col-4 col-md-4">
<Column tweet={techcrunch} />
</div>
</div>
</div>
);
}
}
I have a couple of recommendations here.
First of all, try to consolidate your fetch logic using Promise.all.
Take a look at the documentation here. This will make
fetch(..).then(..)
fetch(..).then(..)
fetch(..).then(..)
into
Promise
.all([fetch(..), fetch(..), fetch(..)])
.then(...) // all 3 responses here
Also when rendering a React component componentDidMount() runs after render(). Take a look at react lifecycle here.
So the solution to be sure that the data you want to render is available is to have a flag on state such as:
this.state = { loading: True, ... } // constructor
Promise
.all([...])
.then(...)
.then(args =>
this.setState({loading: False});
...) // componentDidMount()
render() {
if(this.state.loading)
return <div>Loading...</div>
return (
// your component with data already set into the state :)
)
}

how to distinguish which component called a callback function?

I'm new to react, sorry if that is newbe question. I have a component Dropdown which returns a value via a callback function. I would like to render that twice to choose two different values and then simply render chosen values below. How can I allow your two different components to send different data to the component. Below is my code.
index.js
import { Dropdown } from './components/dropdown'
class App extends Component {
constructor(props) {
super(props);
this.calculateRate = this.calculateRate.bind(this);
this.callApi = this.callApi.bind(this);
this.state = {
response: "",
currA: 0,
currB: 1
}
}
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 = (key, val) => {
// if the calling agent sent currA data, update currA,
// else if the calling agent sent currB data, update currB
if (key === 'A') this.setState({currA: val})
if (key === 'B') this.setState({currB: val})
console.log('updated curr' + key + ' to ' + val);
}
render() {
return (
<div className='App'>
<div>
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'A'} val={this.state.currA} />
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'B'} val={this.state.currB} />
</div>
</div>
);
}
}
export default App;
dropdown.js
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) });
});
}
render(){
var selectCurr = (curr) =>
<select
onChange={event => props.callbackFromParent(props.stateKey, event.target.value)}
>
{(this.state.list).map(x => <option>{x}</option>)}
</select>;
return (
<div>
{selectCurr()}
</div>
);
}
}
I'm not exactly sure what you're trying to achieve, but hopefully the following shows how you can allow your two different components to send different data to the <App> component.
The important changes are: we need to bind methods to the <App> component in the constructor() function, then we can use the .bind() method in the Dropdown component to specify the data to pass into the callback function:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.calculateRate = this.calculateRate.bind(this);
this.callApi = this.callApi.bind(this);
this.state = {
response: "",
currA: 0,
currB: 1
}
}
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 = (key, val) => {
// if the calling agent sent currA data, update currA,
// else if the calling agent sent currB data, update currB
if (key === 'A') this.setState({currA: val})
if (key === 'B') this.setState({currB: val})
console.log('updated curr' + key + ' to ' + val);
}
render() {
return (
<div className='App'>
<div>
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'A'} val={this.state.currA} />
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'B'} val={this.state.currB} />
</div>
</div>
);
}
}
const Dropdown = props => (
<select onChange={event => props.callbackFromParent(props.stateKey, event.target.value)}>
<option value='cats'>Cats</option>
<option value='dogs'>Dogs</option>
</select>
)
export default App;

Categories