useState set to string not working in Reactjs - 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);
}, [])

Related

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

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 />
}
)

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([]);

In react, how can I deselect an element after it has been set as active with onClick?

I have an image grid in react, with an onclick function that highlights the selected image. Clicking another image will change the active element but I'd like to be able to re-click the selected icon to deselect it and return the grid to default.
Here is my codesandbox and the script is below
import { useState, useRef, useEffect } from "react";
import "./App.css";
import { Data } from "./Data.js";
function App() {
const scrollRef = useRef();
useEffect(() => {
const el = scrollRef.current;
if (el) {
const wheelListener = (e) => {
e.preventDefault();
el.scrollTo({
left: el.scrollLeft + e.deltaY * 5,
behavior: "smooth"
});
};
el.addEventListener("wheel", wheelListener);
return () => el.removeEventListener("wheel", wheelListener);
}
}, []);
const [active, setActive] = useState(-1);
const [active2, setActive2] = useState(false);
return (
<div ref={scrollRef} className="grid_container">
{Data.map((prev, i) => {
return (
<div
onClick={() => {
setActive(i);
setActive2(true);
console.log(prev.Team);
}}
className={`${
(active === i && "scale") || (active2 && "notScale")
} card`}
key={i}
>
<img src={prev.TeamBadge} alt="" />
</div>
);
})}
</div>
);
}
export default App;
If I understand the problem correctly, I think this solves the problem, based on the codesandbox:
onClick={() => {
setActive(i);
if (active === i) {
setActive2(null);
setActive(null);
} else {
setActive2(true);
}
console.log(i);
console.log(prev.Team);
}}
since every team has its unique number, the logic here is to check if the same number is in the state (if that makes sense). Let me know if this was you are looking for!
You can set your state like that :
setActive2(!active2);

reset new array reactjs infinite scroll

I have tried infinite scroll for reactjs from this link https://www.youtube.com/watch?v=NZKUirTtxcg&t=303 and work perfectly. But I want to improve with my condition.
I have make infite scroll for case products, the product has sub_category and sub_category has one category. For example I have one page showing all products by category (it's showing all sub_category).
The user can choose the product base sub_category (the page showing just what user choose for sub_category).
And my problem is I don't know to reset product variable as new array to fullfill products from sub_category.
I have two component ListInfiteTwo.jsx and UseProductSearch.jsx
ListInfiteTwo.jsx
import React, { useEffect, useState, useRef, useCallback } from 'react';
import axios from 'axios';
import { makeStyles } from '#material-ui/core/styles';
import Grid from '#material-ui/core/Grid';
import '../styleProduct.css';
import { NavbarPageListProduct, NotFoundPage, COBottomNav } from '../../../components';
import configAPI from '../../../api/configAPI';
import productAPI from '../../../api/productAPI';
import Kcard from '../../Card/Kcard';
import UseProductSearch from './UseProductSearch';
export default function ListInfiniteTwo(props) {
const classes = useStyles();
const [totQtyItem, setTotQtyItem] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
const [category, setCategory] = useState(props.match.params.id);
const [subCategory, setSubCategory] = useState(null);
const [subCategories, setSubCategories] = useState([]);
const [amount, setAmount] = useState(0);
const [limit, setLimit] = useState(6);
const [selectedSubCategory, setSelectedSubCategory] = useState('selectedSubCategory');
const {
loading,
error,
products,
hasMore
} = UseProductSearch(pageNumber, category, limit, subCategory)
const observer = useRef()
const lastProductElementRef = useCallback(node => {
if (loading) return
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasMore) {
setPageNumber(prevPageNumber => prevPageNumber + 1)
}
})
if (node) observer.current.observe(node)
}, [loading, hasMore])
useEffect(() => {
let getSubCategoriesAct = configAPI.getSubCategory(kategori);
getSubCategoriesAct.then((response) => {
setSubCategories(response.data)
}).catch(error => {
console.log(error)
});
},[])
const callBackAddItemTotal = (data) => {
setTotQtyItem(data)
}
const callBackDeleteItemTotal = (data) => {
setTotQtyItem(data)
}
const callBackCalculateAmount = (data) => {
setAmount(data);
}
const selectSubCategory = (id) => {
setSubKategori(id)
setPageNumber(1)
}
return (
<>
<NavbarPageListProduct
titleView="List Product"
viewPrev="detailOrder"
totalQtyItem={totQtyItem}
cHistoryId={props.match.params.id}
/>
<div className={classes.root}>
<div className="css-ovr-auto">
<div className="css-ovr-auto">
<div className="css-c-1hj8">
<div className="css-c-2k3l">
{
<>
<div className={ selectedSubCategory === 'selectedSubCategory' ? 'css-sb-sl-top-active' : 'css-sb-sl-top'} >
<div className="css-sb-sl-label">
<span className="css-sb-sl-val"> All on Category </span>
</div>
</div>
{subCategories.map((x, z) =>
<div className="css-sb-sl-top" onClick={() => selectSubCategory(x._id) }>
<div className="css-sb-sl-label">
<span className="css-sb-sl-val" >{x.name}</span>
</div>
</div>
)}
</>
}
</div>
</div>
</div>
</div>
<Grid container spacing={1}>
<Grid container item xs={12} spacing={1}>
{
products.length >= 1 ?
products.map((pr, index) =>
<React.Fragment>
<div ref={lastProductElementRef}></div>
<Kcard
ref={lastProductElementRef}
product={pr}
callBackAddItemTotal={callBackAddItemTotal}
callBackDeleteItemTotal={callBackDeleteItemTotal}
callBackCalculateAmount={callBackCalculateAmount}
/>
</React.Fragment>
)
:
<NotFoundPage
content="No Products"
/>
}
</Grid>
</Grid>
</div>
<div>{loading && 'Loading...'}</div>
<div>{error && 'Error'}</div>
{
amount > 0 ?
<COBottomNav
titleBottom="Total Pay"
amount={amount}
titleBtnBottom="Process"
action='proces_list'
/>
:
""
}
</>
)
}
UseProductSearch.jsx
import { useEffect, useState } from 'react';
import axios from 'axios';
export default function UseProductSearch(pageNumber, category, limit, subCategory) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
const [products, setProducts] = useState([])
const [hasMore, setHasMore] = useState(false)
const [lastPage, setLastPage] = useState(0)
useEffect(() => {
setProducts([])
}, [])
useEffect(() => {
setLoading(true)
setError(false)
let cancel
if (subCategory) {
setProducts([])
}
axios({
method: 'GET',
url: process.env.REACT_APP_API_URL + `data-product-pagination`,
params: {
orderby: 'newest',
type: 'verify',
page: pageNumber,
limit: limit,
xkategori: category,
subkategori: subCategory,
},
cancelToken: new axios.CancelToken(c => cancel = c)
}).then(res => {
if (res.data.data) {
if (res.data.data.data.length > 0) {
setProducts(prevProducts => {
return [...new Set([...prevProducts, ...res.data.data.data])]
})
}
}
setHasMore(res.data.data.data.length > 0)
setLoading(false)
setLastPage(res.data.data.last_page)
}).catch(e => {
if (axios.isCancel(e)) return
setError(true)
})
return () => cancel()
}, [pageNumber, category, limit, subCategory])
return { loading, error, products, hasMore }
}
what I have tried to add code on UseProductSearch.jsx
if (subCategory) {
setProducts([])
}
it's work when user choose sub category the page showing new products base on sub_category, but when I scroll down it's reseting the product to empty array.
Thx, for your help...
Try including subCategory as a dependency in your first useEffect hook from useProductSearch instead. This would reset your array whenever the subCategory state changes.
useEffect(() => {
setProducts([])
}, [subCategory])

