Add item from Fetch API to Array and Displaying new array in React - javascript

I'm learning react for the first time, I have an app where it fetches some data from a public API. I currently have it show 10 cards with random items from the API, and I have added a button to fetch a random item from the API and add it to the array, I managed to get the new item added to the array using push() but it does not show in the app itself. How can I make it that the new item is shown in the app as well?
Here is my code
Home.js
import { useState, useEffect} from "react";
import Card from './Card';
const Home = () => {
const [animals, setAnimals] = useState([]);
const handleDelete = (id) => {
const newAnimals = animals.filter(animal => animal.id !== id);
setAnimals(newAnimals);
}
useEffect(() => {
fetch('https://zoo-animal-api.herokuapp.com/animals/rand/10')
.then(res => {return res.json()})
.then(data => {
setAnimals(data);
});
}, []);
const handleAddAnimal = () => {
fetch('https://zoo-animal-api.herokuapp.com/animals/rand/')
.then(res => {return res.json()})
.then(data => {
animals.push(data);
console.log(animals);
//what to do after this
})
}
return (
<div className="home">
<h2>Animals</h2>
<button onClick={handleAddAnimal}>Add Animal</button>
<Card animals={animals} handleDelete={handleDelete}/>
</div>
);
}
export default Home;
Card.js
const Card = ({animals, handleDelete}) => {
// const animals = props.animals;
return (
<div className="col-3">
{animals.map((animal) => (
<div className="card" key={animal.id}>
<img
src={animal.image_link}
alt={animal.latin_name}
className="card-img-top"
/>
<div className="card-body">
<h3 className="card-title">{animal.name}</h3>
<p>Habitat: {animal.habitat}</p>
<button onClick={() => handleDelete(animal.id)}>Delete Animal</button>
</div>
</div>
))}
</div>
);
}
export default Card;
App.js
import Navbar from './navbar';
import Home from './Home';
function App() {
return (
<section id="app">
<div className="container">
<Navbar />
<div className="row">
<Home />
</div>
</div>
</section>
);
}
export default App;
Screenshot of what I see now
screenshot
(I was also wondering how to fix the items going down instead of side by side but wanted to fix the add button first)
Let me know if there's anything else I should add, any help is appreciated, thank you!

Rather using array.push() method. You try using
setTheArray([...theArray, newElement]); e.g in your case it will be setAnimals([...animals,data]) in your onClick event.
Let me know doest it solve your issue or not.

const handleAddAnimal = () => {
fetch('https://zoo-animal-api.herokuapp.com/animals/rand/')
.then(res => {return res.json()})
.then(data => {
setAnimals([...animals,data])
console.log(animals);
//what to do after this
})
}

Related

*any fuction* "is not a function" Reactjs fetch

