Trying to iterate an array of URL's in React? - reactjs

I am trying to create an image gallery using Heroku's new add:on 'simple-file-upload'. I've managed to get everything saved to the database and I can display 1 image at a time, but am having trouble creating the gallery now. I've set everything up how I think it should be, but when I console.log(files) I am not receiving the URL, but rather just the number 1 from the count. Any ideas what I'm doing wrong here? Here is my code below:
import React from "react";
import SimpleFileUpload, { SimpleFileUploadProvider } from "../components/SimpleFileUpload"
import { useState } from 'react'
import "./styles.css"
const API_KEY = ''
let count = 0;
export default function About() {
const [files, setFiles] = useState([]);
console.log(files)
//var Gallery = [];
//Gallery.push(files);
//console.log(files)
return (
<div className="App">
<h1>upload an image</h1>
<SimpleFileUpload apiKey={API_KEY} onSuccess={() => setFiles([...files, `${++count}`])} />
{!!files &&
files.map(a => {
return (
<div
key={a}
style={{
display: "flex",
justifyContent: "space-between",
marginTop: 5
}}
>
<div>{a}</div>
{/*
button to remove entries from the array, this should also make an API call to remove them from your server (unless the files are required for something like an audit).
*/}
<button onClick={() => setFiles(files.filter(f => f !== a))}>
Remove
</button>
</div>
);
})}
</div>
);
}

It looks like your onSuccess handler isn't accepting files. the ...files in () => setFiles([...files, ${++count}]) is the files from const [files, setFiles] = useState([]);, ie, [], leaving you with ${++count}, ie, 1.
changing it to files => setFiles([...files ${++count}]) should fix it---though i don't know what onSuccess does, so i can't say for sure.

Related

Slow input even with useTransition()

I'm playing with the new useTransition() hook added in React 18.
I created an app highlighting the matching names. If it matches, the item gets green, otherwise red. Here how I did it:
import { useState, useTransition } from 'react';
import people from './people.json'; // 20k items
const PeopleList = ({ people, highlight, isLoading }) => {
return (
<>
<div>{isLoading ? 'Loading...' : 'Ready'}</div>
<ol>
{people.map((name, index) => (
<li
key={index}
style={{
color: (
name.toLowerCase().includes(highlight)
? 'lime'
: 'red'
)
}}
>
{name}
</li>
))}
</ol>
</>
);
};
const App = () => {
const [needle, setNeedle] = useState('');
const [highlight, setHighlight] = useState('');
const [isPending, startTransition] = useTransition();
return (
<div>
<input
type="text"
value={needle}
onChange={event => {
setNeedle(event.target.value);
startTransition(
() => setHighlight(event.target.value.toLowerCase())
);
}}
/>
<PeopleList
people={people}
highlight={highlight}
isLoading={isPending}
/>
</div>
);
};
export default App;
The JSON file contains an array of 20k people. Unfortunately, the useTransition() doesn't seem to improve performance in this case. Whether I use it or not, typing in the input is very laggy, making about a 0.5s delay between each character.
My first thought was that maybe such a big DOM tree would cause a delay in the input, but it doesn't seem the case in a raw HTML page.
Why doesn't useTransition() make typing smooth in my example?
The reason is because your app is running in React 17 mode.
Source: https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#deprecations
react-dom: ReactDOM.render has been deprecated. Using it will warn and run your app in React 17 mode.
How about wrapping PeopleList component in a memo.
As far as my understanding goes. Everytime when setNeedle is being called <App/> component is rerendering and then <PeopleList/> is also rerendering even though nothing changed in <PeopleList/> component.

React - Unhandled Rejection (TypeError): Cannot read property 'front_default' of undefined

