How to prevent my images from disappearing - reactjs

I am using React + Firebase store to display images that I have stored in my firebase storage. When I click the logo or "home" navigation button which routes to the page you are already at("/"), the images disappear. The images also disappear when I click a button used to sort the menu.
This is the file that retrieves the images:
let { docs } = useFirestore("images");
// This code is used to display the images/cards as 2 per row
var chunks = function (array, size) {
var results = [];
while (array.length) {
results.push(array.splice(0, size));
}
return results;
};
let data = chunks(docs, 2);
return (
<>
<div class="container">
<div class="row">
<div class="col-sm col-2 mt-5">
</div>
<div class="col-12">
<div className="cards">
<h1 className="cards__header">Menu</h1>
<div className="cards__container">
<div className="cards__wrapper">
{data.map((childs, index) => {
return (
<ul className="cards__items">
{childs.map((c, cindex) => {
return (
<>
<CardItem
src={c.url}
key={c.id}
text={c.imageText}
amountLeft={c.amountLeft}
label={c.imageLabel}
price={c.price}
id={c.id}
desc={c.desc}
cat={c.cat}
></CardItem>
))}
</>
);
})}
</ul>
);
})}
</div>
</div>
</div>
</div>
</div>
</div>
</>
);
};
This is the useFireStore.js file:ยด
import { useState, useEffect } from "react";
import { projectFirestore } from "../Firebase";
const useFirestore = (collection) => {
const [docs, setDocs] = useState([]);
useEffect(() => {
const unsub = projectFirestore
.collection(collection)
.orderBy("createdAt", "desc")
.onSnapshot((snap) => {
let documents = [];
snap.forEach((doc) => {
documents.push({ ...doc.data(), id: doc.id });
});
setDocs(documents);
});
return () => unsub();
}, [collection]);
return { docs };
};
export default useFirestore;
You can see the app at https://save-a-meal.web.app, it is in Danish though.

The bug is in the chunks part. On a rerender it is called again but it manipulates the docs value. To make your code work just call it like this:
const data = chunks([...docs], 2);
insted of:
const data = chunks(docs, 2);
We create a copy of the data so the function doesn't manipulate it.

Related

Data is not displaying in react components

I don't understand why my fetched data is not displaying.
Data is fetched properly when i check by console.log() however nothing shows up in my JSX section. Code looks alight too. Anyone have idea what is wrong here?
import React from "react";
import { useEffect, useState } from "react";
import axios from "axios";
export const ConcertLatest = () => {
const [concerts, setConcerts] = useState([]);
useEffect(() => {
const loadConcerts = async () => {
const response = await axios.get("/data/concerts");
const rawData = response.data;
const filteredData = rawData.filter((concert) => {
//let date = new Date(concert.datum);
// let newestDate = new Date("2022-09-29");
return concert.datum >= "2022-09-30";
});
setConcerts(filteredData);
};
loadConcerts();
}, []);
if (!concerts.length) {
return <p>Loading...</p>;
}
console.log(concerts); // getting full populated objects
return (
<div>
<h1>Newest concerts </h1>
<div>
<div className="card">
<img src={concerts.image} style={{ width: 100 }} />
<div className="card-text">
<div>
{concerts.map((concert) => {
(<h1>{concert.name}</h1>), (<h2>{concert.datum}</h2>);
})}
</div>
</div>
</div>
</div>
</div>
);
};
Change syntax of map function, either use return keyword with curly braces or just use round braces without return keyword. Eg
<div>
{concerts.map((concert) => {
return (
<React.Fragment>
(<h1>{concert.name}</h1>), (<h2>{concert.datum}</h2>)
</React.Fragment>
)
})}
</div>
Or
<div>
{concerts.map((concert) => (
<React.Fragment>
(<h1>{concert.name}</h1>), (<h2>{concert.datum}</h2>)
</React.Fragment>
))}
</div>

Why is my function not working- pop up image gallery

