React isn't recognizing object within my state - javascript

So i'm building a component in react that is fetching data from an API, and I'm getting a build error in browser. it's telling me that down in the render section, It's not recognizing "students". But i've looked at how the JSON data from the API is structured, and it follows that. I set the state to the data of the JSON, yet when I render it doesn't recognize students? Any tips? thank you so much!
import React from "react";
import axios from "axios";
export default class FetchRandomUser extends React.Component {
state = {
students: []
}
componentDidMount() {
axios.get("...")
.then(data => {
this.setState({ students: data})
console.log(this.state)
})
}
render(){
return (
<ul>
{ students.map(student =>
<li key={student.id}>
{student.city}
</li>
)
}
</ul>
)
}
}

You need to access students from state property of the component,
render(){
const {students} = this.state;
return (
<ul>
{ students.map(student =>
<li key={student.id}>
{student.city}
</li>
)
}
</ul>
)
}

Extending #majdsalloum
setState is async so if you immediately console.log state it won't reflect in your state students variable

try with your state in a constructor like
constructor(props) { super(props); this.state = { students: []}; }
add this.state in front of your students variables.
also, you can view your axios response by just using
console.log(data)

Related

How can i save API response data as a variable in ReactJS so i can import it in any components?

I am following the following tutorial:
https://www.digitalocean.com/community/tutorials/react-axios-react
I am trying to import a value from an API endpoint and save it as a variable in another component.
My API endpoint is very simple compared to the example, it just responds with this:
{"USD":1168.64}
I have made a new component called PersonList.js like in the tutorial:
import React from 'react';
import axios from 'axios';
export default class PersonList extends React.Component {
state = {
persons: []
}
componentDidMount() {
axios.get(`https://myendpoint.com`)
.then(res => {
const persons = res.data;
this.setState({ persons });
})
}
render() {
return (
<ul>
{
this.state.persons
.map(person =>
<li key={person.id}>{person.name}</li>
)
}
</ul>
)
}
}
and then in another component i have i am importing it like this:
import PersonList from './components/PersonList.js';
however i cannot console.log the variable PersonList.
I understand there is 2 issues here:
my PersonList.js is not setup to get just the USD value from the API
i dont know how to save the API response as a variable once its imported.
Can anyone help?
Since you're using class components you'll need to use the componentDidUpdate lifecycle method which will allow you to do something once the component has been updated/the state has been changed.
The previous props and state are passed as arguments to the method, and it's a good idea to use a condition to double check that things have changed so that you don't do any unnecessary work.
const { Component } = React;
class Example extends Component {
constructor() {
super();
this.state = { persons: {} };
}
// Fake API request
componentDidMount() {
const persons = { usd: 1168.64 };
this.setState({ persons });
}
// Has the state changed?
componentDidUpdate(prevProps, prevState) {
if (prevState.persons !== this.state.persons) {
console.log(this.state.persons.usd);
}
}
render() {
return (
<div>
Initial render
</div>
);
}
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

How do I access elements of an array in a property in Javascript object?

The following code works:
import React, { Component } from 'react';
import './App.css';
import Menu_dropdown from "./component/menu_dropdown";
class App extends Component {
constructor() {
super()
this.state = {
races: {}
}
}
componentDidMount(){
fetch('https://www.dnd5eapi.co/api/races/')
.then(response=> response.json())
.then(races => {this.setState({ races : races})});
}
render(){
const { races } = this.state;
console.log(races['results']);
return (
<div className="App">
<header className="App-header">
<Menu_dropdown races = {races}/>
</header>
</div>
);
}
}
export default App;
console will output an array of races. However,it gives me "TypeError: Cannot read property '0' of undefined" when I change to this
console.log(races['results'][0]);
why is results not an array? You can view source json here:
https://www.dnd5eapi.co/api/races/
When state is first initialized to { races: {}}, races.results will be undefined and you can't get any elements from undefined.
Simply change the constructor to:
constructor() {
super()
this.state = {
races: {results: []}
}
}
This way the component can render an empty array while loading and the fetch results when the fetch call is resolved.

React - Ajax data not being passed into Child Component

I have two components, one parent one child. I am using the fetch method in componentDidMount() callback. Once I do this, I set the state with key items to that data that is pulled from the api. Once I do this it should be able to be console logged in the child component as a prop. However this is not working. What am I doing wrong here?
Parent Component:
import React, {Component} from 'react';
import Map from './maps/Map';
class Main extends Component {
constructor(props){
super(props);
this.state = {
name: "John",
items: []
}
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits
})
})
}
render() {
return (
<div>
<Map list={this.state.name} items={this.state.items}></Map>
</div>
)
}
}
export default Main;
Child Component:
import React, {Component} from 'react';
class Map extends Component {
constructor(props) {
super(props);
console.log(props.items)
}
render () {
return (
<h1>{this.props.name}</h1>
)
}
}
export default Map;
First, fetch is asynchronous. So, the fetch statement might be pending by the time you try to console.log the result inside the child constructor.
Putting the console.log inside the render method would work, because the component will be rerendered, if the state items changes.
The constructor for a component only runs one time during a lifecycle. When it does, props.items is undefined because your ajax request is in-flight, so console.log(props.items) doesn't show anything.
If you change your constructor to console.log("constructed");, you'll see one-time output (stack snippets may not show this--look in your browser console). Henceforth, componentDidUpdate() can be used to see the new props that were set when your ajax request finishes.
You could also log the props inside the render method, which will run once before the ajax request resolves and again afterwards when props.items changes.
As a side point, you have <Map list=... but the component tries to render this.props.name, which is undefined.
Also, if you aren't doing anything in the constructor (initializing state or binding functions) as here, you don't need it.
class Map_ /* _ added to avoid name clash */ extends React.Component {
constructor(props) {
super(props);
console.log("constructed");
}
componentDidUpdate(prevProps, prevState) {
const props = JSON.stringify(this.props, null, 2);
console.log("I got new props", props);
}
render() {
return (
<div>
<h1>{this.props.name}</h1>
<pre>
<ul>
{this.props.items.map((e, i) =>
<li key={i}>{JSON.stringify(e, null, 2)}</li>)}
</ul>
</pre>
</div>
);
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {name: "John", items: []};
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({items: dat.hits})
});
}
render() {
return (
<div>
<Map_
name={this.state.name}
items={this.state.items}
/>
</div>
);
}
}
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Main />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
The only problem you have is that you are trying to use this.props.name and your Map component props are called list and items, so it will return undefined.
If you log your props in the constructor you will get the initial state of Main because the fetch hasn't returned anything yet. Remember that the constructor only runs once. So you are probably getting an empty array when you log props.items in the constructor because that's what you have in your initial state.
{
name: "John",
items: []
}
If you log the props in your render method you will see your array filled with the data you fetched, as you can see here:
https://codesandbox.io/s/stoic-cache-m7d43
If you don't want to show the component until the data is fetched you can include a boolean property in your state that you set to true once you the fetch returns a response and pass it as a prop to your component. Your component can you use that variable to show, for example, a spinner while you are fetching the data. Here's an example:
https://codesandbox.io/s/reverent-edison-in9w4
import CircularProgress from "#material-ui/core/CircularProgress"
class Main extends Component {
constructor(props) {
super(props);
this.state = {
name: "John",
items: [],
fecthed: false
};
}
componentDidMount() {
fetch("https://hn.algolia.com/api/v1/search?query=")
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits,
fecthed: true
});
});
}
render() {
return (
<Map
fetched={this.state.fecthed}
list={this.state.name}
items={this.state.items}
/>
);
}
}
class Map extends Component {
render() {
return (
<div>
{this.props.fetched ? (
<div>
<h1>{this.props.list}</h1>
{this.props.items.map((item, indx) => (
<div key={indx}>Author: {item.author}</div>
))}
</div>
) : (
<CircularProgress />
)}
</div>
);
}
}
Hope this helps. Cheers!

