Any efficient way to render nested json list in React? - javascript

I am able to fetch REST API where I can get nested json output, and I want them to display in React component. Now I only can render them in the console which is not my goal actually. I am wondering if there is an efficient way to do this for rendering nested json list in React. can anyone give me a possible idea to make this work?
here is what I did:
import React, { Component } from "react";
class JsonItem extends Component {
render() {
return <li>
{ this.props.name }
{ this.props.children }
</li>
}
}
export default class List extends Component {
constructor(props){
super(props)
this.state = {
data: []
}
};
componentDidMount() {
fetch("/students")
.then(res => res.json())
.then(json => {
this.setState({
data: json
});
});
}
list(data) {
const children = (items) => {
if (items) {
return <ul>{ this.list(items) }</ul>
}
}
return data.map((node, index) => {
return <JsonItem key={ node.id } name={ node.name }>
{ children(node.items) }
</JsonItem>
});
}
render() {
return <ul>
{ this.list(this.props.data) }
</ul>
}
}
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
my current output:
in my above component, I could render nested list on the console like this:
[![enter image description here][1]][1]
desired output:
how can I properly render out nested json output on React? Any idea to make this happen? any thought? Thanks

As you knew .map() is the common solution for this. But you can make this much better like below.
export default class List extends Component {
constructor(props){
super(props)
this.state = {
data: [],
isLoaded: false, //initally the loading state is false.
}
};
componentDidMount() {
fetch("/students")
.then(res => res.json())
.then(json => {
//updating the loading state and data.
this.setState({data: json, isLoaded:true});
});
}
render() {
//Waiting ajax response or ajax not yet triggered.
if(!this.state.isLoaded){
return(<div>Loading...</div>);
}else{
//Rendering the data from state.
let studenDetails = this.state.data.map((student, i) => {
let uin = student.uin;
let studentInfo = Object.keys(student.studentInfo).map((label, i) => {
return (
<div key={i}>
<span>
<strong>{label}: </strong>{`${student.studentInfo[label]}`}
</span>
</div>
);
});
return (
<div key={i}>
<h3>{uin}</h3>
<p>{studentInfo}</p>
</div>
);
});
return (<div>{studenDetails}</div>);
}
}
}
Hope it will help you.

To render a list in react use the .map() function to build a list of jsx elements.
render() {
let myRenderedData = this.state.data.map((x, index) => {
return <p key={index}>{x.uin}</p>
})
return (<div>{myRenderedData}</div>)
}

Related

How to pass argument from functional component to class component

