React , Next js , state keeps it's default value after rerender - reactjs

I have a filters states which are responsable for showing and hiding the specified filter. When i choose a value from the filter the page has to fetch data from api , populate the component with new data (component rerenders) , and the menu stay visible and the filter expanded. This code worked before migrating to next js , now when i rerender the component the state takes it's default value of (false) and the filter in not expanded. Here is some code
This is the main component. What im interestet in is FiltersMenu component.
import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router';
import FiltersMenu from '../../../../../components/Filters/FiltersMenu';
export async function getServerSideProps(context) {
const productsType = context.params.products;
const res = await fetch(some api)
const data = await res.json();
return {
props: {
products:data.data,
price:{
minPrice:data.minPrice,
maxPrice:data.maxPrice
},
avaibleSizes:data.avaibleSizes
},
}
}
export default Products
function Products(props) {
const [isLoaded,setIsLoaded] = useState(false);
const [showFilters,setShowFilters] = useState(false);//show filters menu or not
const [products,setProducts] = useState();//data of the products fetcher from backend
const [filterValues,setFilterValues]=useState({price:0,color:[],size:[]});//values to send to the server to filter
const toogleShowFilters = (bool) =>{
setShowFilters(bool);
}
const handleSetFilterValues = (e) =>{
//changes values of filterValues
//and after that set isLoaded to false
}
const filterProducts = () =>{
fetch(some api)
.then(res=>res.json())
.then(data=>{
setProducts(data);
setIsLoaded(true);
})
}
useEffect(()=>{
filterProducts();
},[filterValues])
useEffect(()=>{
setProducts(props.products);
setIsLoaded(true);
},[])
if(isLoaded === false) return <p>Loading</p>
return (
<div>
<div className='products-heading'>
<FiltersMenu
showFilters={showFilters}
toogleShowFilters={toogleShowFilters}
price={props.price}
avaibleSizes={props.avaibleSizes}
filterValues={filterValues}
handleSetFilterValues={handleSetFilterValues}
/>
</div>
</div>
)
}
Here is how Filters menu component looks
import React,{useState , useRef, useEffect} from 'react'
import PriceFilter from './PriceFilter';
function FiltersMenu(props) {
const [showPriceFilter,setShowPriceFilter] = useState(false);
const filtersMenu = useRef();
const showFiltersMenu = () =>{
filtersMenu.current.className="filters animate__animated animate__slideInUp";
props.toogleShowFilters(true);
console.log(props.showFilters + " t ");
}
const hideFiltersMenu = () =>{
filtersMenu.current.className="filters animate__animated animate__slideOutDown";
setTimeout(()=>{filtersMenu.current.className='hidden'},1000);
props.toogleShowFilters(false);
console.log(props.showFilters + " t ");
}
useEffect(()=>{
if(props.showFilters===false){
hideFiltersMenu();
}
},[])
return (
<div>
<div onClick = {showFiltersMenu} className='filters-button'>
<img src='/images/filter.png'/>
Filters
</div>
{
<div ref={filtersMenu} className="filters">
<div className='heading'>
<h2>Filters</h2>
<img onClick = {hideFiltersMenu} src='/images/close.png' className='close-icon'/>
</div>
<div
onClick={()=>{setShowPriceFilter((prev)=>!prev)}}
className={"holder" +" " + (showPriceFilter===true && "highlight")}>
<div>Price</div>
<i className="fa-solid fa-caret-down"></i>
</div>
{
showPriceFilter && <PriceFilter price={props.price}
filterValues={props.filterValues}
handleSetFilterValues={props.handleSetFilterValues}
/>
}
</div>
}
</div>
)
}
export default FiltersMenu;
And PriceFilter component
import React, { useState } from 'react'
function PriceFilter(props) {
const [currcentPrice,setCurrentPrice] = useState(props.price.maxPrice);
const handleChange = (e) =>{
props.handleSetFilterValues(e);
}
const andjustPrice = (e) =>{
setCurrentPrice(e.target.value);
}
return (
<div className='price-filter animate__animated animate__flipInX'>
<p className='current-price'>{currcentPrice}$</p>
<div className='slider-holder'>
<p>{props.price.minPrice}</p>
<input onChange={andjustPrice} onMouseUp={handleChange} value={currcentPrice}
type="range" min={props.price.minPrice} max={props.price.maxPrice + 1} name='price' />
<p>{props.price.maxPrice + 1}</p>
</div>
</div>
)
}
export default PriceFilter
Every time i change the price the All components rerender , the main components keeps it's state values , but FiltersMenu takes the default values.