I'm new to React and I learn best by building my own projects with things that's "fun".
I'm building a Pokedex and everything has been pretty neat, until today when building out a new function.
It's supposed to search every time the user passes in another letter with the "searchPokemon" function.
When this is assigned to the button it works like a charm, but when I try to add it to the "onChange" handler within the input it generates this:
How does that come?
If I assign an invalid pokemon name (string) and then search when the searchPokemon function is assigned to the button it doesn't generate an error message, but now it does?
I assume I need some sort of if statement, but I'm not sure how to go about it.
import Axios from "axios";
import React, { useState } from "react";
import "./SearchPokemon.css";
function PK() {
const api = Axios.create({
baseURL: "https://pokeapi.co/api/v2/",
});
const [pokemonSearched, setPokemonSearched] = useState(false);
const [search, setSearch] = useState("");
const [pokemon, setPokemon] = useState({});
const [pokemonDescription, fetchDescription] = useState({});
const [evolution, pokemonEvolution] = useState({});
const searchPokemon = () => {
api.get(`pokemon/${search}`.toLowerCase()).then((response) => {
setPokemon({
name: response.data.name,
height: response.data.height,
weight: response.data.weight,
img: response.data.sprites.front_default,
id: response.data.id,
type: response.data.types[0].type.name,
type2: response.data.types[1]?.type.name,
});
api.get(`pokemon-species/${response.data.id}/`).then((response) => {
fetchDescription({
entry: response.data.flavor_text_entries[0].flavor_text,
evolution: response.data.evolution_chain.url,
});
api.get(`${response.data.evolution_chain.url}`).then((response) => {
pokemonEvolution({
evolution: response.data.chain.evolves_to[0]?.species.name,
});
});
});
});
setPokemonSearched(true);
};
return (
<div className="page">
<div className="search-section">
<input
placeholder="Search..."
type="text"
onChange={(event) => {
setSearch(event.target.value);
searchPokemon();
}}
/>
<button onClick={searchPokemon}>Click me</button>
</div>
<div className="main">
{!pokemonSearched ? null : (
<>
<h1 style={{ textTransform: "capitalize" }}>{pokemon.name}</h1>
<h1>No. {pokemon.id}</h1>
<img src={pokemon.img} alt="" />
<div className="info-wrapper">
<div className="info">
<h3 style={{ textTransform: "capitalize" }}>
Type: {pokemon.type} {pokemon.type2}
</h3>
<h3>Height: {pokemon.height * 10} Cm</h3>
<h3>Weight: {pokemon.weight / 10} Kg</h3>
</div>
</div>
<div className="desc">
<div className="desc-info">
<h3 style={{ textTransform: "capitalize" }}>
{pokemonDescription.entry}
</h3>
</div>
</div>
</>
)}
</div>
</div>
);
}
export default PK;
The error tells exactly what the problem is!
You cannot read property type of undefined.
So what does that mean? You have on line 23 the following:
type2: response.data.types[1]?.type.name,
As you are using Typescript, so you have to understand what exactly the ? means on that line, which is just you making an assumption that the properties followed by the ? will be present everytime! Even though the typing says otherwise.
But, as you can see in the docs of the pokemon endpoint, types is a list, and some don't have more than 1 type in the array, so you have to figure out a way of checking weather that typing is present to set the state.
Edit: As you are triggering the searchPokemon function on every keyboard stroke (try doing the same here), you are using the hook setPokemon on every response, even when there's no response.data, so here's what causing you trouble, you just need to find a way to validate the response before updating the state.

Trying to implement local storage in React. It is not tracking the input value that I send

I am trying to implement a local storage in REACT in order to save in a database the data(numbers) that I type in the input. The code works when I check it in the console.log but it does not work as I wish. Everytime I add a value it deletes the previous one. So I can just read the last input that I put. It does not shows me all the inputs that I put before. I would like to mention that this component is rendered dinamycally so in the parent component I get four different buttons where I can type the number that I want to. Thanks in advance
import React, { useState, useEffect } from 'react';
function Stake() {
const [stakes, setStakes] = useState([]);
const addStake = (e) => {
e.preventDefault();
const newStake = {
input: e.target.stake.value,
};
setStakes([...stakes, newStake]);
};
useEffect(() => {
const json = JSON.stringify(stakes);
localStorage.setItem("stakes", json);
}, [stakes]);
console.log(stakes)
return (
<div>
<form onSubmit={addStake}>
<input style={{ marginLeft: "40px", width: "50px" }} type="text" name="stake" required />
{/* <button style={{ marginLeft: "40px" }} type="submit">OK</button> */}
</form>
</div>
);
}
export default Stake;
Right now your component always starts with an empty array: useState([]).
When the component renders for the first time, you need to retrieve existing values from locaStorage and set it as the local state of component:
useEffect(() => {
const stakes= JSON.parse(localStorage.getItem("stakes")) || [];
setStakes(stakes);
}, []);