fetch JSON (http get) not working

I want run it using http get, but it not show nothing, Where is the error?. Angular http.get easier to get JSON and doing ngFor and show, but on React is little special. So, in conclusion I don't like do a simple "import data from './data.json'", I need load json from the cloud.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class App extends Component {
// 1.JSON
constructor(props) {
super(props);
this.state = {
items: [],
};
}
// 2. JSON
componentJSON() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(data => this.setState({ items: data.items }))
}
render() {
// this.componentJSON = this.componentJSON.bind(this);
this.setState({ items: data})
// 3. JSON
// const { items } = this.state;
return (
<Router>
<div className="App">
<ul>
{items.map(item =>
<li key={item.title}>
{item.title}
</li>
)}
</ul>
</div>
</Router>
);
}
}
export default App;
Working now!,
Thanks anyway friends!
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
items : []
};
// You should bind this object to componentWillMount method, other setState was not working
this.componentWillMount = this.componentWillMount.bind(this);
}
// This method is call before component will mounted
componentWillMount() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then( data => this.setState({ items : data }) );
}
render() {
const { items } = this.state;
return (
<ul>
{items.map(item =>
<li key={item.title}>
{item.title}
</li>
)}
</ul>
);
}
}
export default App;
In your function you are assuming that the context is the class, but its not, its not how js works, so when you are trying to use this.setState it would not work because the context of the function doesnt have any function called setState.
A simple way of solving this is binding the function to the class, by simply adding the following line in the ctor:
this.componentJSON = this.componentJSON.bind(this);
You need to call your componentJSON function.
It is best to do this within componentDidMount()
componentDidMount(){
this.componentJSON()
}
This will get called when the component is rendered in the browser.
It is a common mistake to call your API within componentWillMount() but this will make your API call happen twice.
As mentioned in my other comment, be careful about calling your API in your render function as it will mean that you API is called every time there is a re-render. A re-render happens after setting state and since you are setting state in your API call, it will cause an infinite loop
Use componentDidMount
componentDidMount ()
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(data => this.setState({ hits: data.hits }))
}

