Display one object from fetched data undefined - reactjs

I'm new to React and I'm having a hard time trying to display the first element of an array for example. I'm getting a TypeError: Cannot read property 'title' of undefined even if it's working with a console.log. Here is the code :
constructor() {
super()
this.state = {
posts: []
}
this.componentDidMount = this.componentDidMount.bind(this)
}
async componentDidMount() {
const url = "https://jsonplaceholder.typicode.com/posts";
const response = await fetch(url);
const data = await response.json();
this.setState({
posts: data
})
console.log(data); //working
console.log(this.state.posts) //working
console.log(this.state.posts[0].title) //working
}
render() {
return (
<div className="head-title"> { this.state.posts[0].title } </div>
<div className="head-body"> { this.state.posts[0].body} </div>
)
}
What am I doing wrong ?

You can do like this. In addition to my answer :
class test extends Component{
constructor() {
super()
this.state = {
posts: [],
loading :true
}
this.componentDidMount = this.componentDidMount.bind(this)
}
async componentDidMount() {
const url = "https://jsonplaceholder.typicode.com/posts";
const response = await fetch(url);
const data = await response.json();
this.setState({
posts: data,
loading:false
})
console.log(data); //working
console.log(this.state.posts) //working
console.log(this.state.posts[0].title) //working
}
}
render() {
if(this.state.loading) return null;//Dont render component
//or add ternary condition
return (
<div className="head-title"> { this.state.posts[0].title } </div>
<div className="head-body"> { this.state.posts[0].body} </div>
)
}
}

Your component will attempt to render this.state.posts[0].title while the request still hasn't finished and thus will throw an error.
Add a check to make sure your data exists for example like so:
render() {
return (
<div className="head-title"> { (this.state.posts.length > 0) ? this.state.posts[0].title : null } </div>
<div className="head-body"> { (this.state.posts.length > 0) ? this.state.posts[0].body : null} </div>
)
}

Related

How to make React.js fetch data from api as state and pass this state data from its parent to child component

