This question already has an answer here:
List elements not rendering in React [duplicate]
(1 answer)
Closed 24 days ago.
I am going through a react course and currently learning react's lifecycle method. So far, I have been able to call the API using componentDidMount and have set the state. However, I can't seem to get the card components to show the images in the cardlist div. I'm sure I've done it correctly (looping through the state and creating a Card component with the props).
import react, {Component} from 'react';
import axios from 'axios';
import Card from './Card';
const api = ' https://deckofcardsapi.com/api/deck/';
class CardList extends Component {
state = {
deck: '',
drawn: []
}
componentDidMount = async() => {
let response = await axios.get(`${api}new/shuffle`);
this.setState({ deck: response.data })
}
getCards = async() => {
const deck_id = this.state.deck.deck_id;
let response = await axios.get(`${api}${deck_id}/draw/`);
let card = response.data.cards[0];
this.setState(st => ({
drawn: [
...st.drawn, {
id: card.code,
image: card.image,
name: `${card.value} of ${card.suit}`
}
]
}))
}
render(){
const cards = this.state.drawn.map(c => {
<Card image={c.image} key={c.id} name={c.name} />
})
return (
<div className="CardList">
<button onClick={this.getCards}>Get Cards</button>
{cards}
</div>
)
}
}
export default CardList;
import react, {Component} from 'react';
class Card extends Component {
render(){
return (
<img src={this.props.image} alt={this.props.name} />
)
}
}
export default Card;
import CardList from './CardList';
function App() {
return (
<div className="App">
<CardList />
</div>
);
}
export default App;
Your map function:
const cards = this.state.drawn.map(c => {
<Card image={c.image} key={c.id} name={c.name} />
})
does not return anything. So the result of this code is an array of undefined.
You have two options:
Add return:
const cards = this.state.drawn.map(c => {
return <Card image={c.image} key={c.id} name={c.name} />
})
Wrap in (), not {}:
const cards = this.state.drawn.map(c => (
<Card image={c.image} key={c.id} name={c.name} />
))
You should return the card component inside the map
const cards = this.state.drawn.map(c => {
return <Card image={c.image} key={c.id} name={c.name} />
})
Related
I am trying to render a popup when a card is clicked. My problem is that I can't get the showPopup function running on click of the card component.
//... all required imports
class App extends Component {
constructor() {
super();
this.state = {
monsters: [],
searchField: ''
};
}
componentDidMount() {
// Fetches monsters and updates the state (working fine)
}
showPopup = () => {
console.log(2);
};
render() {
const { monsters, searchField } = this.state;
const filteredMonsters = monsters.filter(monster => monster.name.toLowerCase().includes(searchField.toLowerCase()));
return (
<div className="App">
<CardList className="name" monsters={filteredMonsters} showPopup={e => this.showPopup(e)} />
</div>
);
}
}
Following is the code for my CardList component
import React from 'react';
import { Card } from '../card/card.comp';
import './card-list.styles.css';
export const CardList = props => {
return (
<div className="card-list">
{props.monsters.map(monster => (
<Card key={monster.id} monster={monster} onClick={props.showPopup} />
))}
</div>
);
};
The onclick function above is not working as expected. Please help me find out the problem.
EDIT The code for card component
import React from 'react';
import './card.styles.css';
export const Card = props => {
return (
<div className="card-container">
<img src={`https://robohash.org/${props.monster.id}?set=5&size=150x150`} alt="Monster" />
<h2 key={props.monster.id}>{props.monster.name}</h2>
<p>{props.monster.email}</p>
</div>
);
};
I' m new to React and I'm building a simple React app that displays all the nations of the world on the screen and a small search bar that shows the data of the searched nation.
Here an image of the site
But I don't know how to show the country you want to click in the scrollbar.
Here the app.js code:
import React, { Component } from 'react';
import './App.css';
import NavBar from '../Components/NavBar';
import SideBar from './SideBar';
import CountryList from '../Components/SideBarComponents/CountryList';
import Scroll from '../Components/SideBarComponents/Scroll';
import Main from './Main';
import SearchCountry from '../Components/MainComponents/SearchCountry';
import SearchedCountry from '../Components/MainComponents/SearchedCountry';
import Datas from '../Components/MainComponents/Datas';
class App extends Component {
constructor() {
super();
this.state = {
nations: [],
searchField: '',
button: false
}
}
onSearchChange = (event) => {
this.setState({searchField: event.target.value});
console.log(this.state.searchField)
}
onClickChange = () => {
this.setState(prevsState => ({
button: true
}))
}
render() {
const {nations, searchField, button, searchMemory} = this.state;
const searchedNation = nations.filter(nation => {
if(button) {
return nation.name.toLowerCase().includes(searchField.toLowerCase())
}
});
return (
<div>
<div>
<NavBar/>
</div>
<Main>
<div className='backgr-img'>
<SearchCountry searchChange={this.onSearchChange} clickChange={this.onClickChange}/>
<SearchedCountry nations={searchedNation}/>
</div>
<Datas nations={searchedNation}/>
</Main>
<SideBar>
<Scroll className='scroll'>
<CountryList nations={nations} clickFunc/>
</Scroll>
</SideBar>
</div>
);
}
componentDidMount() {
fetch('https://restcountries.eu/rest/v2/all')
.then(response => response.json())
.then(x => this.setState({nations: x}));
}
componentDidUpdate() {
this.state.button = false;
}
}
export default App;
The countryList:
import React from 'react';
import Images from './Images';
const CountryList = ({nations, clickFunc}) => {
return (
<div className='container' style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(115px, 3fr))'}}>
{
nations.map((country, i) => {
return (
<Images
key={country.numericCode}
name={country.name}
flag={country.flag}
clickChange={clickFunc}
/>
);
})
}
</div>
)
}
export default CountryList;
And the images.js:
import React from 'react';
import './images.css'
const Images = ({name, capital, region, population, flag, numericCode, clickChange}) => {
return (
<div className='hover bg-navy pa2 ma1 tc w10' onClick={clickChange = () => name}>
<img alt='flag' src={flag} />
<div>
<h6 className='ma0 white'>{name}</h6>
{capital}
{region}
{population}
{numericCode}
</div>
</div>
);
}
export default Images;
I had thought of using the onClick event on the single nation that was going to return the name of the clicked nation. After that I would have entered the name in the searchField and set the button to true in order to run the searchedNation function.
I thank anyone who gives me an answer in advance.
To keep the actual structure, you can try using onClickChange in Images:
onClickChange = (newName = null) => {
if(newName) {
this.setState(prevsState => ({
searchField: newName
}))
}
// old code continues
this.setState(prevsState => ({
button: true
}))
}
then in onClick of Images you call:
onClick={() => {clickChange(name)}}
Or you can try as well use react hooks (but this will require some refactoring) cause you'll need to change a property from a parent component.
With that you can use useState hook to change the value from parent component (from Images to App):
const [searchField, setSearchField] = useState('');
Then you pass setSearchField to images as props and changes the searchField value when Images is clicked:
onClick={() => {
clickChange()
setSearchField(name)
}}
I am doing a small project and have a list of components that display information about countries. Now I have added react router so that when I click on a card it displays more information about that country. Now when I click on the card nothing happens! Below is the code for the Countries.
import React, { Component } from 'react';
import { CountryList } from './Components/Card-List/CountryList';
import { SearchBox } from './Components/Search-box/Search-Box';
import './Countries.styles.css';
import { DetailCountryCard } from './Components/DetailCountryCard/DetailCountryCard';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
class Countries extends Component {
constructor() {
super();
this.state = {
countries:[],
searchField:"",
regionField:"",
darkMode: false
}
this.setDarkMode = this.setDarkMode.bind(this);
};
componentDidMount() {
fetch("https://restcountries.eu/rest/v2/all")
.then(response => response.json())
.then(all => this.setState({ countries: all,
regions: all}))
.catch(error => console.log("I have errored" + error));
}
setDarkMode(e){
this.setState((prevState) => ({ darkMode: !prevState.darkMode }));
}
render() {
const { countries, searchField, regionField, darkMode } = this.state;
const filterCountries = countries.filter((country) => country.name.toLowerCase().includes(searchField.toLowerCase()) &&
country.region.toLowerCase().includes(regionField.toLowerCase()));
return(
<Router>
<div className={darkMode ? "dark-mode" : "light-mode" }>
<nav className="navbar-items">
<h1 className="header">Where in the World</h1>
<div className="moon-end">
<button onClick={this.setDarkMode}>
<i className={darkMode ? "moon fas fa-moon" : "moon far fa-moon" }></i>
</button>
<h2>{darkMode ? "Dark Mode" : "Light Mode" }</h2>
</div>
</nav>
<div className="Input">
< SearchBox type="search" placeholder="Search a Country" handlechange={e=> this.setState({
searchField: e.target.value })}
/>
< SearchBox type="regions" placeholder="Filter by Regions" handlechange={e=> this.setState({
regionField: e.target.value })}
/>
</div>
<CountryList countries={filterCountries} />
{/* <Route path="/" exact component={Countries} /> */}
<Switch>
<Route path="/card-detail/:name" component={ DetailCountryCard } exact/>
</Switch>
</div>
</Router>
);
}
}
export default Countries
The link for each card is in the following component:
import React from 'react';
import './CountryList.styles.css';
import {Link} from 'react-router-dom'
import { CountryCard } from '../Card/CountryCard';
export const CountryList = (props) => (
<div className='card-list'>
{props.countries.map(country => (
<Link to={`/card-detail/${country.name}`} >
<CountryCard key={country.alpha2Code} country={country} />
</Link>
))}
</div>
);
This should go to the following component:
import React from 'react';
import { useEffect } from 'react';
import { useState } from 'react';
export const DetailCountryCard = ({match}) => {
useEffect(() => {
fetchItem();
console.log(match);
},[])
const [country, setCountry] = useState([])
const fetchItem = async ()=> {
const fetchCountry = await fetch(`https://restcountries.eu/rest/v2/name/${match.params.name}`);
const countries = await fetchCountry.json();
setCountry(countries);
console.log(country);
}
return (
<div>
{country.map(town => (
<div>
<h1 key={town.alpha2Code}>{town.name}</h1>
<p>Native Name{town.nativeName}</p>
<p>Region: {town.region}</p>
<p>Languages: {town.languages[0].name}</p>
</div>
))}
</div>
);
}
Not sure what I am missing. I don't think I have done a typo on the component. So not sure why it is not rendering? Any help would be appreciated.
You just need add dependency of match in useEffect in DetailCountryCard. Because [] its similar in Class ComponentcomponentDidMount()` and you need to listen when match it's changed.
This is final code to DetailCountryCard:
import React from "react";
import { useEffect } from "react";
import { useState } from "react";
export const DetailCountryCard = ({ match }) => {
useEffect(() => {
fetchItem();
console.log(match);
}, [match]);
const [country, setCountry] = useState([]);
const fetchItem = async () => {
const fetchCountry = await fetch(
`https://restcountries.eu/rest/v2/name/${match.params.name}`
);
const countries = await fetchCountry.json();
setCountry(countries);
console.log(country);
};
return (
<div>
{country.map(town => (
<div>
<h1 key={town.alpha2Code}>{town.name}</h1>
<p>Native Name{town.nativeName}</p>
<p>Region: {town.region}</p>
<p>Languages: {town.languages[0].name}</p>
</div>
))}
</div>
);
};
I tested in CodeSandBox and it works!
Link
I would like to update the parent state from child component, which renders each object of the array of objects. The main goal of the child component is to update the original value from the array of objects.
I've the following code
Parent:
import { useState } from 'react';
import ExpenseItem from './expenseItem';
function Update({ data }) {
const [ expenses, setExpenses ] = useState(data);
return (
<div>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} {...expense} />;
})}
<button>Save</button>
</div>
);
}
export default Update;
child:
import { useState, useRef } from 'react';
function ExpenseItem({ description, date, credit, debit }) {
const [ edit, setEdit ] = useState(false);
const [ expenseDescription, setExpenseDescription ] = useState(description);
const textInput = useRef();
const renderDefaultView = () => {
return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
};
const renderEditView = () => {
return (
<div>
<input
type="text"
ref={textInput}
defaultValue={expenseDescription}
onDoubleClick={() => setEdit(true)}
/>
<button onClick={() => setEdit(false)}>X</button>
<button onClick={() => updateValue()}>OK</button>
</div>
);
};
const updateValue = () => {
const value = textInput.current.value;
setExpenseDescription(value);
textInput.current.defaultValue = value;
setEdit(false);
};
return (
<div>
{edit ? renderEditView() : renderDefaultView()}
<span>{date}</span>
<p>{debit}</p>
<p>{credit}</p>
</div>
);
}
export default ExpenseItem;
Once way, is to pass the parent state property (expenses) and the function that updates it (setExpenses) to the child Component via the props:
Parent:
import React from 'react';
import ReactDOM from 'react-dom';
import { useState } from 'react';
import ExpenseItem from './ExpenseItem';
function Update({ data }) {
const [ expenses, setExpenses ] = useState(data);
return (
<div>
Checking: { expenses[0].description } | { expenses[1].description }
<hr/>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} index={index} expenses={expenses} setExpenses={setExpenses} />;
})}
<button>Save</button>
</div>
);
}
export default Update;
Child:
import React from 'react';
import { useState, useRef } from 'react';
function ExpenseItem( props ) {
let { description, date, credit, debit } = props.expenses[props.index];
const setExpenses = props.setExpenses;
const [ edit, setEdit ] = useState(false);
const [ expenseDescription, setExpenseDescription ] = useState(description);
const textInput = useRef();
const renderDefaultView = () => {
return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
};
const renderEditView = () => {
return (
<div>
<input
type="text"
ref={textInput}
defaultValue={expenseDescription}
onDoubleClick={() => setEdit(true)}
/>
<button onClick={() => setEdit(false)}>X</button>
<button onClick={() => updateValue()}>OK</button>
</div>
);
};
const updateValue = () => {
const value = textInput.current.value;
setExpenseDescription(value);
textInput.current.defaultValue = value;
setEdit(false);
const expenses = [ ...props.expenses ]; // Get a copy of the expenses array
// Replace the current expense item
expenses.splice( props.index, 1, {
description: value, date, credit, debit
});
// Update the parent state
setExpenses( expenses );
};
return (
<div>
{edit ? renderEditView() : renderDefaultView()}
<span>{date}</span>
<p>{debit}</p>
<p>{credit}</p>
</div>
);
}
export default ExpenseItem;
Working demo
This can get really complicated as you move along, so the best option is to look for some sort of State Management solution, like using the Context API.
Also, take a look at this interesting post that talks about using the map index value as a key value: Index as a key is an anti-pattern
I am trying to make an application from The movie data base api.
I came across a small problem.
I have two components. In first I use fetch and I use the map() function for the Card component in which I would like to display data from the api. How to connect them correctly?
https://codesandbox.io/s/p3vxqqz53q
First component for render list:
import React, { Component } from 'react';
import Card from "./Card";
class ListApp extends Component {
constructor(props){
super(props);
this.state = {
items: [],
isLoaded: false,
}
};
componentDidMount = () => {
fetch("https://api.themoviedb.org/3/movie/popular?api_key=xxxxxxxx&page=1")
.then(resp => resp.json())
.then(resp => {
this.setState({
isLoaded: true,
items: resp.results
})
console.log(this.state.items)
})};
render() {
var {isLoaded, items} = this.state;
return (
<div>
{items.map( () => ( <Card/> ) )};
</div>
);
}
}
export default ListApp;
Second component Card:
import React from 'react';
const Card = (items) => {
return (
<div className="movie-container">
<img src="https://image.tmdb.org/t/p/w185/{items.poster_path}" alt="NO PHOTO" className="movie-container__img" />
<div className="movie-container__about">
<span className="movie-container__percent">{items.vote_average}</span>
<h2 className="movie-container__title">{items.original_title}</h2>
<p className="movie-container__date">{items.release_date}</p>
<p className="movie-container__text">{items.overview}</p>
MORE
</div>
</div>
)
}
export default Card;
You need to pass the item object as a prop to the Card component like
{items.map(item => <Card key={item.id} item={item} /> )}
and then access item from within the Card component like
const Card = (props) => {
const {item} = props;
...
}
This code should work.
The map in the ListApp as #Aakash suggested:
render() {
var { isLoaded, items } = this.state;
return (
<div>
{items.map(item => (<Card key={item.id} item={item} />))};
</div>
);
}
An Card correctly referencing the item prop:
// Card.js
import React from 'react';
const Card = (props) => {
const { item } = props;
return (
<div className="movie-container">
<img src="https://image.tmdb.org/t/p/w185/{items.poster_path}" alt="NO PHOTO" className="movie-container__img" />
<div className="movie-container__about">
<span className="movie-container__percent">{item.vote_average}</span>
<h2 className="movie-container__title">{item.original_title}</h2>
<p className="movie-container__date">{item.release_date}</p>
<p className="movie-container__text">{item.overview}</p>
MORE
</div>
</div>
)
}
export default Card;