I'm trying to make a standard effect, where clicking on an image in a gallery will enlarge the image, put it at the center of the screen (in front of everything else), and darken the background. I haven't set up the slideshow part yet (so it won't change images), but the aim is to create an index so that I can do that in the future. I'm following a tutorial and trying to adapt it to my backend, but I'm missing a beat. It's not registering which image has been clicked (and I'm getting two errors in the console- 404, and 500). I'm using Nextjs as my frontend, Sanity for my backend.
import React, { useState } from 'react';
import { client, urlFor } from '../lib/client';
import { Header, Footer, Modal } from '../components';
const sets = ({setData, imagesData}) => {
const [clickedImage, setClickedImage] = useState(null);
const [currentIndex, setCurrentIndex] = useState(null);
const handleClick = (imagesData, index) => {
setCurrentIndex(index);
setClickedImage(imagesData.image);
};
const handleRotationRight = () => {
const totalLength = imagesData.imageItems.length;
if(currentIndex + 1 >= totalLength){
setCurrentIndex(0);
const newData = imagesData.imageItems[0];
setClickedImage(newData);
return;
}
const newIndex = currentIndex + 1;
const newData = imagesData.imageItems.filter((image) => {
return imagesData.imageItems.indexOf(image) === newIndex;
});
const newItem = newData[0].image;
setClickedImage(newItem);
setCurrentIndex(newIndex);
};
return (
<div>
<Header />
<main className="slug-gallery">
<div className="title">
<div className="title-line-left"></div>
<h2>{setData.set_name}</h2>
<div className="title-line-right"></div>
</div>
<div className="images-container">
<ul className="overall-images">
{imagesData.imageItems && imagesData.imageItems.map((imagesData, index) => (
<li key={index}>
<img
src={urlFor(imagesData.image).auto('format').url()}
className="the_image"
alt='test a'
onClick={() => handleClick(imagesData, index)}
/>
</li>
))}
</ul>
</div>
{clickedImage && (
<Modal
clickedImage={clickedImage}
handleRotationRight={handleRotationRight}
setClickedImage={setClickedImage}
/>
)}
</main>
<Footer />
</div>
)
}
export default sets
export const getServerSideProps = async (pageContext) => {
const setSlug = pageContext.query.slug;
const setQuery = `*[_type == 'set' && slug.current == $setSlug][0]`;
const imagesQuery = `*[_type == 'set' && slug.current == $setSlug][0]{'imageItems':set_images[]{image{
asset->{_id, url}, alt, name, date, size, materials}}}`;
const setData = await client.fetch(setQuery, {setSlug});
const imagesData = await client.fetch(imagesQuery, {setSlug});
return {
props: {setData, imagesData}
}
}
Heres the Modal component:
import React from 'react'
const Modal = ({clickedImage, handleRotationRight, setClickedImage}) => {
const handleClick = (e) => {
if(e.target.classList.contains("dismiss")){
setClickedImage(null);
}
}
return(
<>
<div className="overlay dismiss" onClick={handleClick}>
<img src={clickedImage} alt='test b'/>
<span className="dismiss" onClick={handleClick}>x</span>
</div>
<div onClick={handleRotationRight} className="overlay-arrows_left">
<img src="/next_portfolio/public/images/cart.png" alt='test c'/>
</div>
</>
)
};
export default Modal;

Creating a history page with React Hooks

I am trying to create a history page with react hooks that keeps track of the users most recent searches they don't have to be persistent through refreshes only from this session.
my search component looks like this this is a simple app that does not need a UI just a simple navigation on the search page it will show the results and on the history page I would like to be able to show the previous searches from this session
I am trying to keep track of the debouncedTerm so I can display it in a new component
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Search = () => {
const history = [];
const [term, setTerm] = useState('');
const [debouncedTerm, setDebouncedTerm] = useState(term);
const [results, setResults] = useState([]);
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedTerm(term);
}, 1000);
return () => {
clearTimeout(timerId);
};
}, [term]);
useEffect(() => {
const search = async () => {
const { data } = await axios.get('http://hn.algolia.com/api/v1/search?', {
params: {
query: debouncedTerm,
},
});
setResults(data.hits);
};
if (debouncedTerm) {
search();
}
}, [debouncedTerm]);
const renderedResults = results.map((result) => {
return (
<div key={result.objectID} className="item">
<div className="right floated content">
<a className="ui button" href={result.url}>
Go
</a>
</div>
<div className="content">
<div className="header">{result.title}</div>
</div>
</div>
);
});
return (
<div>
<div className="ui form">
<div className="field">
<label>Hacker News Search:</label>
<input
value={term}
onChange={(e) => setTerm(e.target.value)}
className="input"
/>
</div>
</div>
<div className="ui celled list">{renderedResults}</div>
</div>
);
};
export default Search;
Your code looks like it's going in the right direction but you have a constant const history = []; and you must keep in mind that this will not work, because you will have a new constant re-declared in every render. You must use React setState like so:
const [history, setHistory] = useState([]);
You can read more about it in the React documentation.
edit:
In order to add new elements to the existing history you have to append it like this:
setHistory(oldHistory => [...oldHistory, newHistoryElement]);