i've tried to make a fetch and take one element of the data base by id on Reactjs, backend with javascript(node + express + sequelize), but i had some problems and i couldn't at anytime. im searched on google but i dont know hot to apply the solutions on my code... here some examples of my trying:
import { useState } from "react";
import { useParams, Link } from "react-router-dom";
import "./detallesBarrio.css";
const BarriosDetails = () => {
const { id } = useParams();
const [barrio, setBarrio] = useState({});
const loadBarrioDetails = () => {
fetch("http://localhost:3001/api/barrios")
.then((x) => x.json())
.then((y) => y.data)
// .then((z) => console.log(z))
.then((allBarrio) => setBarrio(allBarrio));
};
let byid = barrio.filter(obj => {
return obj.id === id;
});
console.log(byid);
loadBarrioDetails();
return (
<main className="barrio-details">
{byid.map(obj => {
return (
<div key={obj.id}>
<div>
<h1>Detalles de {obj.nombre}</h1>
<img src={obj.foto} alt="imagen no disponible" />
<article>
<Link to="/barrios">Volver</Link>
</article>
</div>
</div>
);
})}
</main>
);
};
export default BarriosDetails;
also my tryings:
-no filter function (problem: map is not a function)
import { useState } from "react";
import { useParams, Link } from "react-router-dom";
import "./detallesBarrio.css";
const BarriosDetails = () => {
const { id } = useParams();
const [barrio, setBarrio] = useState({});
const loadBarrioDetails = () => {
fetch(`http://localhost:3001/api/barrios`)
.then((x) => x.json())
.then((y) => y.data)
// .then((z) => console.log(z))
.then((allBarrio) => setBarrio(allBarrio));
};
loadBarrioDetails();
return (
<main className="barrio-details">
{barrio.map((barri) => {
return (
<div key={barri.id}>
<div>
<h1>Detalles de {barri[id].nombre}</h1>
<img src={barri[id].foto} alt="imagen no disponible" />
<article>
<Link to="/barrios">Volver</Link>
</article>
</div>
</div>
);
})}
</main>
);
};
export default BarriosDetails;
-no maping (problem: the code dont recognise 'nombre'(database camp') on '{barrio[id].nombre}')
import { useState } from "react";
import { useParams, Link } from "react-router-dom";
import "./detallesBarrio.css";
const BarriosDetails = () => {
const { id } = useParams();
const [barrio, setBarrio] = useState({});
const loadBarrioDetails = () => {
fetch(`http://localhost:3001/api/barrios`)
.then((x) => x.json())
.then((y) => y.data)
.then((z) => console.log(z))
.then((allBarrio) => setBarrio(allBarrio));
};
loadBarrioDetails();
return (
<main className="barrio-details">
<div>
<h1>Detalles de {barrio[id].nombre}</h1>
<img src={barrio[id].foto} alt="imagen no disponible" />
<article>
<Link to="/barrios">Volver</Link>
</article>
</div>
</main>
);
};
export default BarriosDetails;
yes console.log give me the correct array
You are getting map is not a function because you're initiating the state as an object. is should be useState([]) instead of useState({})
based on your code you want to filter by id and id is unique so it's better to use array.find(obj => obj.id === id) than using array.filter().
in your first snippet let byid = barrio.filter(obj => { return obj.id === id; }); is executed before running the fetch and loading data.
try using useEffect for this kind of needs and loading data as a side effect after mounting the component and you should check the existance of an object or array before accessing it useEffect(() => loadBarrioDetails(), [])
Or since you just need one single object you can use like this:
import { useState } from "react";
import { useParams, Link } from "react-router-dom";
import "./detallesBarrio.css";
const BarriosDetails = () => {
const { id } = useParams();
const [barrio, setBarrio] = useState(null);
const loadBarrioDetails = () => {
fetch(`http://localhost:3001/api/barrios`)
.then((x) => x.json())
.then((y) => y.data)
.then((allBarrio) => {
const byId = allBarrio.find(obj => obj === id);
setBarrio(byId ?? null);
});
};
useEffect(() => loadBarrioDetails(), []);
if (!barrio) return <></> // anything to display that the data is loading or doesn't exist depends on you need.
return (
<main className="barrio-details">
<div>
<h1>Detalles de {barrio.nombre}</h1>
<img src={barrio.foto} alt="imagen no disponible" />
<article>
<Link to="/barrios">Volver</Link>
</article>
</div>
</main>
);
};
export default BarriosDetails;

React OnClick iteration

I want to do an onClick counter but I have a problem with the counter iterating correctly. In the app there are 3 "products" and after clicking "Add To Cart" button the state of the object is updated but all of the products are generated separately. I think that is cousing the problem where the counter is different for each of the products or everything will work correctly if I lift the state up, but the console.log is just freshly generated for all of the products. I'm not really sure so I need help with that.
Here is some code in the order from the parent to the last child:
import { useEffect, useState } from "react";
import ProductList from "./ProductList";
const Products = () => {
const [products, setProducts] = useState (null);
useEffect (() => {
fetch('http://localhost:8000/products')
.then(res => {
return res.json();
})
.then(data => {
setProducts(data);
})
}, []);
return (
<div className="ProductList">
{products && <ProductList products={products}/>}
</div>
);
}
export default Products;
import Card from "./Card";
const ProductList = (props) => {
const products = props.products;
return (
<div className="ProductList" >
{products.map((product) => (
<Card product={product} key={product.id} />))}
</div>
);
}
export default ProductList;
import { useState } from "react";
const Card= ({ product }) => {
const [showDescription, setShowDescription] = useState(false);
const [CartCounter, setCartCounter ] = useState(0);
console.log(CartCounter);
return (
<div className="Product-Preview" >
<div className="backdrop" style={{ backgroundImage: `url(${product.image})` }}></div>
<h2>{product.title}</h2>
<div>{product.price}</div>
<button className="ShowDescription" onClick={() => setShowDescription(!showDescription)}>Details</button>
<button className="AddToCart" onClick={() => setCartCounter(CartCounter + 1)}>Add To Cart </button>
{showDescription && <p>{product.description}</p>}
<br />
</div>
);
};
export default Card;
Ok, you want to keep track of an aggregated value. I'll list code in some high level.
const ProductList = () => {
const [count, setCount] = useState(0)
const addOrRemove = n => { setCount(v => v + n) }
return products.map(p => <Card addOrRemove={addOrRemove} />)
}
const Card = ({ addOrRemove }) => {
// optional if you want to track card count
// const [count, setCount] = useState(0)
return (
<>
<button onClick={() => { addOrRemove(1) }>Add</button>
<button onClick={() => { addOrRemove(-1) }>Remove</button>
</>
)
}
Essentially either you track the local count or not, you need to let the parent to decide what is the final count, otherwise there'll be some out of sync issue between the child and parent.

How to pass data from child to parent and render content based on selected value in dropdown?

I am learning React as I am fetching data from Pokéapi to make a list component, card component, detail component and filter component. I am trying to make a filter so you can filter by pokémon type. Only the cards that also contain that type string should then render (Not there yet). So I am not sure if a) I should make a different call from API inside PokemonList depending on selected value or b) if I should compare the values and just change how the PokemonCard element is rendered inside PokemonList.js depending on the comparison. I managed to pass data from filter to the list component. I have then been trying to pass the type data from PokemonCard.js to the list component so that I can compare these two values but I find it hard to use callbacks to pass the type data from the card component, since I dont pass it through an event or something like that.
Which method should I use here to simplify the filtering? Make different API call or render PokemonCard element conditionally?
Is it a good idea to compare filter option to pokemon card's type in PokemonList.js? Then how can I pass that data from the card component since I don't pass it through click event?
Thankful for any ideas! I paste the code from list component that contains the cards, card component and filter component.
PokemonList component:
import { useState } from 'react';
import useSWR from 'swr';
import PokemonCard from './PokemonCard';
import PokemonFilter from './PokemonFilter';
import './PokemonList.css';
const PokemonList = () => {
const [index, setIndex] = useState(0);
const [type, setType] = useState('');
function selectedType(type) { // value from filter dropdown
setType(type)
console.log("handled")
console.log(type)
}
const url = `https://pokeapi.co/api/v2/pokemon?limit=9&offset=${index}`;
const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);
if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>
result.results.sort((a, b) => a.name < b.name ? -1 : 1);
return (
<section>
<PokemonFilter onSelectedType={selectedType} selectedPokemonType={type} />
<div className="pokemon-list">
<div className="pokemons">
{result.results.map((pokemon) => (
<PokemonCard key={pokemon.name} pokemon={pokemon} /> // callback needed??
))}
</div>
<div className="pagination">
<button
onClick={() => setIndex(index - 9)}
disabled={result.previous === null}
>
Previous
</button>
<button
onClick={() => setIndex(index + 9)}
disabled={result.next === null}
>
Next
</button>
</div>
</div>
</section>
)
}
export default PokemonList;
PokemonCard component:
import { Link } from "react-router-dom";
import useSWR from 'swr';
import './PokemonCard.css';
const PokemonCard = ({ pokemon }) => {
const { name } = pokemon;
const url = `https://pokeapi.co/api/v2/pokemon/${name}`;
const { data, error } = useSWR(url);
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
const { types, abilities } = data;
// types[0].type.name <---- value I want to pass to PokemonList.js
return (
<div className='pokemon-card'>
<div className='pokemon-card__content'>
<img
className='pokemon-card__image'
src={data.sprites.front_default}
alt={name}
/>
<div className='pokemon-card__info'>
<p className='pokemon-card__name'>Name: {name}</p>
<p className='pokemon-card__abilities'>Abilities: {abilities[0].ability.name}</p>
<p className='pokemon-card__categories'>Category: {types[0].type.name}</p>
</div>
</div>
<Link className='pokemon-card__link' to={{
pathname: `/${name}`,
state: data
}}>
View Details
</Link>
</div>
)
}
export default PokemonCard;
PokemonFilter component:
import './PokemonFilter.css';
import useSWR from 'swr';
const PokemonFilter = ({onSelectedType, selectedPokemonType}) => {
const url = `https://pokeapi.co/api/v2/type/`;
const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);
if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>
function filteredTypeHandler(e) {
console.log(e.target.value);
onSelectedType(e.target.value);
}
console.log(selectedPokemonType)
return(
<div className="pokemon-types__sidebar">
<h2>Filter Pokémon by type</h2>
<select
name="pokemon-type"
className="pokemon-types__filter"
onChange={filteredTypeHandler}
>
<option value="All">Filter By Type</option>
{result.results.map((type) => {
return (
<option key={type.name} value={type.name}> {type.name}</option>
)
})}
</select>
</div>
)
}
export default PokemonFilter;
Here is an example to improve, modify, ... I didn't test, it's just a visual example.
I don't know about useSWR sorry, I use axios in my example...
If you want to centralize all your API requests, you can create a useApi hook, on the internet you will find tutorials.
PokemonList.js
import React, { useState, useEffect } from 'react';
import axios from 'axios'; // or swr
import PokemonFilter from './PokemonFilter';
import PokemonCard from './PokemonCard';
export default function PokemonList() {
const [data, setData] = useState([]);
const [filter, setFilter] = useState('');
// Executed every first render
useEffect(() => {
getData();
}, []);
// Executed only when filter changes
useEffect(() => {
getDataByTypes(filter);
}, [filter]);
// Get data
const getData = async () => {
const uri = 'https://xxx';
try {
const response = await axios.get(uri);
setData(response.data...);
} catch (error) {
console.log(error);
}
};
// Get data by types
const getDataByTypes = async (filter) => {
const uri = `https://xxx/type/${filter}...`;
if (filter) {
try {
const response = await axios.get(uri);
setData(response.data...);
} catch (error) {
console.log(error);
}
}
};
return (
<div className="main">
<PokemonFilter filter={filter} setFilter={setFilter} />
<div className="container">
<div className="cards-container">
{data.map((d) => (
<PokemonCard key={d.name} data={d} />
))}
</div>
</div>
</div>
);
}
PokemonCard.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function PokemonCard({ data }) {
const [pokemons, setPokemons] = useState();
useEffect(() => {
getPokemons(data);
}, [data]);
// Get Pokemons
const getPokemons = async (data) => {
const uri = `https://xxx/pokemon/${data.name}/`;
try {
const response = await axios.get(uri);
setPokemons(response.data...);
} catch (error) {
console.log(error);
}
};
return (
<div>
{pokemons && (
<div className="card">
<img src={pokemons.sprites.front_default} alt={pokemons.name} />
<p>{pokemons.name}</p>
<p>{pokemons.abilities[0].ability.name}</p>
<p>{pokemons.types[0].type.name}</p>
</div>
)}
</div>
);
}
PokemonFilter.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function PokemonFilter({ filter, setFilter }) {
const [types, setTypes] = useState([]);
useEffect(() => {
getType();
}, []);
// Get Type
const getType = async () => {
const uri = 'https://xxx/type/';
try {
const response = await axios.get(uri);
setTypes(response.data.results....);
} catch (error) {
console.log(error);
}
};
const handleFilter = (e) => {
setFilter(e.target.value);
};
return (
<select onChange={handleFilter} value={filter}>
<option>Filter by type</option>
{types.map((type) => {
return (
<option key={type.name} value={type.name}>
{type.name}
</option>
);
})}
</select>
);
}