function Component receive props but doesn't render it

Situation:
first, I fetch imgs list from database:
{imgs:
[
{_id: '...',img:'***.png'},
...,
]
}
then, signature img.src using ali-oss-hook, results like:
{imgs:
[
{_id:'...', img: '***.png', src: 'signatured-http-address'}
...,
]
}
then, pass the imgs to PictureList component :
<PictureList imgs={images}
PictureList receive the new props,but didn't render it
const PictureList = ({ imgs }) => {
return (
<ul>
{imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
Code
Picture.js
import React, {useEffect, useState, useRef } from 'react'
import { useAlioss } from '../../hooks/oss-hook'
import PictureList from '../../components/PictureList'
import './style.less'
const Pictures = () => {
const [loading, setLoading] = useState(true)
const [signatured, setSignatured] = useState(false)
const [results, setResults] = useState()
const [images, setImages] = useState([])
const { allowUrl } = useAlioss()
const resultsDoSetRef = useRef(false)
async function getImages() {
try {
const dbResponse = await fetch(
`${process.env.REACT_APP_BACKEND_URL}/upload/images`
);
const resu = await dbResponse.json();
setResults(resu)
resultsDoSetRef.current = true
} catch (e) {
console.log("get images failed")
} finally {
setLoading(false)
console.log("get images done!")
}
}
useEffect(() => {
getImages();
}, [])
async function signatureUrl(raw) {
setSignatured(false)
try {
let tempImgs = []
raw.imgs.forEach((r) => {
allowUrl(r.img).then((res) => {
r.img = res;
tempImgs.push(r)
});
});
setImages(tempImgs);
} catch (e) {
console.log("signature failed",e)
} finally {
setSignatured(true)
console.log("signature done!")
}
}
useEffect(() => {
if (resultsDoSetRef.current) {
resultsDoSetRef.current = false
signatureUrl(results);
}
},[results])
return (
<div className="picture">
{loading ? <h1>Loading</h1> : <PictureList imgs={images} />}
</div>
);
};
export default Pictures
PictureList.js
const PictureList = ({ imgs }) => {
return (
<ul>
{imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
chrome react devTool component shows props
chrome devTool element shows empty PictureList
In chrome devTool react component shows right props, but the PictureList component still empty <ul></ul>.
Which part is wrong?
Look at the PictureList.js, you are receving "imgs" as the arugument of the function, this is not the same as property you passed in
<PictureList imgs={images}
This "imgs" is actually an object that has a property imgs, so your code will become:
const PictureList = ({ imgs }) => {
return (
<ul>
{imgs.imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
P.S : Just a suggestion, generally props(or something similarly descriptiove) is used as the parameter argument, so your code will be something like this:
const PictureList = ({ props }) => {
return (
<ul>
{props.imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
In Picture --signatureUrl() method, raw.imgs.forEach() returns a bounch of promises, these promises can't resolve all at once.
When setImages(tempImgs) ,the images in useState hook receive a empty array first, then push new image to images array when raw.imgs.forEach() returned promise resolve a new image item.
So, we must wait all allowUrl(r.img) promises resolved, then setImages(tempImgs).
function signatureUrl(raw) {
const tasks = raw.imgs.map(i => allowUrl(i.img))
Promise.all(tasks).then(values => {
let resultImgs = raw.imgs.map((t, index) => ({ ...t, src: values[index] }));
setImages(resultImgs)
})
}
PS: Solution do works, but all analizes may be wrong, for reference only.

Resources