Found a solution , the problem is in
if(isLoaded === false) return <p>Loading</p>
When filterValues are changed isLoaded is set to false therefor the FiltersMenu component doesen't render , after that when isLoaded is true again , FiltersMenu is rendered again and thats the reason the state in FiltersMenu has default values , to fix it simply isLoaded can be used in return as that
return (
{
isLoaded === true && <MyComponent />
}
)

Related

useState set to string not working in Reactjs

I have this code that controls the behavior of what to map from an array onClick. The useState is set to a string const [activeFilter, setActiveFilter] = useState('All'); that is supposed to automatically filter all products containing the string as tag but it doesn't do this automatically and I can't figure out why. Please help with code below.
index.js
import React, { useEffect, useState } from 'react'
import {client} from '../lib/client'
import { Product, FooterBanner, HeroBanner } from '../components'
const Home = ({products, bannerData}) => {
const [productItems, setProductItems] = useState([])
const [filterWork, setFilterWork] = useState([]);
const [activeFilter, setActiveFilter] = useState('All');
useEffect(() => {
setProductItems(products)
}, [])
const handleProductFilter = (item) => {
setActiveFilter(item)
setTimeout(() => {
if (item == 'All'){
setFilterWork(productItems)
}else{
setFilterWork(productItems.filter((productItem)=> productItem.tags.includes(item)))
}
}, 500)
}
return (
<>
<HeroBanner heroBanner={bannerData.length && bannerData[0]} />
<div className='products-heading'>
<h2>Best Selling Products</h2>
<p>Smoke accessories of many variations</p>
</div>
<div className='product_filter'>
{['Lighter', 'Pipe', 'Roller', 'Hookah', 'All'].map((item, index) => (
<div
key={index}
className={`product_filter-item app__flex p-text ${activeFilter === item ? 'item-active' : ''}`}
onClick={() => handleProductFilter(item)}
>
{item}
</div>
))}
</div>
<div className='products-container'>
{
filterWork.map((product) => <Product key={product._id} product={product} />)
}
</div>
<FooterBanner footerBanner={bannerData && bannerData[0]} />
</>
)
};
export const getServerSideProps = async () => {
const query = '*[_type == "product"]'
const products = await client.fetch(query)
const bannerQuery = '*[_type == "banner"]'
const bannerData = await client.fetch(bannerQuery)
return {
props: {products, bannerData}
}
}
export default Home
The image below is what it looks like on load and the only time All products containing 'All' tags are visible is when the All button is clicked on again, regardless of it being active initially
No products are being displayed initially when the component renders because the displayed products are loaded from the filterWork state that is only set once an onClick event is triggered. To fix this you can simply set the initial products in the useEffect because you are starting with all the products being displayed.
useEffect(() => {
setProductItems(products);
setFilterWork(products);
}, [])

useEffect fails on page refresh

I am an infant programmer and I am trying to fetch an api and style the results using React. My page works fine on the initial load and subsequent saves on VScode,but when I actually refresh the page from the browser I get the error thats posted on imageenter image description here:
Here is my code: App.js
```import React, { useEffect, useState } from 'react';
import './App.css';
import Students from './components/Students';
import styled from 'styled-components';
function App() {
const [studentInfo, setStudentInfo] = useState({});
const [searchResult, setSearchResult] = useState({});
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
getStudents();
}, []);
useEffect(() => {
getStudents();
console.log('useEffect');
}, [searchTerm]);
const getStudents = async () => {
const url = 'https://api.hatchways.io/assessment/students';
console.log(url);
fetch(url)
.then((res) => res.json())
.then((data) => {
console.log(data);
searchTerm != ''
? setStudentInfo(filterStudents(data.students))
: setStudentInfo(data.students);
});
};
const filterStudents = (studentsArray) => {
return studentsArray.filter((info) => {
return (
info.firstName.toLowerCase().includes(searchTerm) ||
info.lastName.toLowerCase().includes(searchTerm)
);
});
};
console.log(searchTerm);
return (
<div className="App">
<Students
studentInfo={studentInfo}
setSearchTerm={setSearchTerm}
searchTerm={searchTerm}
/>
</div>
);
}
export default App;```
here is my component Students.js:
```import React, { useState } from 'react';
import styled from 'styled-components';
import GradeDetails from './GradeDetails';
const Students = ({ studentInfo, searchTerm, setSearchTerm }) => {
console.log(typeof studentInfo);
console.log(studentInfo[0]);
const [isCollapsed, setIsCollapsed] = useState(false);
const handleDetails = () => {
setIsCollapsed(!isCollapsed);
};
const average = (arr) => {
let sum = 0;
arr.map((num) => {
sum = sum + parseInt(num);
});
return sum / arr.length.toFixed(3);
};
console.log(isCollapsed);
return (
<Container>
<Input
type="text"
value={searchTerm}
placeholder="Search by name"
onChange={(e) => setSearchTerm(e.target.value.toLowerCase())}
/>
{studentInfo?.map((student) => (
<Wrapper key={student.id}>
<ImageContainer>
<Image src={student.pic}></Image>
</ImageContainer>
<ContentContainer>
<Name>
{student.firstName} {student.lastName}{' '}
</Name>
<Email>Email: {student.email}</Email>
<Company>Company: {student.company}</Company>
<Skills>Skill: {student.skill}</Skills>
<Average>Average:{average(student.grades)}%</Average>
</ContentContainer>
<ButtonContainer>
<Button onClick={handleDetails}>+</Button>
</ButtonContainer>
{isCollapsed && <GradeDetails studentInfo={studentInfo} />}
</Wrapper>
))}
</Container>
);
};```
Every time I have the error, I comment out the codes in Students.js starting from studentInfo.map until the and save and then uncomment it and save and everything works fine again.
I am hoping someone can help me make this work every time so that I don't have to sit at the edge of my seat all the time. Thank you and I apologize for the long question.
You are using an empty object as the initial state for studentInfo (the value passed to useState hook will be used as the default value - docs):
const [studentInfo, setStudentInfo] = useState({});
.map is only supported on Arrays. So this is failing when the component is rendering before the useEffect has completed and updated the value of studentInfo from an object, to an array. Try swapping your initial state to be an array instead:
const [studentInfo, setStudentInfo] = useState([]);

