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>
);
}
}
Related
import React from 'react';
/**
App
Simple react js fetch example
*/
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
isLoaded: false
}
}
/**
componentDidMount
Fetch json array of objects from given url and update state.
*/
componentDidMount() {
fetch('https://run.mocky.io/v3/8260aa5d-8af8-4cff-999e-6e81b217f0ba')
.then(res => res.json())
.then(json => {
this.setState({
items: json,
isLoaded: true,
})
}).catch((err) => {
console.log(err);
});
}
/**
render
Render UI
*/
render() {
const { isLoaded, items } = this.state;
if (!isLoaded)
return Loading...;
return (
{items.map(item => (
Name: {item.name}
))}
);
}
}
export default App;
in render function
return (
{
items.clients.map(item => (<span key={item.id}> Name : {item.name}
</span>)
)
}
)
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.
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'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'
}}
>
I am fetching an array of items (call'em movies) from an API. Every movie has several details (title, director, cast, synopsis etc)
Here's a diagram of my structure:
Here is the MoviesList component :
constructor(props) {
super(props);
this.state = {
movies: []
};
}
componentDidMount() {
fetch('http://localhost:3335/movies')
.then(function(response) {
return response.json()
}).then((movies) => {
this.setState({ movies });
}).catch(function(ex) {
console.log('parsing failed', ex)
})
}
render() {
return (
<div className="movies">
<Movie movies={this.state.movies}></Movie>
</div>
);
}
And here is the movie:
render() {
const movielist = this.props.movies.map((movie) =>
<li>{movie.title}</li>
);
return (
<ul className="movie">
{ movielist }
</ul>
);
}
My questions:
Should I use state or props to pass the data down from Movielist to Movie
Since there's no "for" templating, how do I make it so I am not creating a list inside Movie but I am instead looping the entire Movie component inside Movielist ?
For example, I would like to achieve the following inside MovieList:
render() {
return (
<div className="movies">
<Movie for movie in movies></Movie>
</div>
);
}
render() of MovieList :
render() {
return (
<div className="movies">
{
this.state.movies
? <ul>
{this.state.movies.map((movie, index) => <Movie title={movie.title} key={index}/>)}
</ul>
: null
}
</div>
);
}
render() of Movie :
render() {
return (
<li className="movie">{this.props.title}</li>
);
}
i props/state - I think what you're doing is fine; passing your outer component's (MoviesList) state down to its child as a prop (you wouldn't set the outer component's own properties with the result of the api call).
ii You can map over the movies in your outer component, for example:
renderMovie(movie) {
return (
<Movie movie={movie} />
);
}
render()
{
return (
<div className="movies">
<ul>
{ this.state.movies.map(this.renderMovie) }
</ul>
</div>
);
}