I have a component with a lot of buttons, which increment the counter. I need the counter all the way up in the main component, so I want to pass the Counter value;
import { useState } from "react";
import Card from "./Card";
const ProductList = (props) => {
const products = props.products;
const [ Counter, setCounter ] = useState(0);
const Count = n => { setCounter(v => v + n) }
return (
<div className="ProductList" >
{products.map((product) => (
<Card product={product} key={product.id} C={Count} />))}
</div>
);
}
export default ProductList;
up to the parent component:
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;
It's a simple question, but I'm not sure how it works. Can anyone help? Thanks!
If you need the Counter reference in the Products component you should define it in the component itself and pass the function reference down to the child component.
Try to change your code like this:
Products component:
const Products = () => {
const [products, setProducts] = useState(null);
const [Counter, setCounter] = useState(0);
const count = (n) => {
setCounter((v) => v + n);
};
useEffect(() => {
fetch('http://localhost:8000/products')
.then((res) => {
return res.json();
})
.then((data) => {
setProducts(data);
});
}, []);
return (
<div className='ProductList'>
{products && <ProductList products={products} count={count}/>}
</div>
);
};
export default Products;
ProductList component
import Card from "./Card";
const ProductList = (props) => {
const products = props.products;
const count = props.count
return (
<div className="ProductList" >
{products.map((product) => (
<Card product={product} key={product.id} C={count} />))}
</div>
);
}
export default ProductList;
However, if you need to pass the function down multiple levels, you should probably use the Context API, but from the question, I presume you just need to go down one level.
Define the counter state in the parent component then pass both the counter and setCounter to the child as props. Update counter with setCounter and it will be update in the parent component also.
Child:
import Card from "./Card";
const ProductList = ({counter, setCounter}) => {
const products = props.products;
const Count = n => { setCounter(v => v + n) }
return (
<div className="ProductList" >
{products.map((product) => (
<Card product={product} key={product.id} C={Count} />))}
</div>
);
}
export default ProductList;
Parent:
import ProductList from "./ProductList";
const Products = () => {
const [products, setProducts] = useState (null);
const [counter, setCounter] = useState(0);
useEffect (() => {
fetch('http://localhost:8000/products')
.then(res => {
return res.json();
})
.then(data => {
setProducts(data);
})
}, []);
return (
<div className="ProductList">
{products &&
<ProductList
counter={counter}
setCounter={setCounter}
products={products}
/>}
</div>
);
}
export default Products;
Related
I Can't render my events. Its showing this error -
"Cannot update a component (App) while rendering a different component (EventList). To locate the bad setState() call inside EventList, follow the stack trace as described in https://reactjs.org/link/setstate-in-render"
Here is EventList Component code -
import { useEffect, useState } from "react";
import EventList from "../../event-list";
import EventForm from "../event-form";
const EventAction = ({
getEventsByClockID,
addEvent,
updateEvent,
clockID,
deleteEvent,
deleteEventsByClockID,
}) => {
const [isCreate, setIsCreate] = useState(false);
const [isToggle, setIsToggle] = useState(false);
const [eventState, setEventState] = useState(null)
const handleCreate = () => {
setIsCreate(!isCreate);
}
useEffect(() => {
setEventState(getEventsByClockID(clockID, true));
}, [isToggle])
const handleToggle = () => {
setIsToggle(!isToggle);
}
return (
<div>
<div>
<button onClick={handleCreate}>Create Event</button>
<button onClick={handleToggle}>Toggle Events</button>
</div>
{isCreate && (
<>
<h3>Create Event</h3>
<EventForm
clockID={clockID}
handleEvent={addEvent}
/>
</>
)}
{isToggle && (
<>
<h3>Events of this clock</h3>
<EventList
clockID={clockID}
eventState={eventState}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</>
)}
</div>
)
}
export default EventAction;
Here is my App Component Code -
import ClockList from "./components/clock-list";
import LocalClock from "./components/local-clock";
import useApp from "./hooks/useApp";
import { localClockInitState } from "./initialStates/clockInitState";
const App = () => {
const {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
} = useApp(localClockInitState);
return (
<div>
<LocalClock
clock={localClock}
updateClock={updateLocalClock}
createClock={createClock}
/>
<ClockList
clocks={clocks}
localClock={localClock.date}
updateClock={updateClock}
deleteClock={deleteClock}
getEventsByClockID={getEventsByClockID}
addEvent={addEvent}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</div>
)
}
export default App;
and Here is my useApp hook -
import { useState } from "react";
import deepClone from "../utils/deepClone";
import generateID from "../utils/generateId";
import useEvents from "./useEvents";
const getID = generateID('clock');
const useApp = (initValue) => {
const [localClock, setLocalClock] = useState(deepClone(initValue));
const [clocks, setClocks] = useState([]);
const {
// events,
// getEvents,
getEventsByClockID,
addEvent,
deleteEvent,
deleteEventsByClockID,
updateEvent,
} = useEvents();
const updateLocalClock = (data) => {
setLocalClock({
...localClock,
...data,
})
}
const createClock = (clock) => {
clock.id = getID.next().value;
setClocks((prev) => ([
...prev, clock
]))
}
const updateClock = (updatedClock) => {
setClocks(clocks.map(clock => {
if(clock.id === updatedClock.id) return updatedClock;
return clock;
}));
}
const deleteClock = (id) => {
setClocks(clocks.filter(clock => clock.id !== id));
}
return {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
}
}
export default useApp;
I want to show all events incorporated with each individual clock.
I am using react-select with more than 20,000 options fetched from the database via Node API.
Page was not even loading .
Now, I added "react-select-async-pagination".
But the data is fetched once only.
import React, { useRef, useState, useEffect } from "react";
import Select from "react-select";
import LoadOptions from "./LoadOptions";
import { AsyncPaginate } from "react-select-async-paginate";
const TooManySelect = () => {
const [value, onChange] = useState(null);
return (
<div className="Select-options">
<label>Pdt code</label>
<AsyncPaginate
defaultOptions
value={value}
loadOptions={LoadOptions}
onChange={onChange}
/>
</div>
);
};
export default TooManySelect;
LoadOptions : Here is the api call. I am passing the count of the last row fetched every time via "prevLast" so that I can use the OFFSET prevLast ROWS inside database query.
import { useState } from "react";
const sleep = (ms) =>
new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
const baseurl = "http://localhost:5000/api";
const LoadOptions = async (search, prevOptions) => {
const [prevLast, setPrevLast] = useState(0);
const [pdtOpt, setPdtOpt] = useState([]);
await sleep(1000);
const response = await fetch(`${baseurl}/pdt/${prevLast}`);
const pList = await response.json();
const pdtList = [];
for (let i = 0; i < pList.length; i++) {
pdtList.push({ label: pList[i].pdtno, value: pList[i].pdtno });
}
setPdtOpt(pdtList);
setPrevLast(pList.length);
return {
options: pdtList,
hasMore: true
};
};
export default LoadOptions;
Here is my codesandbox link.
https://codesandbox.io/s/react-select-paginate-test-tob90j
My question is : How can we access thousands of (select) options from DB without page freeze?
I got this link while googleing.
https://blog.saeloun.com/2022/03/03/infinite-scroll-with-pagination.html#using-lazy-loading-and-pagination
It helped me came to a perfect solution.
So my current code goes like this
(Made only a few changes from the code given in the above link).
Select component:
import React, { useState } from "react";
import SelectWrapper from "./SelectWrapper";
const baseurl = "http://localhost:5000/api";
function MainSelect() {
const [options, setOptions] = useState([]);
const [selectedOption, setSelectedOption] = useState("");
const [pageNo, setPageNo] = useState(0);
const [hasNextPage, setHasNextPage] = useState(true);
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
const loadOptions = async (page) => {
try {
// console.log(`Page ${page}`);
const size = 50;
setIsNextPageLoading(true);
const data = await fetch(`${baseurl}/pdt/${page}/${size}`);
const pList = await data.json();
const pdtList = [];
for (let i = 0; i < pList.length; i++) {
pdtList.push({ label: pList[i].pdtno, value: pList[i].pdtno });
}
setOptions(pdtList);
setIsNextPageLoading(false);
setHasNextPage(pdtList.length < 500);
setPageNo(page);
} catch (err) {
console.log(err);
}
};
console.log(options);
const loadNextPage = async () => {
await loadOptions(pageNo + 1);
};
return (
<div className="dropdown">
<div className="dropdown">
<div className="label">
<label>Pdt</label>
</div>
<SelectWrapper
value={selectedOption}
placeholder="Select"
isClearable
hasNextPage={hasNextPage}
isNextPageLoading={isNextPageLoading}
options={options}
loadNextPage={loadNextPage}
onChange={(selected) => setSelectedOption(selected)}
/>
</div>
</div>
);
}
export default MainSelect;
SelectWrapper that carries out the Virtualization part:
import React, { useEffect, useState } from "react";
import { FixedSizeList as List } from "react-window";
import InfiniteLoader from "react-window-infinite-loader";
import Select from "react-select";
import AutoSizer from "react-virtualized-auto-sizer";
const SelectWrapper = (props) => {
const {
hasNextPage,
isNextPageLoading,
options,
loadNextPage,
placeholder,
onChange,
value,
} = props;
const [selectedOption, setSelectedOption] = useState(value);
useEffect(() => {
setSelectedOption(value);
}, [value]);
const itemCount = hasNextPage ? options.length + 1 : options.length;
const loadMoreItems = isNextPageLoading ? () => {} : loadNextPage;
const isItemLoaded = (index) => !hasNextPage || index < options.length;
const MenuList = ({ children }) => {
const childrenArray = React.Children.toArray(children);
const Item = ({ index, style, ...rest }) => {
const child = childrenArray[index];
return (
<div
className="drop-down-list"
style={{
borderBottom: "1px solid rgb(243 234 234 / 72%)",
display: "flex",
alignItems: "center",
...style,
}}
onClick={() => handleChange(options[index])}
{...rest}
>
{isItemLoaded(index) && child ? child : `Loading...`}
</div>
);
};
return (
<AutoSizer disableHeight>
{({ width }) => (
<InfiniteLoader
isItemLoaded={(index) => index < options.length}
itemCount={itemCount}
loadMoreItems={loadMoreItems}
>
{({ onItemsRendered, ref }) => (
<List
className="List"
height={150}
itemCount={itemCount}
itemSize={35}
onItemsRendered={onItemsRendered}
ref={ref}
width={width}
overscanCount={4}
>
{Item}
</List>
)}
</InfiniteLoader>
)}
</AutoSizer>
);
};
const handleChange = (selected) => {
console.log("test");
onChange(selected);
};
return (
<Select
placeholder={placeholder}
components={{ MenuList }}
value={selectedOption}
options={options}
{...props}
/>
);
};
export default SelectWrapper;
For anyone who needs the same, I have updated the codepen as well.
https://codesandbox.io/s/react-select-paginate-test-tob90j
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.
I'm trying to make a Checkbox component.
Here is my Checkbox.tsx.
import React from "react";
import * as S from "./style";
const Checkbox: React.FC<S.ICheckboxProps> = ({ checked, setChecked }) => {
return <S.StyledCheckbox checked={checked} onClick={setChecked} />;
};
and this is my useCheckbox.tsx,
import { useState } from "react";
export const useCheckbox = (initialState: boolean) => {
const [checked, _setChecked] = useState<boolean>(initialState);
const setCheckedToggle = () => _setChecked((prev) => !prev);
const setCheckedTrue = () => _setChecked(true);
const setCheckedFalse = () => _setChecked(false);
return { checked, setCheckedToggle, setCheckedTrue, setCheckedFalse };
};
export default Checkbox;
It works good. I can use this like
import Layout from "components/Layout";
import { useCheckbox } from "hooks/useCheckbox";
import Checkbox from "components/Checkbox";
const Home = () => {
const { checked, setCheckedToggle } = useCheckbox(false);
return (
<Layout>
<Checkbox checked={checked} setChecked={setCheckedToggle} />
</Layout>
);
};
export default Home;
But I have trouble in the List component.
List has a Checkbox component, and I have to use this List with data.
const Home = ({data}) => {
return (
<Layout>
{data.map((d) => <List />)}
</Layout>
);
};
In this case, is there a way to determine if the list is selected?
If the List has useCheckbox, the Home component doesn't know the checked state.
Should I use useCheckbox in the Home component for data.length times? I think this is not good.
Thanks for reading, and Happy new year.
If you want the checkbox state to exist at the level of Home then you'll need state in the Home component that can handle multiple items, either as an array or object.
Then where you map over data you can pass down checked and setChecked as props to List, with all the logic defined in Home using the item index (or preferably an ID if you have one) in relation to your Home state.
Here's an example of a hook you could use in Home
import { useState } from "react";
export const useCheckboxes = () => {
const [checkedIds, setCheckedIds] = useState([]);
const addToChecked = (id) => setCheckedIds((prev) => [...prev, id]);
const removeFromChecked = (id) =>
setCheckedIds((prev) => prev.filter((existingId) => existingId !== id));
const isChecked = (id) => !!checkedIds.find(id);
const toggleChecked = (id) =>
isChecked(id) ? removeFromChecked(id) : addToChecked(id);
return { isChecked, toggleChecked };
};
And you would use it like this
const Home = ({ data }) => {
const { isChecked, toggleChecked } = useCheckboxes();
return (
<Layout>
{data.map((d) => (
<List
key={d.id}
checked={isChecked(d.id)}
toggleChecked={() => toggleChecked(d.id)}
/>
))}
</Layout>
);
};
I am creating an array of categories in useEffect callback, it works fine when i console.log it.
But then when I .map() it, the resulting array is empty.
import React, { useEffect } from 'react';
export const Categories = (props) => {
let categories = [];
useEffect(() => {
props.films.forEach((film) => {
if (categories.findIndex(item => item === film.category) === -1)
{
categories.push(film.category);
console.log(categories);
}
})
}, [props.films, categories])
return (
<div>
{categories.map((category) => {
return (
<div>
{category}
</div>
)
})}
</div>
);
}
Does someone have an idea ?
You should use a state value for categories:
const [categories, setCategories] = React.useState([])
useEffect(() => {
let categories = []
props.films.forEach((film) => {
if (categories.findIndex(item => item === film.category) === -1)
{
categories.push(film.category);
console.log(categories);
}
})
setCategories(categories)
}, [props.films])
The component is not getting re-rendered when the data in categories is getting changed. In order to render the categories you need to store the data using useState.
import React, { useEffect, useState } from 'react';
export const Categories = (props) => {
const [categories, setCategories] = useState([]);
useEffect(() => {
let filmCategories = []
props.films.forEach((film) => {
if (categories.findIndex(item => item === film.category) === -1)
{
filmCategories.push(film.category);
console.log(filmCategories);
}
})
setCategories(filmCategories)
}, [props.films])
return (
<div>
{categories.map((category) => {
return (
<div>
{category}
</div>
)
})}
</div>
);
}
Hope this helps.
I think you maybe be new to react. I recommend you to take a look at React State and Lifecycle
You are using the react hook useEfect which will be called after your component is rendered in DOM
I can think of two possible solutions to solve this
1) using the react hook useState
import React, { useEffect } from 'react';
export const Categories = (props) => {
//Make the local variable into a variable representing state
let [categories, setCategories] = useState([]);
useEffect(() => {
const result = [...categories];
props.films.forEach((film) => {
if (result.findIndex(item => item === film.category) === -1)
{
result.push(film.category);
console.log(result);
}
})
//Set the state value to trigger a re-render of your component
if(result.length !== categories.length)
setCategories(result);
}, [props.films, categories])
return (
<div>
{categories.map((category) => {
return (
<div>
{category}
</div>
)
})}
</div>
);
}
2) If re-rendering is not required, remove the useEffect hook
import React, { useEffect } from 'react';
export const Categories = (props) => {
let categories = props.films.map(film => {
if (categories.findIndex(item => item === film.category) === -1)
{
categories.push(film.category);
console.log(categories);
}
}
return (
<div>
{categories.map((category) => {
return (
<div>
{category}
</div>
)
})}
</div>
);
}
If the useEffect react hook is required, then solution 1 is better
If there is no need to re-render the react component, then solution 2 is better