Component not rerendering after axios Get (React)

I'm trying to render List of items of my DB using React.Context.
All my request work pretty well.
when i console log my states first I get an empty array and then array with the data that I need but my component is not updating. I have to go to another page an then go back to this page to get the data. I don't really understand why... here are my files..
ArticlesContext.js :
import React, { useState, createContext, useEffect } from 'react';
import axios from 'axios'
export const ArticlesContext = createContext();
export function ArticlesProvider(props) {
const [articles, setArticles] = useState([]);
const [user, setUser] =useState(0)
async function getArticles () {
await axios.get(`/api/publicItem`)
.then(res => {
setArticles(res.data);
})
}
useEffect( () => {
getArticles()
}, [user])
console.log(articles);
return (
<ArticlesContext.Provider value={[articles, setArticles]}>
{props.children}
</ArticlesContext.Provider>
);
}
Inventaire.js :
import React, { useContext, useEffect, useState } from 'react';
import './Inventaire.css';
import { ArticlesContext } from '../../../context/ArticlesContext';
import DeleteAlert from './Delete/Delete';
import Modify from './Modify/Modify';
import Filter from './Filter/Filter';
import axios from 'axios'
import Crud from '../../Elements/Articles/Crud/Crud';
import List from './List/List';
export default function Inventaire() {
const [articles, setArticles] = useContext(ArticlesContext);
const [filter, setFilter] = useState(articles)
console.log(articles);
//list for Inputs
const cat = articles.map(a => a.category.toLowerCase());
const categoryFilter = ([...new Set(cat)]);
const gender = articles.map(a => a.gender.toLowerCase());
const genderFilter = ([...new Set(gender)]);
//Event Listenner
//Uncheck All checkboxes
function UncheckAll() {
const el = document.querySelectorAll("input.checkboxFilter");
console.log(el);
for (var i = 0; i < el.length; i++) {
var check = el[i];
if (!check.disabled) {
check.checked = false;
}
}
}
//SearchBar
const searchChange = (e) => {
e.preventDefault();
const stuff = articles.filter((i) => {
return i.name.toLowerCase().match(e.target.value.toLowerCase())
})
setFilter(stuff)
UncheckAll(true)
}
const Types = (e) => {
if (e.target.checked === true) {
const stuff = filter.filter((i) => {
return i.category.toLowerCase().match(e.target.value.toLowerCase())
})
setFilter(stuff)
console.log(articles);
} else if (e.target.checked === false) {
setFilter(articles)
}
}
const Gender = (e) => {
if (e.target.checked === true) {
const stuff = filter.filter((i) => {
console.log(i.category, e.target.value);
return i.gender.toLowerCase().match(e.target.value.toLowerCase())
})
setFilter(stuff)
} else if (e.target.checked === false) {
setFilter(articles)
}
}
return (
<div className="inventaireContainer">
<input type="text" placeholder="Recherche un Article" onChange={searchChange} />
<div className="inventaireMenu">
<Crud />
<Filter
filter={Types}
categorys={categoryFilter}
genre={genderFilter}
target={Gender}
/>
</div>
<List filter={filter} articles={articles}/>
</div>
)
}
List.js :
import React from 'react';
import DeleteAlert from '../Delete/Delete';
import Modify from '../Modify/Modify';
export default function List({ filter, articles }) {
return (
<div>
{filter.map((details, i) => {
return (
<div className="inventaireBlock" >
<div className="inventaireGrid">
<div className="inventaireItemImg">
<img src={details.image} alt="ItemImg" />
</div>
<h2>{details.name}</h2>
<h3>{details.category}</h3>
<h3>{details.gender}</h3>
<div>
<p>S :{details.sizes[0].s}</p>
<p>M :{details.sizes[0].m}</p>
<p>L :{details.sizes[0].l}</p>
<p>XL :{details.sizes[0].xl}</p>
</div>
<h2> Prix: {details.price}</h2>
<div className="modify">
<Modify details={details._id} />
</div>
<div className="delete" >
<DeleteAlert details={details._id} articles={articles} />
</div>
</div>
</div>
)
})}
</div>
)
}
Thanks for your time

