How render components after fetching data - reactjs

I have a container called "Recetas" (Receipes) which has inside a component called "Showcase". The idea is that the user can create a request in "Recetas" and when the data is fetched, the container will re-render and "Showcase" will update with the new data.
I'm saving the request in the state of "Recetas" and passing the data to "Showcase" with props.
The problem is that the render is being execute before I receive the new data. So I'm always showing "old" data. Is there any way I can put on hold the render until I've received the data? Or how can I solve it?
class Recetas extends Component {
state = {
loading: false,
data: [],
maxResult: 12,
minResult: 0,
query: 'burger',
appId: 'xxxxxx',
appKey: 'xxxxx'
}
componentDidMount() {
this.fetchData();
}
async fetchData() {
this.setState({loading: true});
console.log('fetching ...');
try {
const request = `https://api.edamam.com/search?q=${this.state.query}&app_id=${this.state.appId}&app_key=${this.state.appKey}&from=${this.state.minResult}&to=${this.state.maxResult}`;
console.log('request: ', request);
const result = await axios(request);
this.setState({ data: result.data.hits, loading: false });
} catch (error) {
console.log(error);
}
}
queryHandler = value => {
this.setState({
query: value
});
this.fetchData();
}
render() {
return (
<div className={classes.Recetas}>
{console.log('actualState: ', this.state)}
<SearchInput
query={this.state.query}
queryHandler={(value) => this.queryHandler(value)}/>
<Showcase
data={this.state.data}
loading={this.state.loading}/>
</div>
);
}
Showcase component
const showcase = props => {
const spinner = <Spinner />;
const recetas = props.data.map((elem, index) => {
return <Receta key={index} title={elem.recipe.label} url={elem.recipe.image} />
});
console.log('[Showcase] props.data: ', props.data);
return (
<div className={classes.Showcase}>
{props.loading ? spinner : recetas}
</div>
);
}

In case the Function Component not watch the props change. You should move the spinner to the container component.
Recetas.js
render() {
...
const {loading, data} = this.state
return (
...
<div className={classes.Recetas}>
{loading ? < Spinner /> : <Showcase data={data} />}
</div>
);
}
//////////////
ShowCase.js
const showcase = props => (
<div className={classes.Showcase}>
{
props.data.map(({recipe: {label, imgage}}, index) => <Receta key={index} title={label} url={image} />)
}
</div>
)
And using Destructuring_assignment for shorter code.

Finally I managed to solve it adding a setTimeout function which execute the fetch 500 ms after updating the state in 'queryhandler' method.

Related

React Hook useEffect has a missing dependency: 'context'. Either include it or remove the dependency array

My goal is to make an API request when user types something on input. I'm getting the data successfully. However the component is rerendering twice and giving me this warning. If I include 'context' I'm getting an infinite loop. Here's my code:
Component.js:
const SearchBox = () => {
const [searchTerm, setSearchTerm] = useState("");
const { handleSearch, searchResults } = useContext(MovieContext);
console.log(searchResults);
useEffect(() => {
let timer;
timer = setTimeout(() => {
handleSearch(searchTerm);
}, 500);
return () => clearTimeout(timer);
}, [searchTerm]);
const renderResults = () => {
if (searchResults.length > 0) {
searchResults.map(result => {
return (
<div key={result.Title}>
<img src={result.Poster} alt={result.Title} />;
</div>
);
});
}
return;
};
return (
<>
<label>
<b>Search</b>
</label>
<input
className="input"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
<div className="dropdown is-active">
<div className="dropdown-menu">
<div className="dropdown-content results">{renderResults()}</div>
</div>
</div>
</>
);
};
On top of this context.searchResults is undefined, although I set the initial value as an empty array. I wanted to know what causing this. What am I doing wrong? Here is my context code below:
Context.js:
const Context = React.createContext("");
export class MovieStore extends Component {
constructor(props) {
super(props);
this.state = {
searchResults: [],
handleSearch: this.handleSearch
};
}
handleSearch = async term => {
try {
if (term !== "") {
const response = await axios.get("http://www.omdbapi.com/", {
params: {
apikey: apikey,
s: term
}
});
this.setState({ searchResults: response.data.Search });
}
} catch (error) {
console.log(error);
}
};
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
Exactly the same thing about an infinite loop is mentioned in React docs here. So the cause of infinite loop is that, in context render function, you create new value every time render is called.
render() {
return (
<Context.Provider
// ! value object creates every time render is called - it's bad
value={{ ...this.state, handleSearch: this.handleSearch }}
>
{this.props.children}
</Context.Provider>
);
}
It causes every consumer to rerender when context state updates. So, if you put context in dependencies array of useEffect, eventually, it'll cause an infinite loop, because context value is always different. Here's what happens:
Context makes a search query.
Context state updates with the new data, which causes all consumers to rerender.
In context consumer useEffect sees that context value has been
updated and it calls setTimeout which will call for another search
in context provider in 500ms.
Consumer calls context to make another search query and we've got an infinite loop!
The solution is to keep context value the same object, while only updating its properties. It can be done by putting all the necessary properties inside of context state. Like that:
export class MovieStore extends Component {
handleSearch = async term => {
try {
if (term !== "") {
const response = await axios.get("http://www.omdbapi.com/", {
params: {
apikey: "15bfc1e3",
s: term
}
});
this.setState({ searchResults: response.data.Search });
}
} catch (error) {
console.log(error);
}
};
state = {
searchResults: [],
handleSearch: this.handleSearch // <~ put method directly to the state
};
render() {
return (
<Context.Provider value={this.state}> // <~ Just returning state here
{this.props.children}
</Context.Provider>
);
}
}
Hope it helps <3

React render array of images in array

end app for woocommerce store, but i have problem rendering the first image of array in
when i console.log(images.src) i see the list of urls of the images, but in img src= it return : TypeError: Cannot read property 'src' of undefined
I will be very thankful to help me correctly map the images.
here is my code:
class App extends React.Component {
constructor(props) {
super(props);
this.getPosts = this.getPosts.bind(this);
this.state = {
posts : [],
images: []
};
}
getPosts = async () => {
let res = await api.get("products", {
per_page: 20,
})
let { data } = await res;
this.setState({ posts: data });
}
componentDidMount = async () => {
await this.getPosts();
};
render() {
const { posts } = this.state;
const { images } = this.state
return(
<div>
<Head>
<title>Онлайн магазин KIKI.BG</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<React.Fragment >
{posts.map((posts, index ) => {
{
posts.images.map((images, subindex) =>
console.log(images.src),
<img src={images[0].src} />
)}
return (
<div>
<h1>{posts.name}</h1>
<h2>{posts.price}</h2>
</div>
)})}
</React.Fragment>
</div>
)
}
}
export default App;
{posts.map((posts, index ) => {
{
posts.images.src.map((image, subindex) =>
<img src={image.src} />
)}
return (
<div>
<h1>{posts.name}</h1>
<h2>{posts.price}</h2>
</div>
)
})}
well, console.log(images.src) i see the list of urls of the images doesn't make any sense.. images is array. So images[0] should be image with data with property src on it?. Btw a lot of stuff in this code is just wrong.
Don't rebind getPosts already bound getPosts function in constructor (via class property) (getPosts). BTW you dont need to bind is here at all, its not called as a callback.
Its weird, that you call await res after api.get() ... shouldn't be it just await api.get()? Another await is usually used on fetch, when you do something like await response.json().
There is no need for async/await in componentDidMount
If getPosts will throw it will mess up your component, its better to handle error in catch and call props.onError(error) for example
You don't have any key attributes on element in map, thats wrong. You should put some unique id there (url fe? if not same, or id) for proper component re-render.
You have some weird brackets issue in your maps...
You shouldn't use more than one h1 one the page :-)
images.src should be string, not array...
Why is there subindex and index when u are not using it?
Why you store images when they are not filled anywhere? Are they in the response of get? Thats maybe why u get an TypeError !
I would add loading and no data message...
That would be my code:
import { Component, Fragment } from 'react';
class App extends Component {
static defaultProps = {
onError: console.error;
};
state = {
posts: [],
images: [],
loading: false,
};
// This could be done with hooks much better tho...
async componentDidMount () {
this.setState({ loading: true });
try {
await this._fetchData();
}
catch (error) {
this.props.onError(error); // Or something rendered in state.error?
}
finally {
this.setState({ loading: false });
}
}
render () {
const { images, posts, loading } = this.state;
if (!images.length) {
return <div>No data.</div>;
}
if (loading) {
return <div>Loading</div>;
}
const postBoxes = posts.map((post, index) => {
const image = images[index];
// Because you don't know, if that specific image is there... if this are your data..
const imageElement = image ?
<img src={image.src} alt="dont know" /> :
null;
const { name, price } = post;
// If name is unique, otherwise some id.
return (
<Fragment key={name} >
{imageElement}
<h2>{name}</h2>
<h3>{price}</h3>
</Fragment>
);
});
return (
<div>
<Head>
<title>Онлайн магазин KIKI.BG</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Fragment>
{postBoxes}
</Fragment>
</div>
);
}
async _fetchData () {
const { data } = await api.get('products', { per_page: 20 });
const { posts, images } = data;
this.setState({ posts, images });
}
}
export default App;
if console.log(images.src) -> gives list of images.
Then,
<img src={images.src[0]}/> -> should do the trick.
may be, Add a null check to be certain.
images.src[0] && <img src={images.src[0]}/>

How to setState to answer from APi and use map

Im trying to create recipes searcher. In App.js I receive query from search input from another component and I want to setState to answer from APi. Console.log from callback in setState shows updated state but the state is not updated. I need setState updaed so I can use map on it and display list of recipes in render. It gives me error map is not a function because this.state.recipesList is still empty. Anyone can help me ?
class App extends Component {
state = {
query: "",
recipesList: []
};
getQuery = query => {
const key = "2889f0d3f51281eea62fa6726e16991e";
const URL = `https://www.food2fork.com/api/search?key=${key}&q=${query}`;
fetch(URL)
.then(res => res.json())
.then(res => {
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
});
console.log(this.state.recipesList);
};
render() {
const test = this.state.recipesList.map(item => {
return (
<div className="recispesList">
<h1>{item.title}</h1>
</div>
);
});
return (
<div className="App">
<Search query={this.getQuery} />
<div className="contentWrapper">{}</div>
</div>
);
}
}
Search component:
class Search extends Component {
state = {
searchValue: ""
};
handleChange = val => {
let searchValue = val.target.value;
this.setState({
searchValue
});
};
handleSubmit = e => {
e.preventDefault();
this.setState({
searchValue: ""
});
this.props.query(this.state.searchValue);
};
render() {
return (
<div className="searchWrapper">
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.searchValue} />
<button />
</form>
</div>
);
}
}
export default Search;
It seems that instead of directly assigning the whole response to recipesList:
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
you need to get recipes array first via res.recipes:
this.setState(
{
recipesList: res.recipes
},
() => {
console.log(this.state.recipesList);
}
);

How to fix recursively updating state?

I am bulding an app using newsapi. i am facing two issue on my state. i fetch data using api and assign it to my state. and use it in my view.
Issue no 1
My view gets rendered before my app receives the data.
Issue no 2
When I try to update my state after a new fetch. it recursively updates the set of data again and again.
import React, {Component} from 'react';
import NewsComponent from './NewsComponent/NewsComponent'
class News extends Component {
state = {
displayStatus: false,
newsItems: []
};
toogleDisplayHandler = () => {
if(this.state.displayStatus===true){
this.setState({displayStatus:false})
}
else{
this.setState({displayStatus:true})
}
}
render(){
const NewsAPI = require('newsapi');
const newsapi = new NewsAPI('d6da863f882e4a1a89c5152bd3692fb6');
//console.log(this.props.keyword);
newsapi.v2.topHeadlines({
sources: 'bbc-news,abc-news',
q: this.props.keyword
}).then(response => {
//console.log(response)
response.articles.map(article => {
//console.log(article);
return(
//console.log(this.state.newsItems)
this.setState({
newsItems: [...this.state.newsItems, article],
})
//this.state.newsItems.push(article)
)
});
});
let Article = null;
Article = (
<div>
{
this.state.newsItems.map((news, index) => {
return (
<NewsComponent key={index}
title={news.title}
url={news.url}
description={news.description}
author={news.author}
publish={news.publishedAt}
image={news.urlToImage}
/>
)
})
}
</div>
)
return (
<div className="App">
{Article}
<button onClick={this.toogleDisplayHandler}>
{this.state.displayStatus === true ? "Hide Article" : "Display Articles"}
</button>
</div>
)
}
}
export default News;
Please help me to resolve this issue.
You should never setState in render as that would cause an infinite loop. Do it in componentDidMount or the constructor.
I would also recommend not using map for simply iterating over a list. Array.map is a function that is useful for returning an array that is constructed by iterating over another array. If you want to run some code for each element of an array use Array.forEach instead.
Like this:
import React, { Component } from "react";
import NewsComponent from "./NewsComponent/NewsComponent";
class News extends Component {
state = {
displayStatus: false,
newsItems: []
};
toogleDisplayHandler = () => {
if (this.state.displayStatus === true) {
this.setState({ displayStatus: false });
} else {
this.setState({ displayStatus: true });
}
};
componentDidMount = () => {
const NewsAPI = require("newsapi");
const newsapi = new NewsAPI("d6da863f882e4a1a89c5152bd3692fb6");
newsapi.v2
.topHeadlines({
sources: "bbc-news,abc-news",
q: this.props.keyword
})
.then(response => {
response.articles.forEach(article => {
this.setState({
newsItems: [...this.state.newsItems, article]
});
});
});
};
render() {
let Article = null;
Article = (
<div>
{this.state.newsItems.map((news, index) => {
return (
<NewsComponent
key={index}
title={news.title}
url={news.url}
description={news.description}
author={news.author}
publish={news.publishedAt}
image={news.urlToImage}
/>
);
})}
</div>
);
return (
<div className="App">
{Article}
<button onClick={this.toogleDisplayHandler}>
{this.state.displayStatus === true
? "Hide Article"
: "Display Articles"}
</button>
</div>
);
}
}
export default News;
1) You can add a check either your state has the data which you want to show on screen to render the view.
2) Please use ComponentDidMount React life cycle function to fetch data from an external source and update this data in the state. In the Render method, it will keep calling it recursively.

