objects sent to react state looping issue - javascript

I am building a recipe app and I have an api that fetches me recipes based on what i type in. the issue is that whenever i type the search phrase and search, it makes the state super unstable by sending in insane amounts of objects into the state (normally it should be like 10-12 results. These objects are repeat of each other (you can see it in the screenshot i have attached). The code is provided below, can anyone show me why this might be so?
import React, { Component } from 'react';
import RecipeDisplay from '../RecipeDisplay/RecipeDisplay';
import Form from '../Form/Form';
import './RecipeUI.css';
import uuid from 'uuid/v4';
export default class RecipeUI extends Component {
constructor(props) {
super(props);
this.state = {
food: [ '' ],
RecipeUI: [ { title: '', thumbnail: '', href: '' } ]
};
this.search = this.search.bind(this);
}
search(x) {
this.setState({ food: x });
}
componentDidUpdate() {
let url = `https://api.edamam.com/search?q=${this.state
.food}&app_id=cf711&app_key=b67d194436b01d1f576aef`;
fetch(url)
.then((response) => {
return response.json();
})
.then((data) =>
data.hits.map((n) => {
let wow = {
key: uuid(),
title: n.recipe.label,
thumbnail: n.recipe.image,
href: n.recipe.url
};
this.setState({ RecipeUI: [ ...this.state.RecipeUI, wow ] });
console.log(this.state);
})
);
}
render() {
return (
<div className="RecipeUI">
<div className="RecipeUI-header">
<h1>Welcome to the Recipe Fetcher</h1>
<Form search={this.search} />
</div>
<div className="RecipeUI-RecipeDisplay">
{this.state.RecipeUI.map((recipe) => (
<RecipeDisplay
key={recipe.key}
title={recipe.title}
thumbnail={recipe.thumbnail}
ingredients={recipe.ingredients}
href={recipe.href}
/>
))}
</div>
</div>
);
}
}

Please, try this as you are concatenating the existing items in state with that of the items that are being brought from search results, the state has got lot of data. Assuming you need only the search results in state, here is the code below:
import React, { Component } from 'react';
import RecipeDisplay from '../RecipeDisplay/RecipeDisplay';
import Form from '../Form/Form';
import './RecipeUI.css';
import uuid from 'uuid/v4';
export default class RecipeUI extends Component {
constructor(props) {
super(props);
this.state = {
food: '',
RecipeUI: []
};
this.search = this.search.bind(this);
}
search(x) {
this.setState({ food: x });
}
componentDidUpdate() {
let url = `https://api.edamam.com/search?q=${this.state.food}&app_id=cf7165e1&app_key=
946d6fb34daf4db0f02a86bd47b89433`;
fetch(url).then((response) => response.json()).then((data) => {
let tempArr = [];
data.hits.map((n) => {
let wow = {
key: uuid(),
title: n.recipe.label,
thumbnail: n.recipe.image,
href: n.recipe.url
};
tempArr.push(wow);
});
this.setState({RecipeUI:tempArr})
});
}
render() {
return (
<div className="RecipeUI">
<div className="RecipeUI-header">
<h1>Welcome to the Recipe Fetcher</h1>
<Form search={this.search} />
</div>
<div className="RecipeUI-RecipeDisplay">
{this.state.RecipeUI.map((recipe) => (
<RecipeDisplay
key={recipe.key}
title={recipe.title}
thumbnail={recipe.thumbnail}
ingredients={recipe.ingredients}
href={recipe.href}
/>
))}
</div>
</div>
);
}
}

Related

JS/React - Live Search Bar With Mapping

After two days of being stuck on this component, I'm asking for any sort of help. I'm trying to search an API based on user input, and then filter that down to a more specific option as the user keeps typing. After solving a dozen or so errors, I'm still left with "Can't find variable 'Query'", and I just can't seem to find or figure out what exactly it's wanting. There was another post on here that led me in the right direction, but didn't provide any sort of answer for the issue I'm having. Any help here would be appreciated.
import axios from "axios";
import axiosRateLimit from "axios-rate-limit";
import React, { Component } from "react";
import SearchBar from "react-native-elements/dist/searchbar/SearchBar-ios";
class CardSearch extends Component {
state = {
data: [],
filteredData: [],
query: "",
};
handleInputChange = (event) => {
const query = event.target.value;
this.setState((prevState) => {
const filteredData = prevState.data.filter((element) => {
return element.name.toLowerCase().includes(query.toLowerCase());
});
return {
query,
filteredData,
};
});
};
getData = () => {
axiosRateLimit(
axios.get(`https://api.scryfall.com/cards/autocomplete?q=${query}`),
{ maxRPS: 8 }
)
.then((response) => response.json())
.then((data) => {
const { query } = this.state;
const filteredData = data.filter((element) => {
return element.name.toLowerCase().includes(query.toLowerCase());
});
this.setState({
data,
filteredData,
});
});
};
componentWillMount() {
this.getData();
}
render() {
return (
<>
<SearchBar
placeholder='Search For...'
value={this.state.query}
onChange={this.handleInputChange}
/>
<div>
{this.state.filteredData.map((i) => (
<p>{i.name}</p>
))}
</div>
</>
);
}
}
export default CardSearch;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Have a look at this Link. You are not setting the State in a Constructor. And as already mentioned in the comments you will then have to access the query using this.state.query
https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class
The Code-Sample from the React Documentation:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

Sending API data in Gatsby to be used in Chart.js

I am trying to send API data being called from my index.js to my ChartData.js. index.js is a page and ChartData.js is a component in Gatsby, so to begin with I could not figure out if Gatsby's Link to="" function only work from one page to another or if it can send data from a page to a component.
The issue is when I try to access the sent data from index.js to ChartData.js in the line {props.location.state.companyName} I am getting the error: TypeError: props.location is undefined
I plan to switch out labels: ['x', 'y'] for something like labels: [{props.location.state.companyName}, {props.location.state.symbol} etc. I am not sure if this would be the correct syntax either.
A more detailed explanation here: https://www.youtube.com/watch?v=No9cqzqlKS0&feature=youtu.be
index.js:
import React from "react"
import { Link } from "gatsby"
import axios from "axios"
import "../css/style.css"
import Layout from "../components/layout"
import { symbol } from "prop-types"
import ChartData from "../components/ChartData"
export default class index extends React.Component {
state = {
companyName: "",
previousClose: "",
marketCap: "",
change: "",
symbol: "",
topStocks: [],
Yearweekhigh: "",
Yearweeklow: "",
avgTotalVolume: "",
peRatio: ""
}
componentDidMount() {
const API_KEY = '*******************';
axios.get(`https://cloud.iexapis.com/stable/stock/market/previous?token=${API_KEY}`)
.then(res => {
console.log(res)
const topStocks = res.slice(1);
this.setState({ topStocks })
})
}
clickHandler = (event) => {
if (event.keyCode === 13) {
const query = event.target.value;
const API_KEY = '*******************';
axios.get(`https://cloud.iexapis.com/stable/stock/${query}/quote?token=${API_KEY}`)
.then(res => {
const companyName = res.data['companyName'];
this.setState({ companyName })
const previousClose = res.data['previousClose'];
this.setState({ previousClose })
const marketCap = res.data['marketCap'];
this.setState({ marketCap })
const change = res.data['change'];
this.setState({ change })
const symbol = res.data['symbol'];
this.setState({ symbol })
const Yearweekhigh = res.data['week52High'];
this.setState({ Yearweekhigh })
const Yearweeklow = res.data['week52Low'];
this.setState({ Yearweeklow })
const avgTotalVolume = res.data['avgTotalVolume'];
this.setState({ avgTotalVolume })
const peRatio = res.data['peRatio'];
this.setState({ peRatio })
})
}
}
render() {
return (
<Layout>
<div class = "main-div">
<input type="search" class="main-search" onKeyDown={event => this.clickHandler(event)}/>
<table>
<tr>
<th>Ticker-Symbol</th>
<th>Market Cap</th>
<th>Previous Close</th>
</tr>
<tr>
<td>
<Link to='/details/' state={{
setState: this.state.symbol,
companyName: this.state.companyName,
previousClose: this.state.previousClose,
marketCap: this.state.marketCap,
change: this.state.change,
Yearweekhigh: this.state.Yearweekhigh,
Yearweeklow: this.state.Yearweeklow,
avgTotalVolume: this.state.avgTotalVolume,
peRatio: this.state.peRatio
}}>
{this.state.symbol}</Link>
<Link to='/ChartData/' state={{
setState: this.state.symbol,
companyName: this.state.companyName,
previousClose: this.state.previousClose,
marketCap: this.state.marketCap,
change: this.state.change,
Yearweekhigh: this.state.Yearweekhigh,
Yearweeklow: this.state.Yearweeklow,
avgTotalVolume: this.state.avgTotalVolume,
peRatio: this.state.peRatio
}}></Link>
</td>
<td>{this.state.marketCap}</td>
<td>{this.state.previousClose}</td>
</tr>
</table>
</div>
<div>
{
this.state.topStocks.length && this.state.topStocks.map(stock => (
<h1>{stock.symbol}</h1>
))
}
</div>
<ChartData />
</Layout>
)
}
}
details.js
//import { Link } from "gatsby"
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import Layout from '../components/layout';
import "../css/style.css"
const Details = props => {
const [yourState, setYourState] = useState('');
useEffect(() => {
}, []);
return <Layout>
<div>
<h1 class="details-company-name">{props.location.state.companyName}</h1>
<div class = "details-div">
<div class="details-div-1">
<p>Open {} </p>
<p>High {} </p>
<p>Low {} </p>
<p>52 WK HIGH <h2>{props.location.state.Yearweekhigh}</h2> </p>
<p>52 WK LOW <h2>{props.location.state.Yearweeklow}</h2> </p>
</div>
<div class="details-div-2">
<p>VOLUME</p>
<p>AVG VOL <h2>{props.location.state.avgTotalVolume}</h2> </p>
<p>MKT CAP <h2>{props.location.state.marketCap}</h2></p>
<p>P/E RATIO <h2>{props.location.state.peRatio}</h2></p>
<p>DIV/YIELD</p>
</div>
</div>
</div>
</Layout>;
};
export default Details;
ChartData.js
import React, {useState, useEffect } from "react";
import { Line } from "react-chartjs-2";
const ChartData = props => {
const [yourState, setYourState] = useState('');
const chart = () => {
setYourState({
labels: ['x', 'y'],
datasets: [
{
level: 'level of xyz',
data: [22, 55]
}
]
})
}
useEffect(() => {
chart()
}, [])
return(
<div>
<h1>Hello</h1>
{props.location.state.companyName}
<div>
<Line data={yourState}/>
</div>
</div>
)
}
export default ChartData;
There's a quite a bit going on here that needs clarification. You mention graphql in the title, but there's no graphql in your code.
You are using axios to fetch data at runtime in the componentDidMount lifecycle method, and then setting the result to state.
I assume that once you have that data, all you want to do is pass it to your chart component so that it can render itself on the index page.
Consider the following example which does the same thing; Fetches some data from the Rick & Morty api, sets the results to state, and passes the relevant part of that state via props directly to the <Characters /> component.
From there, the <Characters /> component has everything it needs in order to render. (It has no state, and is not concerned about where the data actually came from).
// index.js
import React from 'react';
import './App.css';
import Characters from './Characters'
const api = "https://rickandmortyapi.com/api/character/";
class IndexPage extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch(api)
.then(res => res.json())
.then(
json => {
console.log(json)
this.setState({
isLoaded: true,
data: json.results
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
error => {
this.setState({
isLoaded: true,
error
});
}
);
}
render() {
const { error, isLoaded, data } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<Characters data={data} />
);
}
}
}
export default IndexPage;
// Characters.js
import React from 'react';
class Characters extends React.Component {
render() {
return (
<ul>
{this.props.data.map(item => (
<li key={item.id}>
<dl>
<dt>Name:</dt>
<dd>{item.name}</dd>
<dt>Species:</dt>
<dd>{item.species}</dd>
<dt>Status:</dt>
<dd>{item.status}</dd>
</dl>
</li>
))}
</ul>
);
}
}
export default Characters;
Codesandbox Example using functional components and hooks
Gatsby’s <Link> component allows you to link between pages (and does some other stuff like prefetching resources, and can share data between pages). As you are rendering the <ChartData /> component on the index page, this is not required to solve your problem.
Using <Link> with state works because details is a gatsby page. As <ChartData> is not a page, you can't *link* to it.

ReactJS TypeError: Cannot read property 'eventEmitter' of undefined

I'm currently building a ReactJS Weather app where I have a drop-down list with different cities and a container with the information about weather on the selected city. When i fetch the weather data from an API i have a default city and I want to refetch the data when user selects another city in the dropdown list.
I will provide you with the code.
App.jsx class (the main class)
import React, { Component } from "react";
import "./sass/app.scss";
import axios from "axios";
import { Dropdown } from "semantic-ui-react";
import NavigationBar from "./components/NavigationBar";
import WeatherComponent from "./components/WeatherComponent";
import { locationOptions } from "./locations.js";
const WEATHER_KEY = "5f0f0f2a61c0f3f650984fb442f03d86";
class App extends Component {
constructor(props) {
super(props);
this.state = {
cityName: "Pristina",
isLoading: true,
isSelectedLocationOpen: false
};
}
componentDidMount() {
const { cityName } = this.state;
const { eventEmitter } = this.props;
const URL = `http://api.weatherstack.com/current?access_key=${WEATHER_KEY}&query=${cityName}`;
axios
.get(URL)
.then(res => {
return res.data;
})
.then(data => {
this.setState({
isLoading: false,
name: data.location.name,
country: data.location.country,
temperature: data.current.temperature,
weather_descriptions: data.current.weather_descriptions[0],
weather_icons: data.current.weather_icons[0],
observation_time: data.current.observation_time
});
})
.catch(err => {
console.error("Cannot fetch weatcher from API", err);
});
eventEmitter.on("updateLocation", data => {
this.setState({ cityName: data });
});
}
handleChange() {
const { eventEmitter } = this.props;
const { cityName } = this.state;
eventEmitter.emit("updateLocation", cityName);
}
render() {
const {
isLoading,
name,
temperature,
weather_descriptions,
weather_icons,
observation_time,
country
} = this.state;
return (
<div className="main-container">
<div className="first-container">
<div className="wrapper">
{isLoading && <h3>Loading ...</h3>}
<NavigationBar />
{!isLoading && (
<WeatherComponent
className="weather-container"
name={name}
temperature={temperature}
weather_descriptions={weather_descriptions}
weather_icons={weather_icons}
observation_time={observation_time}
country={country}
/>
)}
<Dropdown
placeholder="Select location"
search
selection
defaultValue={this.state.cityName}
options={locationOptions.map(item => {
return {
key: item.key,
value: item.value,
text: item.text
};
})}
onChange={this.handleChange}
value={locationOptions.value}
/>
</div>
</div>
</div>
);
}
}
export default App;
store.js class
import React from "react";
import { EventEmitter } from "events";
export default class Store extends React.Component {
constructor(props) {
super(props);
this.eventEmitter = new EventEmitter();
// Main App State
this.state = {
appName: "Weather App"
};
}
render() {
return React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
...this.state,
eventEmitter: this.eventEmitter
});
});
}
}
WeatherComponent.js
import React from "react";
import "../sass/weather.scss";
import sunnyIcon from "../assets/sunnyicon.png";
import sun from "../assets/sunicon.png";
export default class WeatherComponent extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
// weather_descriptions i have to find a better icon for current weather
render() {
const {
temperature,
weather_descriptions,
observation_time,
name,
country
} = this.props;
return (
<div className="weather-container">
<div className="location-container">
<img src={sunnyIcon} className="logo2" alt="" />
<h1 className="total-weather-report">Today's weather report</h1>
<h1 className="location">{`${name}, ${country}`}</h1>
</div>
<div className="degree-container">
<img src={sunnyIcon} className="weather-logo2" alt="" />
<h2 className="degree-value">{`${temperature}°C`}</h2>
</div>
<div className="info-container">
<h2 className="local-weather-report">Local Weather Report</h2>
<div className="hr"></div>
<img src={sun} className="sun-icon" alt="" />
<h2 className="day">Sunday</h2>
<h2 className="weather-type">{weather_descriptions}</h2>
<h2 className="last-observation">Last observed on:</h2>
<h2 className="observation-time">{observation_time}</h2>
</div>
<div className="weekly-weather"></div>
</div>
);
}
}
When I run the app everything works but when I try to change the city from the dropdown, it crashes and this error pops-up.
The error
EventEmitter is part of the NodeJS API, is not available for browsers.
EDIT:
In App.jsx you have a function called "handleChange", that function should do the same thing you are doing on "componenDidMount" but using the actual value of the Dropdown, you don't need to manually create events.
Hope it helps