I am working on a React.js + D3.js project. I wanted App.js to fetch data from a json file and save this data into state and pass this parent sate data down to my child component state through the property. I found if I use static data in App.js works fine, but once fetching from a json file, it failed because no data can be stored into property. My App.js like this:
import React, { Component } from 'react';
import SandkeyGraph from './particle/SandkeyGraph';
class App extends Component {
state = {
data : null
}
// works fine in this way!
// state = {
// data: {
// "nodes":[
// {"node":0,"name":"node0"},
// {"node":1,"name":"node1"},
// {"node":2,"name":"node2"},
// {"node":3,"name":"node3"},
// {"node":4,"name":"node4"}
// ],
// "links":[
// {"source":0,"target":2,"value":2},
// {"source":1,"target":2,"value":2},
// {"source":1,"target":3,"value":2},
// {"source":0,"target":4,"value":2},
// {"source":2,"target":3,"value":2},
// {"source":2,"target":4,"value":2},
// {"source":3,"target":4,"value":4}
// ]}
// }
componentWillMount() {
this.getData('./data/sankey.json');
}
getData = (uri) => {
fetch(uri)
.then((response) => {
return response.json();
})
.then((data) => {
// successful got the data
console.log(data);
this.setState({ data });
});
}
render() {
// failed
const { data } = this.state;
return (
<div>
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData = {this.state.data}
/>
</div>
);
}
}
export default App;
parrt of my is like this:
class SankeyGraph extends Component {
displayName: 'SankeyGraph';
state = {
sankeyData : null
}
constructor(props) {
super(props);
this.state.sankeyData = props.sankeyData || null;
}
PropTypes : {
id : PropTypes.string,
height: PropTypes.number,
width: PropTypes.number,
sankeyData : PropTypes.object,
}
componentDidMount () {
// will be null, if using fetch from App.js
//console.log(this.state.sankeyData);
this.setContext();
}
//...
Does anyone know how to handle this situation? Thank you so much in advanced!
After working out the problem, it turned out that there was no problem with fetch. It just didn't account for null in any of the components in the program (It would crash after using a null value.
For example in render:
render() {
if (this.state.data) {
return (
<div>
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData = {this.state.data}
/>
</div>
);
}
else {
return <div/>
}
}
Or, the use of a ternary operator would work as well to be more concise (answer by #Eliran):
return (
{this.state.data ?
<div>
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData = {this.state.data}
/>
</div> : <div>No Data Available</div>
);
You can add in your render function a condition:
render() {
// failed
const { data } = this.state;
return (
<div>
{data ?
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData={data}
/> : "Loading..."
}
</div>
);
}
and only if data is populated the component will be rendered.
...
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
data : null
}
}
It seems like an error on the state declaration?
1.- Import your json in App component on top like this: import jsonData from './data/sankey.json'
2.- Set jsonData in state jsonData in App component.
constructor(props) {
super(props)
this.state = {
jsonData : {}
}
}
componentWillMount() {
this.setState({ jsonData })
}
You do not need to fetch as you have your json locally.
Once you have your json in your state, you can display it in render like this for example:
this.state.jsonData.data.links.map(x=>
<div>
<div>x.links.source</div>
<div>x.links.target</div>
</div>
)
I've been testing and you need to replace the getData() method to this:
getData = (uri) => {
fetch(uri, {
headers : {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then((response) => {
return response.json();
})
.then((data) => {
// successful got the data
console.log(data);
this.setState({ data });
});
}
This is because you need to declare 'Content-Type' and 'Accept' headers on your fetch options in order to make your request.

React: Issues with Conditional Rendering

In my React-App, i use the Firebase SDK. If a user wants to reset his password, he will be redirected to a page within my app. If the code is valid, the component <PWResetConfirmForm /> should be rended. If the code is invalid, the component <PWResetOutdatedForm /> is to be rendered.
My Page Component looks like this:
class PWResetConfirmPage extends Component {
constructor(props) {
super(props);
this.state = {};
this.verfiyResetPassword = this.verfiyResetPassword.bind(this);
}
verfiyResetPassword() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
auth.doVerfiyPasswordReset(code)
.then(function () {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetConfirmForm></PWResetConfirmForm>
</div>
);
})
.catch(function () {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
})
}
render() {
return (
<div>
{this.verfiyResetPassword()}
</div>
);
}
}
export default PWResetConfirmPage
When i try to run, i get a blank page and not error.
Where is my issue and how can i fix that?
Thank you very much for your help and for your time
You will not be able to return JSX from within then()/catch() of auth.doVerfiyPasswordReset() like that. You can instead approach this by taking advantage of React.Component lifecycle method componentDidMount and using setState() to manipulate state properties for conditional rendering. I've added state properties to the component, one to track whether loading (API call has completed) and one to track whether the call was a success (then) or failure (catch). These properties are used to conditionally generate JSX content for rendering. This is assuming that verfiyResetPassword() is intended to run when the component is first mounted, instead of every time render() is called:
class App extends Component {
constructor() {
super();
this.state = {
isResetVerified: null,
loading: true
};
}
componentDidMount() {
this.verfiyResetPassword();
}
verfiyResetPassword() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
auth.doVerfiyPasswordReset('foobar')
.then(() => {
this.setState({
...this.state,
isResetVerified: true,
loading: false
});
})
.catch(() => {
this.setState({
...this.state,
isResetVerified: false,
loading: false
});
})
}
getContent() {
if (this.state.loading) {
return (
<div>Loading...</div>
);
} else {
if (this.state.isResetVerified) {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetConfirmForm></PWResetConfirmForm>
</div>
);
} else {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
}
}
}
Here is a basic example in action.
Also, in the constructor this.verfiyResetPassword = this.verfiyResetPassword.bind(this); would only be needed if verfiyResetPassword() is executed by a DOM event such as button onClick or similar.
Hopefully that helps!
I could still fix the error myself:
class PWResetConfirmPage extends Component {
constructor(props) {
super(props);
this.state = {
isValid: false,
code: "",
};
this.verfiyResetPassword = this.verfiyResetPassword.bind(this);
}
componentDidMount() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
this.setState({code:code})
auth.doVerfiyPasswordReset(code)
.then(() => {
this.setState({
...this.state,
isValid: true,
});
})
.catch(() => {
this.setState({
...this.state,
isValid: false,
});
})
}
verfiyResetPassword() {
if (this.state.isValid) {
return (
<div>
<TopBar></TopBar>
<PWResetConfirmForm code={this.state.code}></PWResetConfirmForm>
</div>
);
} else {
return (
<div>
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
}
}
render() {
return (
<div className="HomePage-Main">
{this.verfiyResetPassword()}
</div>
);
}
}
export default PWResetConfirmPage

Showing JSON data in ReactJS

I cant show the JSON response to the HTML, all I am getting is his.state.selectedData.map is not a function.
This is the payload
{
"id": 1,
"name":"john",
"age" : 22
}
This is in the constructor
this.state = {
selectedData : []
}
This is the HTTP request:
axios.post("/api/data", data)
.then(res =>{
console.log(res)
this.setState({
selectedData : res.data
})
})
And this is how I am trying to show the result
<div>
<ul>
{ this.state.selectedData.map(data => {
<li>{data.name}</li>
})}
</ul>
</div>
what I am doing wrong ?
Initially selectedData is array after the ajax you changing it to object. So map function is not going work.
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedData :[]
}
}
readJson(json){
let output=[]
Object.keys(json).forEach(key=>{
output.push(
<li>{json[key]}</li>
)
});
return output;
}
render() {
return (
<div>
<ul>{this.readJson({ "id": 1,"name":"john","age" : 22 })}</ul>
</div>
)
}
}
The response from api is json object and your are trying to use map in json object will not work. Change selectedData to json object like below in the constructor
this.state = {
selectedData : {}
}
And in the code you can directly refer the name key like below and remove the map.
this.state.selectedData.name && <li>{this.state.selectedData.name}</li>
You could try something like this if you want to keep your state object as an array.
let newData = this.state.selectedData;
axios.post("/api/data", data)
.then(res =>{
console.log(res)
newData.push(res.data)
this.setState({
selectedData : newData
})
})
constructor(props) {
super(props);
this.state = {
loadedList: [],
lists: [],
};
}
componentDidMount() {
axios.get('http://localhost:3001/../static/data/terrifdata.json')
.then(response => {
this.setState({
lists: response.data,
loadedList: response.data,
isLoading: false
})
})
}
<div>
{loadedList.map((postDetail, index) => {
return (
<h5>
{postDetail.name}
</h5>
)}
}
</div>

React - Cannot get property setState of null

I am intending to get snapshot val from Firebase within my React component. I want to get the values based on init of the component and attach a listener for changes.
class ChatMessages extends Component {
constructor(props) {
super(props);
this.state = {
messages: [],
};
this.getMessages = this.getMessages.bind(this);
}
getMessages(event) {
const messagesRef = firebase.database().ref('messages');
messagesRef.on('value', function(snapshot) {
this.setState({ messages: snapshot.val() });
});
}
componentDidMount() {
this.getMessages();
}
render() {
return (
<div className="container">
<ul>
<li>Default Chat Message</li>
{ this.state.messages }
</ul>
</div>
);
}
}
This is because 'this' is losing its context. So that, 'this.setState' is being undefined. You can have a reference for the actual 'this' via a variable called 'that'.
class ChatMessages extends Component {
constructor(props) {
super(props);
this.state = {
messages: [],
};
this.getMessages = this.getMessages.bind(this);
}
getMessages(event) {
const messagesRef = firebase.database().ref('messages');
let that = this
messagesRef.on('value', function(snapshot) {
// here
that.setState({ messages: snapshot.val() });
});
}
componentDidMount() {
this.getMessages();
}
render() {
return (
<div className="container">
<ul>
<li>Default Chat Message</li>
{ this.state.messages }
</ul>
</div>
);
}
}
Or if possible, you can use arrow function, which keeps its context.
getMessages(event) {
const messagesRef = firebase.database().ref('messages');
// here
messagesRef.on('value', snapshot => {
// here
that.setState({ messages: snapshot.val() });
});
}

ReactJS show loader on child component update

I have seen a lot loader plugins that work for the Mount life cycle but none for the update part and I wonder how to handle it?
What I tried was following setup for parent:
class App extends React.Component {
constructor() {
super()
this.state = {loader_wrap:false};
this.hideLoader = this.hideLoader.bind(this);
this.showLoader = this.showLoader.bind(this);
}
hideLoader(){
this.setState({loader_wrap: false});
}
showLoader() {
this.setState({loader_wrap: true});
}
render() {
var loaderStyle;
if (this.state.loader_wrap) {
loaderStyle = {display:"block"};
} else {
loaderStyle = {display:"none"};
}
return (
<div>
<div id="content">
{React.cloneElement(content, {
hideLoader: this.hideLoader,
showLoader: this.showLoader
})}
</div>
<div id="loader-wrap" style={loaderStyle}>
<img className="loader hidden-sm hidden-xs" src='source/file/'>
</div>
</div>
)
}
}
And this is the child calling the methods:
class Childextends React.Component {
constructor() {
super();
this.state = {results:[]};
this.calculate = this.calculate.bind(this);
}
calculate(dict) {
this.props.showLoader();
Actions.action(dict)
.then(results => {
this.setState({results: results});
})
.catch((err) => {
var errResp = JSON.parse(err.response);
console.log(errResp);
this.setState({responseErrors: errResp});
});
}
componentDidMount() {
this.props.hideLoader();
}
componentDidUpdate() {
this.props.hideLoader();
}
componentWillReceiveProps(values){
this.setState({results:values.results});
}
render() {
return (
/*stuff to be returned*/
)
}
}
I also tried to use the Will methods .. which worked even worser :D
Any ideas how to implement this? I use react with flux but don't now how to use it in this case ..
Why not just call hideLoader() in the callback of the action's promise?
class Childextends React.Component {
constructor() {
super();
this.state = {results:[]};
this.calculate = this.calculate.bind(this);
}
calculate(dict) {
this.props.showLoader();
Actions.action(dict)
.then(results => {
this.setState({results: results});
})
.catch((err) => {
var errResp = JSON.parse(err.response);
console.log(errResp);
this.setState({responseErrors: errResp});
})
.then(() => {
this.props.hideLoader();
});
}
render() {
return (
/*stuff to be returned*/
)
}
}
Edit: A different approach to the parent component as well - rather than hiding the element with a style, just don't render it if it isn't required.
render() {
return (
<div>
<div id="content">
{React.cloneElement(content, {
hideLoader: this.hideLoader,
showLoader: this.showLoader
})}
</div>
{this.state.loader_wrap &&
<div id="loader-wrap" style={loaderStyle}>
<img className="loader hidden-sm hidden-xs" src='source/file/'>
</div>
}
</div>
)
}

Resources