useEffect not running on refresh

I'm having an issue with the useEffect hook on this blog site I'm building. Im trying to fetch all the blogs from the backend so I can use them to populate this section with the latest five blogs. When I use the code below, the empty array in the useEffect prevents the infinite amount of fetch calls, which is great.
But then I run into a problem where if I refresh the page or navigate back to it I get an error on line 35 saying "cannot find mainImage of undefined".
My question is how do I have the fetchCall populate the state and do so even on a refresh so that I can still access the info I need. Thanks!
import React, {useState, useEffect} from 'react';
import CardItem from './CardItem';
import './Cards.css';
function Cards() {
const [blogs, setBlogs] = useState();
useEffect(() => {
fetchBlogs();
// eslint-disable-next-line react-hooks/exhaustive-deps
},[]);
const fetchBlogs = async () => {
console.log('ok');
const response = await fetch('http://localhost:3000/blogs');
const data = await response.json();
setBlogs(data);
};
return (
<div className='cards'>
<div className='header-container'>
<img
className='logo'
alt='logo'
src='https://Zi.imgur.com/MaLqLee.png'
/>
<h1 className='cards-title'>Hot takes and hometown bias</h1>
</div>
<div className='cards-container'>
<div className='cards-wrapper'>
<ul className='cards-items'>
<CardItem
src={blogs.length > 0 ? blogs[0].mainImage : ''}
text="Don't look now, but Zach Lavine officially kicks ass."
label='Bulls'
path='/bulls'
/>
<CardItem
src='https://i.imgur.com/EmVgHk2.jpg'
text='The curious case of Mitch Trubisky apologists.'
label='Bears'
path='/bears'
/>
</ul>
<ul className='cards-items'>
<CardItem
src='https://i.imgur.com/ZZ5dLJU.jpg'
text='How Did We Get Here? The Suddenly Bleak State of the Cubs.'
label='Cubs'
path='/cubs'
/>
<CardItem
src='https://i.imgur.com/MwgKmUM.jpg'
text='Pace and Nagy: So, how much can we blame these guys?'
label='Bears'
path='/bears'
/>
<CardItem
src='https://i.imgur.com/Y2Eorvu.jpg'
text='Thad Young: An Ode to the NBA Journeyman.'
label='Bulls'
path='/bulls'
/>
</ul>
</div>
</div>
</div>
);
}
export default Cards;
The fetch call is asynchronous. This means it is not guaranteed to be complete before the program enters the next line.
Because of this the blogs array will be empty at the first render. You can add an check in the src CardItem component to only use the value returned from the fetch call when it is available:
<CardItem
src={blogs.length > 0 ? blogs[0].mainImage : ''}
...
/>
An alternative would be to use the fact that blogs is an array and use the map operator to build one or more CardItems.
<ul className='cards-items'>
{blogs.map(blog => <CardItem
src={blog.mainImage}
...
/>)}
</ul>
I faced the same problem, and here is how I solved it..
First, I created a loading state, and set the initial state to true.
// const [singlePackage, setPackage] = useState([]);
const [isLoading, setLoading] = useState(true);
then, in the useEffect hook, I set the state to false like so..
useEffect(() => {
axios.get(baseURL).then((response) => {
setPackage(response.data);
setLoading(false);
})
}, []);
Then, I used a condition, if the loading state is true, return the spinner else return the component like so...
if (isLoading) {
return (
<div className="loadingContainer">
<Loader
type="ThreeDots"
color="#00b22d"
height={100}
width={100}
//3 secs
/>
</div>
)
} else {
return (
// your code here
)}
I am using react-loader-spinner, and just styled the container
you can install it using...
npm install react-loader-spinner --save
the style for container ...
.loadingContainer{
position: fixed;
top: 50%;
left:50%;
transform: translate(-50%,50%);
}