React: Pass Firebase Data Down Via Props

I'm trying to pass some Firebase data down from one component via props to another component, but it doesn't seem to be letting me iterate over the Firebase data in the child component.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
games: []
};
}
componentDidMount() {
const gamesRef = firebase.database().ref('games').orderByKey();
gamesRef.once('value', snap => {
snap.forEach((childSnapshot) => {
this.state.games.push(childSnapshot.val());
})
})
}
render() {
return (
<div className="App">
<Games data={ this.state.games } />
</div>
);
}
}
Games.js
class Games extends Component {
componentDidMount() {
console.log(this.props.data); // this logs successfully
}
render() {
return (
<div className="container">
<div className="Games flex flex-end flex-wrap">
{ this.props.data.map(function (game, i) {
return (
<h1>{ game.title }</h1>
)
}) }
</div>
</div>
);
}
}
For some reason I'm having a problem when trying to map() over my props.data. It's definitely being passed down to my Games component, because it's printing the console.log(this.props.data) to the console with the data it gets back from Firebase.
Do I have to wait for my Firebase data to resolve before mapping over it, and if so how do I do this?
Any help is appreciated. Thanks in advance!
I think the problem lies with your componentDidMount in your App class. You're updating state with
this.state.games.push(childSnapshot.val());
You shouldn't do that. State should only be updated with this.setState (or at the very least you should use this.forceUpdate()), as otherwise it will not re-render. I would instead advise doing
componentDidMount() {
const gamesRef = firebase.database().ref('games').orderByKey();
let newGames;
gamesRef.once('value', snap => {
snap.forEach((childSnapshot) => {
newGames.push(childSnapshot.val());
})
})
this.setState({games: newGames});
}
This will cause a re-render of the App component, causing the new data to be passed as a prop to the Games component.

Categories