React - UseState not triggering rerender following fetch inside useEffect hook

I am just working through my first React tutorial using hooks - I am using trying to fetch local data within useEffect and then update the state using useState. I am then passing the state into a Context.provider which a child element (Card.js) subscribes to. Even though useEffect runs, the Card component and the state isn't being rerendered/updated. What am I doing wrong?
MainContext.js -
import React, { createContext, useState, useContext, useEffect, Fragment } from 'react';
import List from '../containers/List';
export const myContext= createContext();
export const MainContext = ({children}) => {
const [films, setFilms] = useState([]);
const loadData = () => {
try {
fetch('../src/assets/data.json').then( result => result.json()).then(movies => {
setFilms(films)
})
} catch (error) {
console.log('there has been an error')
}}
useEffect(() => {
loadData()
},[]);
return (
<myContext.Provider value={{films,setFilms }}>
{children()}
</myContext.Provider>
);
}
Card.js -
function Card() {
const {films} = useContext(myContext)
if (films !== undefined) {
return (
<div>
{ films.map((movie,i) => {
return (
<div key={i}>
<img src={movie.img.src} className='card-img-top' alt={movie.img.alt} />
<div className='card-body'>
<h2 className='card-title'>{`#${movie.ranking} - ${movie.title} (${movie.year})`}</h2>
</div>
<ul className='list-group list-group-flush'>
<li className='list-group-item'>{`Distributor: ${movie.distributor}`}</li>
<li className='list-group-item'>{`Amount: ${movie.amount}`}</li>
</ul></div>
)
})
}
</div>
)
} else {
return <div>{'Update Failed'}</div>
}
}
export default Card
You don't have any dependency in your useEffect array. This is why it doesn't trigger again once the app is mounted. You need to pass it a dependency, so it can run again each time the dependency value changes. Also, consider adding async before your loadData function.

React Router props.match and props.history.push are undefined while using useContext