react.js, how to create a search filter from API data?

I am trying to create my first search bar in React.js. I am trying to implement search functionality with filter method. I faced a problem with filter method, which gives an error like "filter is not defined". I am stuck on it for 2 days, I have looked several tutorials and endless youtube videos. This is the simpliest approach, I guess. Any help will be appreciated.
import React, { useState, useEffect } from "react";
import Recipe from "./Recipe";
import "./styles.css";
export default function RecipeList() {
const apiURL = "https://www.themealdb.com/api/json/v1/1/search.php?f=c";
const [myRecipes, setRecipes] = useState("");
const [search, setSearch] = useState("");
// fetch recipe from API
function fetchRecipes() {
fetch(apiURL)
.then(response => response.json())
.then(data => setRecipes(data.meals))
.catch(console.log("Error"));
}
function onDeleteHandler(index) {
setRecipes(
myRecipes.filter((element, filterIndex) => index !== filterIndex)
);
}
useEffect(() => {
fetchRecipes();
}, []);
const filterRecipes = myRecipe.meal.filter( element => {
return element.name.toLowerCase().includes(search.toLocaleLowerCase())
})
{/* filter method above doesn't work */}
return (
<div>
<label>
<div className="input-group mb-3 cb-search">
<input
type="text"
className="form-control"
placeholder="Search for recipes..."
aria-label="Recipient's username"
aria-describedby="button-addon2"
onChange = {e => setSearch (e.target.value)}
/>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
id="button-addon2"
>
Search
</button>
</div>
</div>
</label>
<div>
<button
className="btn btn-info cb-button fetch-button"
onClick={fetchRecipes}
>
Fetch Recipe
</button>
<br />
{filterRecipes.map((element, index) => (
<Recipe
key={index}
index = {index}
onDelete={onDeleteHandler}
{...element}
name = {element.strMeal}
/>
))}
{/** name of child component */}
{/** strMeal is the name of Recipe in API object */}
</div>
</div>
);
}
link for code codesandbox
I made some changes on your code updated code
const [myRecipes, setRecipes] = useState([]);
You should declare myRecipes as an array if u intended to use map function.
const filterRecipes = myRecipe.meal.filter( element => {
return element.name.toLowerCase().includes(search.toLocaleLowerCase())
})
You have the wrong variable passing through, it should be myRecipes
filterRecipes.map((element, index) => (
<Recipe
key={index}
index = {index}
onDelete={onDeleteHandler}
{...element}
name = {element.strMeal}
/>
3. You should check whether your filterRecipes is not undefined before you use map function.
Lastly, your fetch API return error which unable to setRecipes.
I could not resolve you task completely because of low count of information according the task, but, i think, my answer will be useful for you.
So, tthe first thing I would like to draw attention to is a initial state in the parameter of useState function. In this task it sould be as:
const [myRecipes, setRecipes] = useState({meals: []});
Because, before fetching data, React has a time to run the code, and, when it come to line 32, it see what in the myRecipes (myRecipes, not a myRecipe. Please, pay attention when you write the code) a string except an array.
And in the line 32 i recommend you to add something checking of have you resolved request of data like:
const filterRecipes = myRecipes.meals.length
? myRecipes.meals.filter(element => {
return element.name.toLowerCase().includes(search.toLocaleLowerCase());
});
: []
And look in the data which you receive, because, i think, there are no elements with propName like name (element.name).
I think, i could help you as possible. If you have any questions, ask in comments. Will answer you as soon as possible. Good luck

Resources