I'm creating a react application, and I have a component that is define more or less like this:
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: true,
error: null
};
}
componentDidMount() {
var _this = this;
this.serverRequest =
axios
.get("LinkToAPI")
.then(result => {
_this.setState({
data: result.data,
loading: false,
error: null
});
})
.catch(err => {
_this.setState({
loading: false,
error: err
});
});
}
componentWillUnmount() {
this.serverRequest.abort();
}
renderLoading() {
return <div>Loading...</div>
}
renderError() {
return (
<div>
Something when wrong: {this.state.error.message}
</div>
);
}
renderData() {
const { error, data} = this.state;
if (error) {
return this.renderError();
}
return (
<div>
{data.map(d=> {
if (d.imageUrl) {
<div className="dataDiv" style="background: url('{d.imageUrl}')" key={d.Id}>{d.name}</div>
} else {
<div className="dataDiv" style="background: url('LinkToSomeImage')" key={d.Id}>{d.name}</div>
}
}
)}
</div>
)
}
render() {
return (
<div className="App">
{this.props.loading ? this.renderLoading() : this.renderData()}
</div>
);
}
}
It basically gets the JSON data from the API, and using it renders some divs with the data inside the JSON. I'm applying to the divs containing the data dataDiv class, which is define inside my App.css file. Additionally, I want to set a background image for the div. What exactly I want to do is that if the data entry includes a field named imageUrl I want to use that url as a url to the background image, otherwise, if it is null or empty, I want to use a default url that I found from the internet. What is a proper way to handle this in React? The code segment above doesn't seem to work, especially the if-else statement inside the renderData function. How can I fix this code, or is there any way to handle this more gracefully, probably maybe inside the CSS?
I would do like this
Please make sure to check backgroundUrl equal to your desired CSS.
{data.map(d => {
let backgroundUrl = "LinkToSomeImage";
if (d.imageUrl) {
backgroundUrl = d.imageUrl;
}
return (
<div className="dataDiv" style={{backgroundImage: `url(${backgroundUrl})`}} key={d.Id}>{d.name}</div>
)
})}
EDIT
A full function would be:
renderData() {
const { error, data} = this.state;
if (error) {
return this.renderError();
}
return (
<div>
{data.map(d => {
let backgroundUrl = "LinkToSomeImage";
if (d.imageUrl) {
backgroundUrl = d.imageUrl;
}
return (
<div className="dataDiv" style={{backgroundImage: `url(${backgroundUrl})`}} key={d.Id}>{d.name}</div>
)
})}
</div>
)
}
<Box
onClick={() => {
history.push({
pathname: `/p/c/${data.ProductName.replace(/\//g, "~")}/1`
});
}}
css={{
backgroundImage:`url(${data.imageUrl||"/default-placeholder.png"})`,
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
}}
>
Related
Well i am trying to reduce the line of code at once refactoring the code
import React, { Component } from 'react';
import { Loader } from '../../components';
import './ProductListing.scss';
import { ProductCard } from '../../components';
import { productQuery } from '../../utls/queries';
export class ProductListing extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
products: [],
categoryId: '',
};
}
componentDidMount() {
const currentUrl = window.location.pathname;
const id = currentUrl.replace('/', '');
this.setState({ categoryId: id });
const newQuer = { ...productQuery };
const query = `
query{
categories {
name
products {
id,
name,
brand,
inStock,
gallery,
category,
prices {
amount,
currency {
label,
symbol
}
}
}
}
}
`;
console.log(query === productQuery);
console.log(productQuery);
fetch('http://localhost:4000', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
query,
}),
})
.then((response) => {
return response.json();
})
.then((data) => {
this.setState({
products: data.data,
loading: false,
});
});
}
render() {
if (this.state.loading === true) {
return <Loader />;
} else {
return (
<div>
<h2 className='page__listing__title'>
{this.state.categoryId[0].toUpperCase() +
this.state.categoryId.substring(1)}
</h2>
<div className='productlisting__page'>
{this.state.products.categories.map((item, index) => (
<div key={index}>
{item.name === this.state.categoryId ? (
<div className='product__listing__card'>
{item.products.map((product, i) => (
<ProductCard product={product} key={i} />
))}
</div>
) : (
''
)}
</div>
))}
</div>
</div>
);
}
}
}
export default ProductListing;
In the process of reducing code i see that the query is taking a lot of places so i decided to write it at separate place now i am importing it as productQuery when i console.log(productQuery===query) it says true but the place where i am using the query to fetch data i use productQuery it just give bad error i cant understand ...
if some one have much better idea i really like if you can suggest me much better ways by which i can reduce the lines of code
I think what's happening is you're accidentally destructuring the query you import, when you say
const newQuery = {...productQuery}
productQuery is simply a string (as proven by your console log that stays productQuery === query).
newQuery is an object that destructures the string, and trying to use that would likely result in a failure.
I am using graphql subscriptions to update my UI. Currently I am needing 2 components to update the data, as I am unsure of another way of accessing the subscribeToMore in the componentDidMount block. Is it possible to do all this in one component?
My code looks like this:
App.js
const App = () => (
<Query query={GET_MESSAGES}>
{({ data, loading, subscribeToMore }) => {
if (!data) {
return null;
}
if (loading) {
return <span>Loading ...</span>;
}
return (
<Messages
messages={data.messages}
subscribeToMore={subscribeToMore}
/>
);
}}
</Query>
);
Messages.js
class Messages extends React.Component {
componentDidMount() {
this.props.subscribeToMore({
document: MESSAGE_CREATED,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
return {
messages: [
...prev.messages,
subscriptionData.data.messageCreated,
],
};
},
});
}
render() {
return (
<ul>
{this.props.messages.map(message => (
<li key={message.id}>{message.content}</li>
))}
</ul>
);
}
}
The API that I am using is slow. My React component is using FetchAPI and has two return statements.
I want to incorporate a ternary operator condition that will display a "Loading.." div while I wait for the data like this: https://codesandbox.io/s/api-fetch-using-usestate-useeffect-k024k
How do I include a "Loading" ternary with two return statements in my code below? Note: I drastically reduced my code to not drive anyone insane so I only included the overall structure. You're welcome!
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentDidMount() {
fetch('https.data.json')
.then(response => response.json())
.then(data => this.setState({ data: data}))
}
render() {
let theList = this.state.map((item, id) => {
return (
<Card>A bunch of API Stuff </Card>
);
})
return (
<div className="search-wrap">
<Row>
{theList}
</Row>
</div>
)
}
}
export default App;
My React component is using FetchAPI and has two return statements.
No, it doesn't. It has one:
render() {
let theList = this.state.map((item, id) => {
return (
<Card>A bunch of API Stuff </Card>
);
})
return ( // <========== here
<div className="search-wrap">
<Row>
{theList}
</Row>
</div>
)
}
The other one is a return within your map callback, which doesn't have any effect on your render method at all.
If you want to use the conditional operator to show a loading indicator, you'd do it in your render's return:
render() {
let { loading } = this.state; // *** Get the flag
let theList = this.state.map((item, id) => {
return (
<Card>A bunch of API Stuff </Card>
);
})
return ( // *** Use it in the below
<div className="search-wrap">
<Row>
{loading ? <p>Loading...</p> : theList}
</Row>
</div>
)
}
You might also want to avoid the unnecessary map call, but if you know your state is initialized with an empty array, that call is harmless. But if you want to get rid of it:
render() {
let { loading } = this.state; // *** Get the flag
let theList = !loading && this.state.map((item, id) => {
return (
<Card>A bunch of API Stuff </Card>
);
})
return ( // *** Use it in the below
<div className="search-wrap">
<Row>
{loading ? <p>Loading...</p> : theList}
</Row>
</div>
)
}
Just add a boolean field to your state which indicates that data is being loaded.
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: true, // this one
};
}
componentDidMount() {
fetch('https.data.json')
.then(response => response.json())
.then(data => this.setState({ data: data, loading: false }))
}
render() {
if (this.state.loading)
return <div>Loading...</div>
let theList = this.state.map((item, id) => {
return (
<Card>A bunch of API Stuff </Card>
);
})
return (
<div className="search-wrap">
<Row>
{theList}
</Row>
</div>
)
}
}
export default App;
If I understand correctly, your code should look like the following:
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false
};
}
componentDidMount() {
this.setState({ isLoading: true }); // mount the loading component when we will start the fecth
fetch('https.data.json')
.then(response => response.json())
.then(data => this.setState({ data: data}))
.then(() => this.setState({ isLoading: false })) // unmount Loading component when the fetch ends
}
render() {
let theList = this.state.map((item, id) => {
return (
<Card>A bunch of API Stuff </Card>
);
})
// Here we will return the Loading component as long as isLoading is true
return (isLoading) ? <Loading /> : (
<div className="search-wrap">
<Row>
{theList}
</Row>
</div>
)
}
}
export default App;
So basically we will add a boolean variable (isLoading) that will handle the state of the fetch and we will add it into the state of the component. When the fetch is triggered, this variable will be true otherwise it will be false.
So then in the return statement, we can use a ternary operator based on the value of this new variable. If it is true we will return a Loading component or a simple div saying Loading.... Otherwise we will return the App component with the data loaded.
I hope this help :)
You can do something like this. Note that you can export directly the class since the beginning. Also, you can simplify the state, without a explicit constructor.
To know the fetch state, you should add a isLoading condition before and after fetching the data. Then, in the render, you can return one single node, and inside render the components that you want based on your status. With this coding style, you can even show when the fetch returns an empty array.
export default class App extends Component {
state = {
data: [],
isLoading: false
}
componentDidMount() {
this.setState({
isLoading: true
}, () => {
fetch('https.data.json')
.then(response => response.json())
.then(data => this.setState({
data,false
}))
})
}
render() {
return (
<div>
{
this.state.isLoading &&
<span>
Loading...
</span>
}
{
!this.state.isLoading &&
(this.state.data.length > 0) &&
<div className="search-wrap">
<Row>
{
this.state.data.map((item, id) => {
return (
<Card key={id}>
A bunch of API Stuff
</Card>
);
})
}
</Row>
</div>
}
{
!this.state.isLoading &&
(this.state.data.length === 0) &&
<span>
There's no data to show
</span>
}
</div>
)
}
}
Hope this helps!
Denny
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>)
}
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!