I have moved my project to Codesandbox for better assistance with my question. Here is the link to the project.
I have two components SearchForm and AnimeDetails that receive API calls from my context component AnimeContext. The form is meant to display the searched Anime that was requested and AnimeDetails is supposed to display the details for the selected Anime.
I'm using props.match.params to get the id of the anime within the TopAnime component using <Link to={} /> and props.history.push to redirect to a new page once the form is submitted.
When I attempt to click on an Anime card to get the details, I receive
props.match is undefined
When I submit the form, I see the searched anime appear, but then I receive
props.history is undefined
I'm assuming this is React Router issue and that I am not setting something up correctly.
Here's what I have attempted so far and nothing has worked:
Using Redirect
Using the useHistory hook
Wrapping AnimeProvider with withRouter
In short, I cannot search for any titles and I cannot click on any Anime title on the HomePage to get it's details without getting undefined for props.match and props.history.
SearchForm component
import React, { useContext } from 'react';
import { withRouter } from 'react-router-dom'
import styled from 'styled-components'
import AnimeCard from './AnimeCard/AnimeCard';
import { AnimeContext } from '../store/AnimeContext'
const SearchForm = () => {
const { dataItems, animeSearched, handleSubmit } = useContext(AnimeContext)
return (
<div>
<Form onSubmit={handleSubmit}>
<Input
type="text"
name="anime"
placeholder="Enter title"
// ref={value => myValue = value}
/>
<FormButton type='submit'>Search</FormButton>
</ Form>
{animeSearched
?
<AnimeCard />
: null}
</div>
)
}
export default withRouter(SearchForm)
AnimeDetails component
import React, { useContext, useEffect } from "react";
import styled from "styled-components";
import { AnimeContext } from "../store/AnimeContext";
const AnimeDetails = () => {
const { fetching, anime, fetchAnimeDetails } = useContext(AnimeContext);
useEffect(() => {
fetchAnimeDetails();
});
return (
<>
{fetching && "Fetching..."}
{anime && (
<AnimeDetailsWrapper>
<AnimeDetailsContainer>
<Poster src={anime.image_url} />
{/* Details */}
<Details>
<Title>{anime.title}</Title>
<TitleJpn>{anime.title_japanese}</TitleJpn>
<Score>{anime.score || "N/A"}</Score>
{/* If no score then display N/A */}
<SongList>
<h3>Opening Themes</h3>
{anime.opening_themes // Make sure data is fully loaded before component renders
? anime.opening_themes.map((song, index) => (
<li key={index}>{song}</li>
))
: null}
</SongList>
</Details>
{/* Info Bar */}
<InfoBar>
{
<li>
Epiosdes: <span className="info-span">{anime.episodes}</span>
</li>
}
{
<li>
Duration: <span className="info-span">{anime.duration}</span>
</li>
}
{
<li>
<a
href={anime.trailer_url}
rel="external noopener noreferrer"
target="_blank"
>
View Trailer
</a>
</li>
}
</InfoBar>
{/* Synopsis */}
<Synopsis>{anime.synopsis}</Synopsis>
</AnimeDetailsContainer>
</AnimeDetailsWrapper>
)}
</>
);
};
export default AnimeDetails;
AnimeContext component
import React, { useState, useEffect, createContext } from 'react'
const AnimeContext = createContext()
const API = "https://api.jikan.moe/v3"
const AnimeProvider = (props) => {
const urls = [
`${API}/top/anime/1/airing`,
`${API}/top/anime/1/tv`,
`${API}/top/anime/1/upcoming`,
]
// State for top Anime
const [topTv, setTopTv] = useState([])
const [topAiring, setTopAiring] = useState([])
const [topUpcoming, setTopUpcoming] = useState([])
// State for Anime details
const [animeReq, setAnimeReq] = useState({
fetching: false,
anime: []
})
// State for Anime search form
const [dataItems, setDataItems] = useState([])
const [animeSearched, setAnimeSearched] = useState(false)
// Fetch top Anime
const fetchTopAnime = async () => {
return Promise.all(
urls.map(async url => {
return await fetch(url); // fetch data from urls
})
)
.then((responses) => Promise.all(responses.map(resp => resp.json())) // turn data into JSON
.then(data => {
const topTvFiltered = data[0].top.filter(item => item.rank <= 5) // filter out top 6
const topAiringFiltered = data[1].top.filter(item => item.rank <= 5)
const topUpcomingFiltered = data[2].top.filter(item => item.rank <= 5)
setTopTv(topTvFiltered)
setTopAiring(topAiringFiltered)
setTopUpcoming(topUpcomingFiltered)
console.log(data)
})
)
.catch(err => console.log("There was an error:" + err))
}
useEffect(() => {
fetchTopAnime()
}, [])
// Fetch Anime details
const fetchAnimeDetails = async () => {
setAnimeReq({ fetching: true })
const response = await fetch(`${API}/${props.match.params.animeId}`)
const data = await response.json()
console.log(data);
setAnimeReq({ fetching: false, anime: data }) // set initial state to hold data from our API call
}
const { fetching, anime } = animeReq;
// Fetch searched Anime
async function handleSubmit(e) {
e.preventDefault()
const animeQuery = e.target.elements.anime.value
const response = await fetch(`${API}/search/anime?q=${animeQuery}&page=1`)
const animeData = await response.json()
setDataItems(animeData.results)
setAnimeSearched(!animeSearched)
props.history.push('/dashboard')
}
return (
<AnimeContext.Provider value={{
topTv,
setTopTv,
topAiring,
setTopAiring,
topUpcoming,
setTopUpcoming,
dataItems,
setDataItems,
animeSearched,
setAnimeSearched,
fetching,
anime,
fetchTopAnime,
fetchAnimeDetails,
handleSubmit
}}>
{props.children}
</AnimeContext.Provider>
)
}
export { AnimeProvider, AnimeContext }
You should do this
import { useLocation, useNavigate } from 'react-router-dom';
//You should navigate
const navigate = useNavigate();
navigate('/app/example', { state: { message: "hello" } });
//You can receive
const location = useLocation();
console.log("location data", location.state);

Resources