Update Apollo store after filtered query

I have a list of items which can be filtered out using certain criteria. Whenever i perform a search, i want to grab a filtered item and update its content which seems to update the apollo store correctly, but for some reason changes are not being shown. Perhaps the componentWillReceiveProps lifecycle method is not being fired and i need to implement it by myself?
I tried updating the store manually using "update" after the mutation but it wont work also.
This is my code so far:
ClientList.js
class ClientList extends Component {
constructor(props) {
super(props);
this.state = {
hasMoreItems: true,
loading: false,
clients: [],
searchText: ''
}
}
_executeSearch = async () => {
const { searchText } = this.state;
this.setState({ loading: true });
const result = await this.props.client.query({
query: ALL_CLIENTS_QUERY,
variables: { searchText },
fetchPolicy: 'network-only'
})
this.setState({
clients: result.data.allClients,
loading: false
});
}
render() {
let { allClients, loading, fetchMore } = this.props.data;
const { hasMoreItems, clients, searchText } = this.state;
if (clients.length > 0) {
allClients = clients;
loading = this.state.loading;
}
return (
<section>
<h1 className="text-center">Clients</h1>
<InputGroup>
<InputGroupButton>
<Button onClick={() => this._executeSearch()}>I'm a button</Button>
</InputGroupButton>
<Input
onChange={(e) => this.setState({ searchText: e.target.value })}
placeholder="Search by social or comercial name"
/>
</InputGroup>
{loading ?
<div className="text-center mt-4">
<i className="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
</div>
: <div className="mt-3">
{allClients.map(client =>
<div key={`client-${client.id}`} className="client-content">
<Link to={`/clients/${client.id}`}>
<h1 className="mb-1">
{client.socialName}
<small className="text-muted ml-3">{client.comercialName}</small>
</h1>
</Link>
</div>
})
</div>
</section>
);
};
}
export default withApollo(graphql(ALL_CLIENTS_QUERY)(ClientList));
ClientEdit.js
class ClientEdit extends Component {
onSubmit = async (e) => {
e.preventDefault();
this.setState({ loading: true });
const payload = {
id: this.props.match.params.id,
rfc: this.state.rfc,
socialName: this.state.socialName,
legalRepresentative: this.state.legalRepresentative,
comercialName: this.state.comercialName
}
// Mutation updates the store but doesnt show results
const resp = await this.props.mutate({
variables: payload,
update: (store, { data: { updateClient } }) => {
// Tried updating but it doesnt show changes also;
}
});
}
}
export default compose(
graphql(GET_CLIENT_QUERY, {
options: props => ({
variables: {
id: props.match.params.id
}
})
}),
graphql(UPDATE_CLIENT_MUTATION)
)(ClientEdit);
better if we check that the data is ready and we can render it out , and when data still fetching so no render associated with that apollo data object should be done:
render(){
const {loading} = this.props.data;
if(loading) {return <div>Loading...</div>}
return (
...
)
I manage to fix this using the componentWillReceiveProps lifecycle method. I dont know if this is a bug or maybe there is another solution;
componentWillReceiveProps(newProps) {
const { allClients, loading } = newProps.data;
if (!loading) {
const clients = _.intersectionBy(allClients, this.state.clients, 'id');
this.setState({ clients });
}
}

Resources