I'm trying to get some map over some data and render it as a card style component.
The data structure is as follows within my user array (all fake data)
and this is the code...
import React from "react";
import OverlayContent from "./OverlayContent";
import { onCountryClick } from "../Scene3D/AppSignals";
import Portal from "./Portal";
import "./style.scss";
class Overlay extends React.Component {
constructor(props) {
super(props);
this.state = { overlay: false, users: [], country: [] };
this.openOverlay = this.openOverlay.bind(this);
this.closeOverlay = this.closeOverlay.bind(this);
}
openOverlay() {
this.setState({ overlay: true });
}
closeOverlay() {
this.setState({ overlay: false });
}
onCountryClick = (country, users) => {
this.openOverlay();
this.setState({ users: [users], country });
};
componentDidMount() {
this.onCountrySignal = onCountryClick.add(this.onCountryClick);
}
componentWillUnmount() {
this.onCountrySignal.detach();
}
render() {
const country = this.state.country;
const users = this.state.users;
console.log(users);
return (
<div className="btn-container">
{this.state.overlay && (
<Portal>
<div>
<h1>{country}</h1>
{users &&
users.map(user => (
<div className="user_container">
<h1 key={user.id} className="user_name">
{user.favouriteQuote}
</h1>
</div>
))}
<button className="btn" onClick={this.closeOverlay}>
Close
</button>
</div>
</Portal>
)}
</div>
);
}
}
export default Overlay;
When mapping on this user array it doesn't appear to give me access to the actual user data I need. Is it because I have an array within an array?
onCountryClick = (country, users) => {
this.openOverlay();
this.setState({ users: [users], country });
};
Is users an array? If so remove the brackets here or map through the first element. users[0].map()
Related
I'm attempting to put data that I'm getting from an API onto a modal that will appear whenever a button is clicked.
How is this done? I'm able to use the data from the API without the modal, so I know it's not an issue with the syntax of my componentDidMount(). Not sure what the issue is and how it can be resolved.
import React from 'react';
import './App.css';
import Nav from './Nav';
import Meal from './Meal';
import meals from './Meals';
import Modal1 from './Modal'
function App() {
const mealArr = meals.map(item => <Meal food={item.food} picture={item.picture} type={item.id} />)
return (
<div className="content">
<Nav />
{mealArr}
<Modal1 isOpen={false}/>
</div>
);
}
export default App;
import React from 'react';
import Modal from 'react-modal';
class Modal1 extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
isLoaded: false
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json
})
})
}
render() {
const allItems = this.state.items;
let itemArr = allItems.map(item =>
<div>
<ul>
<li key={item.id}>{item.name}</li>
</ul>
</div>)
return (
<div>
<Modal>
{itemArr}
</Modal>
</div>
)
}
}
export default Modal1;
import React, {Component} from 'react';
import Modal1 from 'react-modal';
class Meal extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
}
this.handleClick = this.handleClick.bind(this);
this.turnOff = this.turnOff.bind(this);
}
handleClick() {
this.setState({isOpen: true})
}
turnOff() {
this.setState({isOpen: false})
}
render() {
return (
<div className="meal-container">
<h2>{this.props.type}</h2>
<h1>{this.props.food}</h1>
<img alt="" src={this.props.picture} />
<p className="steps-button" onClick={this.handleClick}>Steps</p>
<Modal1 className="modal-1" isOpen={this.state.isOpen}/>
</div>
)
}
}
export default Meal;
take a look at allItems, it's an empty array before you get the data from the API.
So, for the first render (before component did mount):
const allItems = this.state.items // ----> const allItems = []
mapping through an empty array will not produce any error and return another empty array, but when you map through an empty array, don't expect to have any item or item.name. so the itemArr is not as your expectation and cause the issue with rendering it.
to avoid from this issue, check your allItems to ensure that the data has arrived.
const allItems = this.state.items;
let itemArr = []
if (allItems.length > 0) {
itemArr = allItems.map(item => (
<div>
<ul>
<li key={item.id}>{item.name}</li>
</ul>
</div>
)
}
return (
<div>
<Modal>
{itemArr}
</Modal>
</div>
)
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>
);
}
}
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
I am facing such problem, i got my array of records fetched from an API, mapped it into single elements and outputting them as single components. I have function which changes state of parent Component, passes value to child component and child component should hide/show div content after button is clicked.
Of course. It is working, but partially - my all divs are being hidden/shown. I have set specific key to each child component but it doesn't work.
App.js
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
import countries from '../../countriesList';
import CitySearchForm from './CitySearchForm/CitySearchForm';
import CityOutput from './CityOutput/CityOutput';
import ErrorMessage from './ErrorMessage/ErrorMessage';
class App extends Component {
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false
}
getCities = (e) => {
e.preventDefault();
const countryName = e.target.elements.country.value.charAt(0).toUpperCase() + e.target.elements.country.value.slice(1);
const countryUrl = 'https://api.openaq.org/v1/countries';
const wikiUrl ='https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&format=json&category=city&redirects&origin=*&titles=';
const allowedCountries = new RegExp(/spain|germany|poland|france/, 'i');
if (allowedCountries.test(countryName)) {
axios
.get(countryUrl)
.then( response => {
const country = response.data.results.find(el => el.name === countryName);
return axios.get(`https://api.openaq.org/v1/cities?country=${country.code}&order_by=count&sort=desc&limit=10`)
})
.then( response => {
const cities = response.data.results.map(record => {
return { name: record.city };
});
cities.forEach(city => {
axios
.get(wikiUrl + city.name)
.then( response => {
let id;
for (let key in response.data.query.pages) {
id = key;
}
const description = response.data.query.pages[id].extract;
this.setState(prevState => ({
cities: [...prevState.cities, {city: `${city.name}`, description}],
infoMessage: prevState.infoMessage = ''
}))
})
})
})
.catch(error => {
console.log('oopsie, something went wrong', error)
})
} else {
this.setState(prevState => ({
infoMessage: prevState.infoMessage = 'This is demo version of our application and is working only for Spain, Poland, Germany and France',
cities: [...prevState.cities = []]
}))
}
}
descriptionTogglerHandler = () => {
this.setState((prevState) => {
return { visible: !prevState.visible};
});
};
render () {
return (
<div className="App">
<ErrorMessage error={this.state.infoMessage}/>
<div className="form-wrapper">
<CitySearchForm getCities={this.getCities} getInformation={this.getInformation} countries={countries}/>
</div>
{this.state.cities.map(({ city, description }) => (
<CityOutput
key={city}
city={city}
description={description}
show={this.state.visible}
descriptionToggler={this.descriptionTogglerHandler} />
))}
</div>
);
}
}
export default App;
CityOutput.js
import React, { Component } from 'react';
import './CityOutput.css';
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show } = this.props;
let descriptionClasses = 'output-record description'
if (show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
export default CityOutput;
Put the visible key and the toggle function in the CityOutput instead of having it in the parent
import React, { Component } from "react";
import "./CityOutput.css";
class CityOutput extends Component {
state = {
visible: true
};
descriptionTogglerHandler = () => {
this.setState({ visible: !this.state.visible });
};
render() {
const { city, description } = this.props;
let descriptionClasses = "output-record description";
if (this.state.visible) {
descriptionClasses = "output-record description open";
}
return (
<div className="output">
<div className="output-record">
<b>City:</b> {city}
</div>
<button onClick={() => this.descriptionTogglerHandler()}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
);
}
}
export default CityOutput;
There are two ways of how I would approach this,
The first one is setting in your state a key property and check and compare that key with the child keys like:
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false.
currKey: 0
}
descriptionTogglerHandler = (key) => {
this.setState((prevState) => {
return { currKey: key, visible: !prevState.visible};
});
};
// then in your child component
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show, currKey, elKey } = this.props;
let descriptionClasses = 'output-record description'
if (show && elKey === currKey) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={() => descriptionToggler(elKey)}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
The other way is to set an isolated state for every child component
class CityOutput extends Component {
constructor(props) {
this.state = {
show: false
}
}
function descriptionToggler() {
const {show} = this.state;
this.setState({
show: !show
})
}
render() {
const { city, descriptionToggler, description } = this.props;
let descriptionClasses = 'output-record description'
if (this.state.show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
I hope this helps ;)
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;