Why will my fetch API call map one nested objects, but not the other?

I'm parsing data from the NASA API using React, and for some reason I can map one nested object within the return but not the other.
Here is my parent component:
import React, { useState } from 'react'
import './NasaAPI.scss'
import NasaImages from './NasaImages'
const NasaAPI = () => {
const [nasaData, setNasaData] = useState([])
const [nasaImage, setNasaImage] = useState("")
const [searchInput, setSearchInput] = useState("")
const [loading, setLoading] = useState(true)
const fetchData = async (e) => {
const data = await fetch(`https://images-api.nasa.gov/search?q=${searchInput}`)
.then(response => response.json())
.then(data => setNasaData(data.collection.items))
.catch(err => console.log(err))
.finally(setLoading(false))
}
const handleSubmit = (e) => {
e.preventDefault()
fetchData()
}
const handleChange = (e) => {
setSearchInput(e.target.value)
}
return (
<div>
<h2>Search NASA Images</h2>
<form onSubmit={handleSubmit}>
<input name="searchValue" type="text" value={searchInput} onChange={handleChange}></input>
<button value="Submit">Submit</button>
</form>
<section>
<NasaImages nasaData={nasaData} loading={loading}/>
</section>
</div>
)
}
export default NasaAPI
Here's where the issue is, in the child component:
import React from 'react'
const NasaImages = ({ nasaData }) => {
console.log(nasaData)
return (
<div>
<h2>This is a where the data go. 👇</h2>
{
nasaData && nasaData.map((data, idx) => {
return (
<div key={idx}>
<p>{data.href}</p>
<div>
{/* {data.links.map((data) => {
return <p>{data.href}</p>
})} */}
{data.data.map((data) => {
return <p>{data.description}</p>
})}
</div>
</div>
)
})
}
</div>
)
}
export default NasaImages
The current configuration works, and will display a data.description (data.data.map) mapping property. However, I want the commented code immediately above it to work which displays a data.href (data.links.map) property.
The JSON looks as follows:
So, the issue is that I can map one set of properties, data.data.map, but cannot access the other in the same object, data.links.map, without getting the error "TypeError: Cannot read property 'map' of undefined". Thank you in advance!
There exists a data element sans a links property, in other words there is some undefined data.links property and you can't map that. Use Optional Chaining operator on data.links when mapping, i.e. data.links?.map. Use this on any potentially undefined nested properties.
const NasaImages = ({ nasaData = [] }) => {
return (
<div>
<h2>This is a where the data go. 👇</h2>
{nasaData.map((data, idx) => (
<div key={idx}>
<p>{data.href}</p>
<div>
{data.links?.map((data, i) => <p key={i}>{data.href}</p>)}
{data.data?.map((data, i) => <p key={i}>{data.description}</p>)}
</div>
</div>
))}
</div>
)
}