"Cannot read property 'map' of undefined" within React, what's wrong here?

Trying to get my head around props so forgive me if its a silly mistake. I am trying to pass all of my data into one variable and pass that out into props (using {item.text} and {item.key}), however, my ".map" isn't picking up anything and there's a bunch of errors, what's wrong with my code?
The problem lays specifically here in this block of code
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
Here is the code in full
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoListt extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList(input) {
let listArray = this.state.list;
listArray.push(input);
var newItem = {
text: listArray,
key: Date.now()
};
this.setState(prevState => {
return {
list: prevState.list.concat(newItem)
};
});
this.setState({
list: listArray
})
}
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList(this.state.userInput)}>Press me</button>
<ul>
{this.testingSetup()}
</ul>
</div>
);
}
}
export default TodoListt;
You can use the spread operator to add to an existing array. Simply add a new object to the array in the state, and then clear the user input, ready for another item. Based on your code, here's a simple example of adding to a state list (haven't run myself, so just check for syntax errors and such):
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoList extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList() {
const { list, userInput } = this.state;
// Add item to state list using spread operator and clear input
this.setState({
list: [...list, {text:userInput, key: Date.now()}],
userInput: ""
});
}
render() {
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList()}>Press me</button>
<hr/>
{/* For each item in the list, render the contents */}
{this.state.list.map(item => (
<div key={item.key}>
<h3>{item.text}</h3>
<p>Time: {item.key}</p>
</div>
))}
</div>
);
}
}
export default TodoList;

