I am new with React and cannot write correct code for the situation. Let me explain in the code
class Test extends React.Component {
render() {
// IndexedDB indexes
const ids = ['id1', 'id2', 'id3'];
// fetch data and build a list
const files = ids.map((id) =>
localforage.getItem(id).then((entry) =>
<li key={entry.id}><img src={entry.data} /></li>
);
);
return (
<ul>{files}</ul>
)
}
}
What is the correct way to asynchronous load all data and display it?
You could do the asynchronous logic in the componentDidMount hook and use setState to put the entries in your state.
Example
class Test extends React.Component {
state = { entries: [] };
componentDidMount() {
const ids = ["id1", "id2", "id3"];
Promise.all(ids.map(id => localforage.getItem(id))).then(entries => {
this.setState({ entries });
});
}
render() {
return (
<ul>
{this.state.entries.map(entry => (
<li key={entry.id}>
<img src={entry.data} />
</li>
))}
</ul>
);
}
}
Related
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.
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>)
}
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
This code works if data already fetched.
But doesn't work if I refresh the page and doesn't rerender element.
I'm also using Next JS if it's worth mentioning.
class Books extends Component {
constructor(props) {
super(props);
this.state = {
book: []
}
this.renderBooks= this.renderBooks.bind(this);
}
renderBooks() {
let item;
let items = [];
return new Promise((resolve, reject) => {
this.props.ids.forEach(address => {
firebase.database().ref(`/books/${address}`)
.on('value', snap => {
item = snap.val();
});
items.push(item);
})
resolve(items);
});
}
async componentDidMount() {
try {
let res = [];
res = await this.renderBooks();
console.log(res);
this.setState({ book: res });
} catch (error) {
console.log(error);
this.setState(prevState => {
return { book: 'err' }
});
}
}
render() {
return (
<div>
{ <List grid={{ gutter: 16 }}
dataSource={ this.state.book }
renderItem={ item => (
<List.Item>
<Card title={ !!item && item.title }>
...Book content...
</Card>
</List.Item>
)} />
}
</div>
);
}
}
export default Books;
Is there anything to know about setState and fetching data that I missed here?
PS. Edited constructor to book: [].
You cannot initialize book with a promise. Instead you can have a solution like below.
Add a conditional rendering to you render method so it will know when to render book. Also you don't need to return new Promise in this case.
class Books extends Component {
constructor(props) {
super(props);
this.state = { books: null }
}
componentDidMount() {
this.renderBooks()
}
renderBooks() {
this.props.ids.forEach(address => {
firebase.database().ref(`/books/${address}`)
.on('value', snap => {
this.setState({books: [...(this.state.books || []), snap.val()] });
});
});
}
render() {
return (
this.state.books ?
<div>
{ <List grid={{ gutter: 16 }}
dataSource={ this.state.books }
renderItem={ item => (
<List.Item>
<Card title={ !!item && item.title }>
...Book content...
</Card>
</List.Item>
)} />
}
</div>
: 'Initializing'
);
}
}
export default Books;
Promises are basically async functions that are resolved when it's time.
So when you do
var item, items = []; // <---- Step 1
this.props.ids.forEach(address => {
firebase.database().ref(`/books/${address}`)
.on('value', snap => {
item = snap.val(); // <--- Step 3
});
});
items.push(item); // <----- Step 2
});
The steps are like this. So you were doing items.push(item) before item was assigned a new value which is snap.val(). And that makes item undefined.
I guess the second result you have is thanks to caching. If the internet connection is SOOOO FAST Step 3 might be earlier than Step 2, but that's a bad assumption. That's why the second time you get the result correct.
In this answer's case, instead of having an items array, the snap.val() is added to this.state.books. But this makes it a bit heavyweight. Because every time a query on('value') is called, the setState method will be triggered and the component will be rendered again. If there were 1000 ids the state would change 1000 times.
That's why instead of getting the data one by one I would suggest you to get all the data at once. Try to google something like 'retrieve multiple data from firebase javascript'. Unfortunately I don't know much about firebase so cannot help there.
You are initializing this.state.book with a promise. Try setting it to null instead:
this.state = {
book: null
}
I am trying to toggle a class in React (only in the else statement).
class Inf extends React.Component {
constructor() {
super();
this.state = {
pizzaData: data
}
}
renderList(info){
const list = this.state.pizzaData.map((entry, index) => {
if (entry.occupied==true){
return <li class="coloring" key={index}>Seat: {entry.seat}{entry.row}</li>;
}
else{
return <li class="colored" key={index}>Seat: {entry.seat}{entry.row}</li>;
}
});
return(
<ul>{list}</ul>
)
}
Now, looking over some of the documentation I was unsure how to do this. I know that there needs to be a "toggle" on the li and (I think) something like this below the this.state={:
pizzaData:data
},
handleClick function(
But I am not sure.
I created a simple example of how you can update your code, also with two components (similar to the idea by #THEtheChad), but without using context since according to react docs it is discouraged to use context directly if you want your app to be stable. If state and props management in app gets too complicated you can include redux (which internally also uses context), but for now I am not including redux since it be might over-complication in this simple case.
Here is PizzaList which has pizzas on its state. The component will render PizzaItem components and pass a callback down so that each PizzaItem can notify its parent (PizzaList) when it is clicked. PizzaList has the responsibility of toggling PizzaItem when it is clicked.
class PizzaList extends React.PureComponent {
state = {
pizzas: []
}
componentDidMount() {
// fetch data about pizzas via an API and perform this.setState
this.setState({ pizzas: [{ seat: 20, occupied: false }, { seat: 10, occupied: true }, { seat: 30, occupied: true }] });
}
handlePizzaItemClick = (pizzaInd) => {
this.setState((prevState) => {
// find clicked pizza and toggle its occupied property
const pizzas = prevState.pizzas.map((pizza, ind) => {
if (ind === pizzaInd)
return { ...pizza, ...{ occupied: !pizza.occupied } };
return pizza;
});
return { pizzas: pizzas };
});
}
render () {
return (
<ul>
{this.state.pizzas.map((pizza, index) =>
<PizzaItem
onClick={this.handlePizzaItemClick}
index={index}
pizza={pizza}
/>)}
</ul>
);
}
}
PizzaItem is a simple function component that doesn't have any state.
const PizzaItem = ({ index, pizza, onClick }) => {
const { seat, row, occupied } = pizza;
const pizzaClassName = occupied ? 'coloring' : 'colored';
return (
<li key={index}
className={pizzaClassName}
onClick={() => onClick(index)}>
Seat: {seat} {row}
</li>
);
}
Here is a working example on codesandbox.
I would update your code and split it into two components, a list component and an item component (in this case pizza?). The list component would provide a method for modifying the list using the context API. In my example, I have an updatePizza method that I pass down in the context.
Then, in the child component, you have a click handler that updates the occupied status of the pizza and tells the parent what the new status is using the context method.
This makes sure that the parent component always has the current state for all the pizzas and passes that down to the children. The parent component becomes the single source of truth here.
class List extends React.Component {
static childContextTypes = {
updatePizza: React.PropTypes.func
}
constructor({ pizzas }){
super()
this.state = { pizzas }
}
updatePizza = (idx, pizza) => {
this.setState( ({ pizzas }) => {
pizzas[idx] = pizza;
return { pizzas }
})
}
getChildContext() {
return { updatePizza: this.updatePizza }
}
render(){
return <ul>{this.state.pizzas.map((pizza, idx) => <Pizza key={ idx } { ...pizza }>)}<ul>
}
}
class Pizza extends React.Component {
static contextTypes = {
updatePizza: React.PropTypes.func
}
handleClick = () => {
this.state.occupied = !this.state.occupied;
this.context.updatePizza(this.state.key, this.state)
}
render() {
const { key, seat, row, occupied } = this.state;
const status = occupied ? 'coloring' : 'colored';
return <li key={ key } className={ status } onClick={ handleClick }> Seat: { seat } { row }</li>
}
}