Where to place a variable in a React component?

Error: "Type error Object(...) is not a function"
I'm trying to debug this line of code that I want to iterate over, but it does not work:
{
shuffleAndPick(images, 3).map((image) => (
<div className="key" key={image.id}>
<h3>{image.name}</h3>
<h3>{image.sanskritname}</h3>
<p>{image.description}</p>
<img src={image.image} alt={image.name} />
</div>
))
}
I want to do the debugging by setting the shuffleAndPick() function with a value into a variable. And then console.log() the variable to see what I get. In other words, I want to do something like this:
const shuffledArray = shuffleAndPick(images, 3)
console.log(shuffledArray)
shuffleAndPick
const shuffleAndPick = (array, amount) => {
return array.sort(() => 0.5 - Math.random()).slice(0, amount);
};
My problem is that I dont know where in my code I should put the variable and the console.log.And I only get errors. The code that I want to debug looks like this:
import React, { useState, useEffect, useParams } from 'react'
import { useHistory } from 'react-router-dom'
import { shuffleAndPick } from '../helpers/shuffleAndPick'
const URL = 'http://localhost:8080/chakra'
export const Energy = () => {
const [images, setImages] = useState([]);
const history = useHistory()
const fetchSolarPlexus = () => {
fetch('http://localhost:8080/chakra/5e6c096afe1b75409f5c6133/asana')
.then (res => res.json())
.then((json) => {
setImages(json)
})
}
useEffect(() => {
fetchSolarPlexus()
}, []);
return (
<section className="WorkOut">
<h2>Energy</h2>
{shuffleAndPick(images, 3).map((image) => (
<div className="key" key={image.id}>
<h3>{image.name}</h3>
<h3>{image.sanskritname}</h3>
<p>{image.description}</p>
<img src={image.image} alt={image.name} />
</div>
))}
<button onClick={() => history.goBack()} className="backLink">
Back
</button>
</section>
)
}
Where should I place the variable I want to console.log?
The answer to my question is to but the test variable before the return.
I also got alot of help debugging.
Se the comments for more info.

Categories

Resources