React.js moving on to the next list

I'm making a movie search page. When I search something, it goes through the data base and find the very first match and display on the page. However, I want to create a function, so when I click next, page displays next movie in the data base. My code follows:
import React, { Component, PropTypes } from 'react';
import SearchBar from './Bar/index.js';
import SearchResult from './Result/index.js';
import axios from 'axios';
import './index.css';
class SearchArea extends Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
searchResult: {},
result: false,
count: 0
};
}
handleSearchBarChange(event) {
this.setState({searchText: event.target.value});
}
handleSearchBarSubmit(event) {
event.preventDefault();
const movie = this.state.searchText;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=c6cd73ec4677bc1d7b6560505cf4f453&language=en-US&query=${movie}&page=1&include_adult=false`)
.then(response => {
if(response.data.results.length >= 0) {
const i = 0;
const {
title,
overview,
release_date: releaseDate
} = response.data.results[this.state.count];
const posterPath = 'https://image.tmdb.org/t/p/w154' + response.data.results[this.state.count].poster_path;
this.setState({
searchResult: {
title,
posterPath,
overview,
releaseDate
},
result: true
});
}
else {
this.setState({
searchResult: {
title: 'No Result',
overview: 'No Overview Available',
posterPath: ''
},
result: true
});
}
})
}
handleSearchNext(event) {
this.handelSearchBarSubmit.overview = response.data.results[1];
}
handleResultClose() {
this.setState({
searchResult: {},
result: false
});
}
render() {
return (
<div>
<SearchBar
value = {this.state.searchText}
onChange = {this.handleSearchBarChange.bind(this)}
onSubmit = {this.handleSearchBarSubmit.bind(this)}
onNext = {this.handleSearchNext.bind(this)}
/>
{this.state.result &&
<SearchResult
searchResult = {this.state.searchResult}
onClose = {this.handleResultClose.bind(this)}
onAdd = {this.props.onAdd}
/>
}
</div>
);
}
}
SearchArea.propTypes = {
onAdd: PropTypes.func.isRequired
};
export default SearchArea;
I can't seem to figure out how to make handleSearchNext. Please help
EDIT
Following is the SearchBar code
import React, { PropTypes } from 'react';
import { Button } from 'semantic-ui-react';
import styles from './index.css';
const SearchBar = (props) => {
return (
<div>
<form onSubmit={(event) => props.onSubmit(event)}>
<input
className="searchBar"
type="text"
placeholder="Search Here"
value={props.value}this
onChange={(event) => props.onChange(event)}
onNext={(event) => props.onChange(event)}
onBack={(event) => props.onChange(event)}
/>
<Button className="button" type="submit">Sumbit</Button>
</form>
<Button className={styles.button} type="previous">Back</Button>
<Button className="button" type="next">Next</Button>
</div>
);
};
SearchBar.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
onNext: PropTypes.func.isRequired
};
export default SearchBar;
You could have your server respond with not only the requested title, but also the next one. That way, when you click on Next, you can immediately display the next movie without waiting for a response, while still querying it in the background by name or id (so that you have the next after it, etc.).
Edit: If I misunderstood what you meant and you already have this (it looks like you are actually querying a whole page of movies at once), you probably simply want something like
handleSearchNext(event) {
this.setState({ searchResult: response.data.results[1], result: true });
}
and handle specially the case when you hit the last item on the page.

Categories

Resources