EDIT - I fixed this and posted the working code.
I'm working on a project and I am having a specific issue I can't figure out how to fix. I am displaying a list of champions images and when the user clicks on one of them (s) then it will change the page to display that champions name. Currently I can console.log any of the names without any issues which means my functional component Newchamp() is working! However I am having trouble passing an argument from NewChamp to the class component SpecificChamp. When I add the last line in Newchamp return and try to display it in SpecificChamp using {s} its undefined!
Is it possible to pass an argument from my functional class to my component class? if not how can I get the page to change to the specific image that is clicked? I am new to react and appreciate any help!
Can anyone please help me out with this
import React, { Component } from 'react';
import './Champions.css';
class AllChamps extends Component {
render() {
let champion = this.props.champion;
return(
<div className='champions'>
<h1> all champions</h1>
{Object.keys(this.props.champions).map((s) => (
<div className='champs' onClick={() => this.props.NewChamp({s, champion})}>
<img
alt='Champion Images'
src={`http://ddragon.leagueoflegends.com/cdn/10.16.1/img/champion/${s}.png`}
onClick={this.props.onClick}
></img>
{s}
</div>
))}
</div>
)}}
class SpecificChamp extends Component {
render() {
let champion = this.props.champion
let Spec = champion[champion.length - 1];
return (
<div className='champions'>
<h1> 1 champions</h1>
<div className='champs'>
<button onClick={this.props.onClick}></button>
{Spec}
</div>
</div>
)}
}
class Champions extends Component {
constructor(props) {
super(props);
this.handleAllChamps = this.handleAllChamps.bind(this);
this.handleSpecificChamp = this.handleSpecificChamp.bind(this);
this.NewChamp = this.NewChamp.bind(this);
this.state = {
champions: [],
champion: [],
clickedChamp: false,
thisChamp: 'ahri'
}}
NewChamp = (props) =>
{
let s = props.s;
props.champion.push(s);
fetch(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`)
.then(response => { return response.json() })
.then((response) => {
Object.keys(response.data).map((a) => (s = a
))})
fetch(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`)
.then(response => { return response.json() })
.then((response) => {
console.log(s)
console.log(response.data)
console.log(props.champion)
})
console.log(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`);
}
handleAllChamps = (props) => {
this.setState({ clickedChamp: true,
})};
handleSpecificChamp = () => {
this.setState({ clickedChamp: false,
})};
componentDidMount(props) {
const apiUrl = `http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion.json`;
fetch(apiUrl)
.then(response => { return response.json() })
.then((response) => {
this.setState({
champions: response.data
}, () => (this.state.champions))
return
})
}
render() {
const clickedChamp = this.state.clickedChamp;
let display;
if (clickedChamp ) {
display = <SpecificChamp champion={this.state.champion} onClick={this.handleSpecificChamp} s={this.state.thisChamp}/>;
} else {
display = <AllChamps champions={this.state.champions} onClick={this.handleAllChamps} NewChamp={this.NewChamp} thisChamp={this.state.thisChamp} champion={this.state.champion} />;
}
return (
<div>
<div className='champions'></div>
{display}
</div>
);
}
}
export default Champions;
The render function in class component does not has any props. You should use props from this like what you have done with handle click.
class SpecificChamp extends Component {
render() {
return (
<div className='champions'>
<h1> 1 champions</h1>
<div className='champs'>
<button onClick={this.props.onClick}></button>
{this.props.s}
</div>
</div>
)}
}

React state one step behind (not duplicate!)

I know this may seem like a repeat and I have read the similar questions and answers. However I am still having an issue getting my parent component and its state to sync up with its child component. It is just one step behind, even in the console.
Parent Component App.js
changeItemQuantity = (id, newQuantity) => {
this.setState(prevState => ({
cartData: {
...prevState.cartData,
items: this.state.cartData.items.map(item => {
if (String(item.productData.productId) === String(id)) {
item.productQuantity = newQuantity
}
return item
})
}
}))
// console.log(id, newQuantity);
// console.log(this.state.cartData)
}
Child Component Cart.js
import React, { Component } from 'react';
class Cart extends Component {
constructor(props) {
super(props);
this.state = {
newQuantity: 1
}
}
handleQuantityChange = (e) => {
this.setState({
newQuantity: e.target.value
})
this.props.changeItemQuantity(e.target.id, this.state.newQuantity);
}
render() {
return (
<div className="cart-list">
{this.props.cartData.items.map(item => {
return (
<div className="cart-item" key={item.productData.productId}>
<div className={`cart-item-image ${item.productData.productImage}`}></div>
<div className="cart-item-data">
<div className="cart-item-name">{item.productData.productName}</div>
<div className="cart-item-s-q">( {item.productSize} ) x <input type="text" id={item.productData.productId} onChange={this.handleQuantityChange} className="cart-field change-q" placeholder={item.productQuantity}/></div>
<div className="cart-item-total">Total : ${(parseInt(item.productData.productPrice) * parseInt(item.productQuantity)).toFixed(2)}</div>
</div>
</div>
)
})}
</div>
)
}
}
export default Cart;
Basically what I am trying to accomplish is being able to be inside of the shopping cart page of my ecom site and adjust the quantity of each item (which is held inside of the parents state). I have that figured out, but when I adjust, the state inside the console and on the screen is not up to date. I have tried using a callback function like so
handleQuantityChange = (e) => {
this.setState({
newQuantity: e.target.value
}, this.props.changeItemQuantity(e.target.id, this.state.newQuantity);)
}
But that did nothing for me. I also tried using a callback inside of the Parent component like so...
changeItemQuantity = (id, newQuantity) => {
this.setState(prevState => ({
cartData: {
...prevState.cartData,
items: this.state.cartData.items.map(item => {
if (String(item.productData.productId) === String(id)) {
item.productQuantity = newQuantity
}
return item
})
}
}), console.log(this.state.cartData))
}
But again, nothing. What do you guys suppose I could do? Thanks in advance!

I'm trying to update state in an object nested within an array... with json data

So the goal is to fetch data from the google books API, which returns JSON data in the same form as my state shows. I want to update title with the title string returned by the JSON data. Right now I get a "failed to compile" on the line I've marked in the code. Then, I would like to pass the title as a props to the List component, which would render it as a list item with each map through. So if 20 books' data are fetched, I would render 20 different titles. I'm new to react so I'm not sure how much is wrong here.
import React, { Component } from 'react';
import List from './List.js';
export default class Main extends Component {
state ={
items : [{
volumeInfo : {
title : "",
}
}]
}
componentDidMount() {
fetch(`https://www.googleapis.com/books/v1/volumes?q=flowers+inauthor:keyes&key=AIzaSyAWQ0wFzFPQ3YHD_uLDC7sSs-HPRM3d__E`)
.then(res => res.json())
.then(
(result) => {
this.setState({
items : [{
volumeInfo : {
title : result.items.map((book) => {
const name = book.volumeInfo.title;
return name;
})
}
}] });
})
}
render() {
return (
<div>
<header>
<h2>Google Book Search</h2>
</header>
<List title={this.state.items}/>
</div>
)
}
}
Here's List.js
import React, { Component } from 'react'
export default class List extends Component {
render() {
return (
<div>
<ul>
<li>{this.props.items}</li>
</ul>
</div>
)
}
}
As the result of your fetch() has the same structure as your items property of the state, all you need to do in the then() callback is to set the result in the state directly as shown below:
componentDidMount() {
fetch('your/long/url')
.then(res => res.json())
.then((result) => {
this.setState({ items: (result.items || []) });
});
}
Now that your state is updated with the needed data, you need to pass it as a prop to your List component:
render() {
return (
<div>
<header>
<h2>Google Book Search</h2>
</header>
<List items={ this.state.items } />
</div>
);
}
Finally, in your List component, you can make use of this prop by rendering it in a map() call:
render() {
return (
<div>
<ul>
{ this.props.items.map((book, i) => (
<li key={ i }>{ book.volumeInfo.title }</li>
)) }
</ul>
</div>
);
}
export default class Main extends Component {
state ={
items : []
}
componentDidMount() {
fetch(`https://www.googleapis.com/books/v1/volumes?q=flowers+inauthor:keyes&key=AIzaSyAWQ0wFzFPQ3YHD_uLDC7sSs-HPRM3d__E`)
.then(res => res.json())
.then((result) => {
const titleList = result.items.map((item)=>{return item.volumeInfo.title});
this.setState({items: titleList})
})
};
render(){
const {items} = this.state;
const titleComponent = items.length > 0
? items.map((item)=>{
return <List title={item} />
})
: null;
return (
<div className="App">
<header>
<h2>Google Book Search</h2>
</header>
{titleComponent}
</div>
)
}
}
Above code should be worked if your List component is working fine.
Change the setState function with this
this.setState({
items : [{
volumeInfo : {
title : result.items.map((book) => {
const name = book.volumeInfo.title;
return name;
})
}
}] });
Looks like brackets was the issue.

Get JSON data in React

I'm making my first project in React so please be gentle.
I'm having trouble putting react data into a component.
Based on this tutorial I setup the component. The source code from video.
class WeatherInfo extends React.Component {
constructor() {
super();
this.state = {
items: [],
isLoaded: false
}
}
componentDidMount() {
fetch(`https://api.openweathermap.org/data/2.5/forecast?q=Austin,USA&appid=583f803dfc6a7f2f96ff9957c330c2b0&units=imperial`)
.then(results => results.json())
.then(json => {
this.setState({
isLoaded: true,
items: json
})
});
}
render() {
let {
isLoaded,
items
} = this.state;
if (!isLoaded) {
return <div> Loading... </div>
} else {
return ( <div>
<ul>
{items.map(item => (
<li key="{item.list}">
test: {item.list.main}
</li>
))}
</ul>
</div>
);
}
}
}
Feeling lost when connecting the JSON into the ul...or whatever I'd like. My recent error is items.map is not a function. But I have a strong feeling even fixing that error won't get the data from the api that I'd like.
Here's a link to the JSON link where the data is I'd like to use. End project would be selecting only some of the data but I'm confident one I know how to access the data correctly I can do that on my own.
Thank you.
URL used in tutorial is returning array of records
https://jsonplaceholder.typicode.com/users
[
{
"id": 1, .....
}
]
While you api end point does not return array of records, it is returning object that's why its failing.
Try this:
{items.list.map(item => (
<li key="{item}">
test: {item.main}
</li>
))}
I got your code working here is a jsFiddle link: https://jsfiddle.net/8q3wbmft/
class WeatherInfo extends React.Component {
constructor() {
super();
this.state = {
items: [],
isLoaded: false
}
}
componentDidMount() {
fetch(`https://api.openweathermap.org/data/2.5/forecast?q=Austin,USA&appid=583f803dfc6a7f2f96ff9957c330c2b0&units=imperial`)
.then(results => results.json())
.then(json => {
this.setState({
isLoaded: true,
items: json
})
});
}
render() {
let {
isLoaded,
items
} = this.state;
if (!isLoaded) {
return (<div> Loading... </div>)
} else {
return ( <div>
<ul>
{items.list.map((item, key) => (
<li key="{key}">
test: {item.main.temp}
</li>
))}
</ul>
</div>
);
}
}
}
ReactDOM.render(
<WeatherInfo name="World" />,
document.getElementById('container')
);
Basically what is happening is this:
The api is an object:
And this object has an array of list
This list is an array of objects
The key main has the main data temp which you need
If you have questions feel free to ask

render method being called before API data is loaded in React

Total beginner with React.
I am trying to work out the standard approach to this situation in React.
I am accessing an api, the data is being returned all ok, except I am trying to set the data as a state of my component, and the render() method is referencing the state before any data is returned so the state property is being defined as 'null'.
In my code sample below you can see I am logging to the console, and despite the order of things, the second log is being returned from the browser before the one that has setState to be the API data.
Any help / explanation as to why this is happening despite using .then() would be appreciated.
Thank you.
PS: I have removed the TeamList component for simplification, but like the 'second log', the component gets rendered before the data has actually been pulled in.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
}
}
componentDidMount() {
const uri = 'http://api.football-data.org/v2/competitions/PL/teams';
let h = new Headers()
h.append('Accept', 'application/json')
h.append('X-Auth-Token', 'XXXXXXXXXXXXXXXXXXXX')
let req = new Request(uri, {
method: 'GET',
headers: h,
mode: 'cors'
})
var component = this;
fetch(req)
.then( (response) => {
return response.json()
})
.then( (json) => {
this.setState({ data: json })
})
.then( (json) => {
console.log( 'second log', this.state.data )
})
.catch( (ex) => {
console.log('parsing failed', ex)
})
console.log( 'first log', this.state.data )
}
render() {
return (
<div>
<div className="App">
<TeamList list={this.state.data} />
</div>
</div>
);
}
}
export default App;
You need to add something like this to the start of your render():
if (this.state.data === null) {
return false;
}
So your code should be:
render() {
if (this.state.data === null) {
return false;
}
return (
<div>
<div className="App">
<TeamList list={this.state.data} />
</div>
</div>
);
}
render() is called immediately, but you want it to return false until this.state.data has data
When you mount a component, it gets rendered immeadiately with the initial state (that you've set in the constructor). Then later, when you call setState, the state gets updated and the component gets rerendered. Therefore it makes sense to show something like "loading..." until state.data is not null:
render() {
return (
<div>
<div className="App">
{this.state.data ? <TeamList list={this.state.data} /> : "loading..." }
</div>
</div>
);
}
Now additionally logging does not work as expected as setState does not return a promise, so:
.then( (json) => {
this.setState({ data: json })
})
.then( (json) => {
console.log( 'second log', this.state.data )
})
is actually the same as:
.then( (json) => {
this.setState({ data: json })
console.log( 'second log', this.state.data )
})
and that still logs null as setState is asynchronous, which means that calling it does not change this.state now but rather somewhen. To log it correctly use the callback:
then( (json) => {
this.setState({ data: json }, () => {
console.log( 'second log', this.state.data )
});
})
Just an idea:
import React, { Component } from 'react';
class App extends Component {
constructor(props)
{
super(props);
this.state = {
data: null,
};
}
componentDidMount()
{
fetch('http://api.football-data.org/v2/competitions/PL/teams')
.then(response => response.json())
.then(data => this.setState({ data }));
}
render() {
return (
<div>
<div className="App">
<TeamList list={this.state.data} />
</div>
</div>
);
}
}
export default App;
TeamList :
class TeamList extends React.Component {
constructor(props) {
super(props);
}
render(){
return (
<ul>
{
this.props.list.map((element, i) => {
return (
<li className="un-res t_d " key={i}>{element}</li>
)
}
})
}
}
export default TeamList
Happy coding!

Categories

Resources