Run useEffect only one when a special property changed

I want to update render when a special property changes. This property income from parents. I Made a useState called loader to handle codes when I have data or not. if the loader is false, my code calls API and if it is true render data.
First of all I use useEffect this way. It didn't update render
useEffect(() => {
callApi();
}, []);
After that I used useEffect this way. props.coordinates is a property that my code should update after it changes.
useEffect(() => {
callApi();
setLoader(false);
}, [props.coordinates]);
But my codes are in loops, and my API key was blocked.
Could you let me know what my mistake is ?
This my component:
import React, { useEffect, useState } from "react";
import axios from "axios";
import ForcastHour from "./ForcastHour";
import "./WeatherHourlyForcast.css";
const WeatherHourlyForcast = (props) => {
const [loader, setLoader] = useState(false);
const [hourlyForcastData, setHourlylyForcastData] = useState(null);
useEffect(() => {
callApi();
setLoader(false);
}, [props.coordinates]);
const showHourlyForcast = (response) => {
console.log("showHourlyForcast", response.data.hourly);
setHourlylyForcastData(response.data.hourly);
setLoader(true);
};
function callApi() {
let latitude = props.coordinates.lat;
let longitude = props.coordinates.lon;
const apiKey = "23422500afd990f6bd64b60f46cf509a";
let units = "metric";
let apiUrl = `https://api.openweathermap.org/data/2.5/onecall?lat=${latitude}&lon=${longitude}&appid=${apiKey}&units=${units}`;
axios.get(apiUrl).then(showHourlyForcast);
console.log("hourly", apiUrl);
}
if (loader) {
return (
<div className="row">
<div className="col-md-6">
<div className="row">
{hourlyForcastData.map(function (hourlyforcast, index) {
if (index < 4 && index > 0) {
return (
<div
className="col-4 box-weather my-auto text-center"
key={index}
>
<ForcastHour data={hourlyforcast} />
</div>
);
}
})}
</div>
</div>
<div className="col-md-6">
<div className="row">
{hourlyForcastData.map(function (hourlyforcast, index) {
if (index < 7 && index > 3) {
return (
<div
className="col-4 box-weather my-auto text-center"
key={index}
>
<ForcastHour data={hourlyforcast} />
</div>
);
}
})}
</div>
</div>
</div>
);
} else {
callApi();
return null;
}
};
export default WeatherHourlyForcast;
While adding dependencies array to the end of useEffect (or any other hook...), each render if the value is not equal to the prev one, the hook will run again.
Because props.coordinates is an object, and in JS objA != objA == true, even if the properties didn't change, React can't know that.
My suggestion is to use the values themselves (assuming they're strings either numbers and so on)
useEffect(() => {
(async () => {
await callApi();
setLoader(false);
})()
}, [props.coordinates.lat, props.coordinates.lon]);
Another thing that you might encounter is setLoader(false) will be called before callApi will be finished, therefore added async behaviour to the hook
You can write your component likes this and call the APIs when the component mount. The API calls happens when the lat, lon values are changed.
import React, { useEffect, useState } from "react";
import axios from "axios";
import ForcastHour from "./ForcastHour";
import "./WeatherHourlyForcast.css";
const WeatherHourlyForcast = (props) => {
const { coordinates : { lat, lon } } = props;
const [loader, setLoader] = useState(false);
const [hourlyForcastData, setHourlylyForcastData] = useState(null);
useEffect(() => {
callApi();
}, [lat, lon]); //It's call the API's when the lat, lon values are changed
const callApi = () => {
setLoader(true);
const latitude = lat;
const longitude = lon;
const apiKey = "23422500afd990f6bd64b60f46cf509a";
const units = "metric";
const apiUrl = `https://api.openweathermap.org/data/2.5/onecall?lat=${latitude}&lon=${longitude}&appid=${apiKey}&units=${units}`;
axios.get(apiUrl).then((response) => {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
console.log("showHourlyForcast", response.data.hourly);
setHourlylyForcastData(response.data.hourly);
setLoader(false);
});
};
if (loader) {
return (
<div>
<h1>Loading...</h1>
</div>
);
}
return (
<div className="row">
<div className="col-md-6">
<div className="row">
{hourlyForcastData.map(function (hourlyforcast, index) {
if (index < 4 && index > 0) {
return (
<div
className="col-4 box-weather my-auto text-center"
key={index}
>
<ForcastHour data={hourlyforcast} />
</div>
);
}
})}
</div>
</div>
<div className="col-md-6">
<div className="row">
{hourlyForcastData.map(function (hourlyforcast, index) {
if (index < 7 && index > 3) {
return (
<div
className="col-4 box-weather my-auto text-center"
key={index}
>
<ForcastHour data={hourlyforcast} />
</div>
);
}
})}
</div>
</div>
</div>
);
};
export default WeatherHourlyForcast;

Values won't appear in the state(react)

SOS! Here i have favourite meals from local storage assigned to favs state. I then use my favs to fetch a meal for each favs value. My favs state updates on initial render, but fetchedMeals is not. It updates only after SECOND render. It seems that i have problem somewhere with async function. Would appreciate any help. Will provide with more details immediately if needed.
import React from 'react';
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import NavBar from '../Home/NavBar';
function Favourites() {
const [favs, setFavs] = useState([]);
const [fetchedMeals, setFetchedMeals] = useState([]);
const getFavsFromLs = () => {
let itemsFromLs = JSON.parse(localStorage.getItem('favs')) || [];
setFavs(itemsFromLs);
};
const fetchMeal = async (mealName) => {
const linkByMealName = `https://www.themealdb.com/api/json/v1/1/search.php?s=${mealName}`;
const res = await fetch(linkByMealName);
const meal = await res.json();
return meal.meals[0];
};
const getFavMeals = async () => {
let items = [];
for (let i = 0; i < favs.length; i++) {
fetchMeal(favs[i]).then((res) => items.push(res));
}
setFetchedMeals(items);
};
useEffect(() => {
getFavsFromLs(); //Moved getFavMeals to another useEffect
}, []);
useEffect(() => {// New use effect that listens favs
if (favs && favs.length) {
getFavMeals();
}
}, [favs]);
return (
<>
<NavBar />
<section className="section">
<div className="row">
<div className="container col l12">
{fetchedMeals.map((meal) => {
const { strMeal, strMealThumb, idMeal } = meal;
return (
<Link to={`/favourites/${strMeal}`}>
<div class="col s12 m6 l3" key={idMeal}>
<div class="card">
<div class="card-image">
<img src={strMealThumb} alt="img"></img>
</div>
<div class="card-content">
<h5 className="truncate center">{strMeal}</h5>
</div>
</div>
</div>
</Link>
);
})}
</div>
</div>
</section>
</>
);
}
export default Favourites;
Since setting state is an asynchronous you need to make useEffect for favs and when they are set from localStorage you can make that getFavMeals and at that point favs will have data.
Also doing console.log immediately after setting state will also show old value, because it needs to rerender. You are trying also on console.log(fetchedMeals).
import React from 'react';
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import NavBar from '../Home/NavBar';
function Favourites() {
const [favs, setFavs] = useState([]);
const [fetchedMeals, setFetchedMeals] = useState([]);
const getFavsFromLs = () => {
let itemsFromLs = JSON.parse(localStorage.getItem('favs')) || [];
setFavs(itemsFromLs);
};
const fetchMeal = async (mealName) => {
const linkByMealName = `https://www.themealdb.com/api/json/v1/1/search.php?s=${mealName}`;
const res = await fetch(linkByMealName);
const meal = await res.json();
console.log(meal.meals[0]);
return meal.meals[0];
};
const getFavMeals = async () => {
let items = [];
for (let i = 0; i < favs.length; i++) {
fetchMeal(favs[i]).then((res) => items.push(res));
}
setFetchedMeals(items);
console.log(fetchedMeals);
console.log(items);
};
useEffect(() => {
getFavsFromLs(); //Moved getFavMeals to another useEffect
}, []);
useEffect(() => {// New use effect that listens favs
if (favs && favs.length) {
getFavMeals();
}
}, [favs]);
return (
<>
<NavBar />
<section className="section">
<div className="row">
<div className="container col l12">
{fetchedMeals &&
fetchedMeals.map((meal) => {
console.log(meal);
const { strMeal, strMealThumb, idMeal } = meal;
console.log(meal);
return (
<Link to={`/favourites/${strMeal}`}>
<div class="col s12 m6 l3" key={idMeal}>
<div class="card">
<div class="card-image">
<img src={strMealThumb} alt="img"></img>
</div>
<div class="card-content">
<h5 className="truncate center">{strMeal}</h5>
</div>
</div>
</div>
</Link>
);
})}
</div>
</div>
</section>
</>
);
}
export default Favourites;

Resources