I am using redux with react but i save redux for main app state, and using react hooks for non-shared state between components.
For Example, i have a navbar menu items, every item color should turn blue once get clicked, so my mind goes to useState to change the item that get clicked and add a class .active to it.
The problem i face is that i do not get the latest state, but i get the previous state and i understand that setting state in react is asynchronous and batched.
Demo Code:
Component
const [targets, getTarget] = useState([]);
const addTarget = (e) => {
getTarget([e.target]);
}
const handleOnItemClick = (e, category) => {
addTarget(e);
console.log(targets) // return [] on first click
}
myFunctionToAddAcTiveClass () {
//i need to use state here but i could not
// because it does not return the latest state
}
return(
<ul className="categories-list-items d-flex justify-content-between mb-4">
{items.map((item, index) => {
return (
<li
key={index}
onClick={(e) => handleOnItemClick(e, category)}
>
{item}
</li>
);
})}
</ul>
)
Is there a way to get the right item on every click to add the .active class??
Related
i have an array, called reportsData, then i need to filter it, generating some checkboxes with each of them having a label based on each name that comes from another array (emittersData), so basically i set it like this:
const [searchUser, setSearchUser] = useState<string[]>([])
const mappedAndFiltered = reportsData
.filter((value: any) =>
searchUser.length > 0 ? searchUser.includes(value.user.name) : true
)
Then i render my checkboxes like this:
function EmittersCheckboxes () {
const [checkedState, setCheckedState] = useState(
new Array(emittersData.length).fill(false)
)
const handleOnChange = (position: any, label: any) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
)
setSearchUser((prev) =>
prev.some((item) => item === label)
? prev.filter((item) => item !== label)
: [...prev, label]
)
setCheckedState(updatedCheckedState)
};
return (
<div className="App">
{emittersData.map((value: any, index: any) => {
return (
<li key={index}>
<div className="toppings-list-item">
<div className="left-section">
<input
className="h-4 w-4 focus:bg-indigo border-2 border-gray-300 rounded"
type="checkbox"
id={`custom-checkbox-${index}`}
name={value.Attributes[2].Value}
value={value.Attributes[2].Value}
checked={checkedState[index]}
onChange={() => handleOnChange(index, value.Attributes[2].Value)}
/>
<label className="ml-3 font-medium text-sm text-gray-700 dark:text-primary" htmlFor={`custom-checkbox-${index}`}>{value.Attributes[2].Value}</label>
</div>
</div>
</li>
);
})}
</div>
)
}
And on the react component i am rendering each checkbox, that is a li, like:
<ul><EmittersCheckboxes /></ul>
And i render the mappedAndFiltered on the end.
Then it is fine, when i click each generated checkbox, it filters the array setting the state in setSearch user and the array is filtered.
You can check it here: streamable. com /v6bpk6
See that the filter is working, the total number of items in the array is changing based on the checkbox selected (one or more).
But the thing is that each checkbox does not become 'checked', it remains blank (untoggled).
What am i doing wrong, why doesnt it check itself?
You've defined your EmittersCheckboxes component inside another component. and every time that the parent component renders (by state change) your internal component is redefined, again and again causing it to lose it's internal state that React holds for you.
Here's a simplified example:
import React, { useState } from "react";
function CheckboxeComponent() {
const [checkedState, setCheckedState] = useState(false);
return (
<div>
<span>CheckboxeComponent</span>
<input
type="checkbox"
checked={checkedState}
onChange={() => setCheckedState((x) => !x)}
/>
</div>
);
}
export default function App() {
const [counter, setCounter] = useState(1);
function InternalCheckboxeComponent() {
const [checkedState, setCheckedState] = useState(false);
return (
<div>
<span>InternalCheckboxeComponent</span>
<input
type="checkbox"
checked={checkedState}
onChange={() => setCheckedState((x) => !x)}
/>
</div>
);
}
return (
<>
<InternalCheckboxeComponent />
<CheckboxeComponent />
<button onClick={() => setCounter((c) => c + 1)}>{counter}</button>
</>
);
}
There's the App (parent component) with its own state (counter), with a button to change this state, clicking this button will increase the counter, causing a re-render of App. This re-render redefines a new Component named InternalCheckboxeComponent every render.
The InternalCheckboxeComponent also has an internal state (checkedState).
And there's an externally defined functional component named CheckboxeComponent, with this component React is able to hold its own state, because it's not redefined (It's the same function)
If you set the state of each to be "checked" and click the button, this will cause a re-render of App, this will redefine the InternalCheckboxeComponent function, causing React to lose its state. and the CheckboxeComponent state remains in React as it's the same function.
I recently start learning React. I have problem when use dispatch in UseEffect, which is inside the child in the loop. I can't publish all project, but below piece of code:
On the home page online store products are displayed. In a separate component there is a small card that is displayed using the map loop:
<Row className='mt-20'>
{products.map(product => (
<ProductItem key={product._id} product={product} history={history} />
))}
</Row>
Child component code:
const ProductItem = ({ product, history }) => {
const dispatch = useDispatch()
const reviewList = useSelector(state => state.reviewList)
const { reviews } = reviewList
useEffect(() => {
let clean = false
if (!clean) dispatch(listReviewsDetailsByProductId(product._id))
return () => (clean = true)
}, [dispatch, product])
const productRate =
reviews.reduce((acc, item) => item.rating + acc, 0) / reviews.length || 0
return (
<Col xl='3' sm='6'>
<Card>
<Card.Body>
<div
className={'product-img position-relative ' + styles.wrapperImage}
>
<Link to={'/product/' + product.slug}>
{product.images[0] && (
<img
src={product.images[0].path}
alt=''
className='mx-auto d-block img-fluid'
/>
)}
</Link>
</div>
<div className='mt-4 text-center'>
<h5 className='mb-3 text-truncate'>
<Link to={'/product/' + product.slug} className='text-dark'>
{product.name}{' '}
</Link>
</h5>
{reviews && (
<div className='mb-3'>
<StarRatingsCustom rating={productRate} size='14' />
</div>
)}
<ProductPrice price={product.price} newPrice={product.newPrice} />
</div>
</Card.Body>
</Card>
</Col>
)
}
I use dispatch action to get reviews for a specific product from the server and then calculate the rating and display it. Unfortunately, it works every other time, the rating appears, then disappears. I would be grateful for your help!
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in SingleProduct (created by Context.Consumer)
in Route (at Root.jsx:96)
The problem is the clean variable which is not a part of your component's state. It exists only inside the scope of the useEffect callback and gets recreated every time that the effect runs.
What is the purpose of clean and when should we set it to true? The cleanup function of a useEffect hook only runs when the component unmounts so that would not be the right place to set a boolean flag like this.
In order to dispatch once per product, we can eliminate it and just rely on the dependencies array. I'm using product_.id instead of product so that it re-runs only if the id changes, but other property changes won't trigger it.
useEffect(() => {
dispatch(listReviewsDetailsByProductId(product._id))
}, [dispatch, product._id])
If this clean state serves some purpose, then it needs to be created with useState so that the same value persists across re-renders. You need to figure out where you would be calling setClean. This code would call the dispatch only once per component even if the product prop changed to a new product, which is probably not what you want.
const [clean, setClean] = useState(false);
useEffect(() => {
if (!clean) {
dispatch(listReviewsDetailsByProductId(product._id))
setClean(true);
}
}, [dispatch, product._id, clean, setClean])
I have created an Accordion component which has data(object) and expanded(boolean) as props.
expanded props is used to set the expanded/collapsed state of this component passed as a prop.
const DeltaAccordion = ({ index, data, expanded = true }) => {
Accordion component also has an internal state
const [isExpanded, setIsExpanded] = useState(expanded);
which is used for expanding/collapsing the accordion.
Below is my complete component
Accordion.jsx
import React, { useState } from "react";
// styles
import styles from "./index.module.scss";
const Accordion = ({ index, data, expanded = true }) => {
// state
const [isExpanded, setIsExpanded] = useState(expanded);
console.log(data.name, `prop-val==${expanded}`, `inner-state==${isExpanded}`);
return (
<div
className={`${styles.container} caption ${isExpanded && styles.expanded}`}
>
<div className={styles.header} onClick={() => setIsExpanded(!isExpanded)}>
<div>{data.name}</div>
<div>Click</div>
</div>
<div className={styles.content}>
{data.newValue && (
<div className={styles.newValue}>
<span>{data.newValue}</span>
</div>
)}
{data.oldValue && (
<div className={styles.oldValue}>
<span>{data.oldValue}</span>
</div>
)}
</div>
</div>
);
};
export default Accordion;
The parent component has a list of data and it loops through the list to create an accordion for each data item.
App.js file
{dataList.map((data, index) => (
<Accordion data={data} expanded={!collpaseAll} index={1} />
))}
Here goes problem
There is also a button in my App.js file which is for either expanding/collapsing all the accordions together.
But when I pass its value as prop expanded to the accordion component then this value is not getting applied to the internal isExpanded state of the accordion component.
Here is live running code at codesandbox: https://codesandbox.io/s/goofy-nobel-qfxm1?file=/src/App.js:635-745
Inside the Accordion
const [isExpanded, setIsExpanded] = useState(expanded);
This line will take first time(on first render) value. To assign it next time(rerender) value you need to add a effect
useEffect(() => {
setIsExpanded(expanded);
}, [expanded]);
And in your case, you can use the props expanded directly inside Accordion, you dont need to take it in local state.
How I do it right now
I have a list of items. Right now, when the user presses button X, shouldShowItem is toggled. shouldShowItem ultimately lies in redux and is passed down into Item as a prop. it's either true or false. When it changes, toggleDisplay is called and changes state in my hook:
useEffect(() => {
toggleDisplay(!display); //this is just a useState hook call
}, [shouldShowItem]); //PS: I'm aware that I don't need this extra step here, but my actual code is a bit more complicated, so I just simplified it here.
My Problem is, that I have one single shouldShowItem property in redux, not one shouldShowItem for each item. I don't want to move this property into the redux-state for each and every item.
Problem:
The problem with my construction however is that shouldShowItem is being saved, which means that if I toggle it at time X for item Y, and then my Item Z also re-renders as a result of an unrelated event, it will re-render with an updated shouldShowItem state, - although that state change was intended for Item X.
Essentially, I am saving the state of shouldShowItem in redux while I just need a toggle, that I can dispatch once, that works on the current Item, and then isn't read / needed anymore. I want to basically dispatch a toggle, - I don't care about the state of each item within redux, I just care that it's toggled once.
Suggestions?
Edit: More Code
Item:
const Item = ({shouldShowItem, itemText, updateCurrentItem})=>
{
const [display, toggleDisplay] = useState(false);
useEffect(() => {
if (isSelected) {
toggleDisplay(!display);
}
}, [shouldShowItem]);
return (
<div
onClick={() => {
toggleDisplay(!display);
updateCurrentItem(item._id);
}}
>
{display && <span>{itemText}</span>}
</div>
);
}
Mapping through item list:
allItems.map((item, i) => {
return (
<Item
key={i}
shouldShowItem={this.props.shouldShowItem}
itemText={item.text}
updateCurrentItem={this.props.updateCurrentItem}
);
});
The props here come from redux, so shouldShowItem is a boolean value that lies in redux, and updateCurrentItem is an action creator. And well, in redux i simply just toggle the shouldShowItem true & false whenever the toggle action is dispatched. (The toggle action that sets & unsets true/false of shouldShowItem is in some other component and works fine)
instead of a boolean shouldShowItem, why not convert it into an object of ids with boolean values:
const [showItem, setShowItem] = useState({id_1: false, id_2: false})
const toggleDisplay = id => {
setShowItem({...showItem, [id]: !showItem[id]})
updateCurrentItem(id)
}
allItems.map((item, i) => {
return (
<Item
key={i}
shouldShowItem={showItem[item._id]}
itemText={item.text}
updateCurrentItem={toggleDisplay}
);
});
const Item = ({shouldShowItem, itemText, updateCurrentItem}) => {
return (
<div
onClick={() => toggleDisplay(item._id)}
>
{shouldShowItem && <span>{itemText}</span>}
</div>
)
}
I am trying to create an accordion component using React, but the animation is not working.
The basic idea is, I believe, pretty standard, I am giving each item body a max-height of 0 which is affected by adding a show class to an element. I am able to select and show the item I want, but the animation to slide in/out is not working.
With the Chrome dev tools open, when I click on one of the items I can see that the whole "accordion" element is flashing, which leads me to believe that the whole element is being re-rendered. But I am unsure why this would be the case.
Here is the relevant Accordion component:
import React, { useState } from "react";
const Accordion = ({ items }) => {
const [selectedItem, setSelectedItem] = useState(0);
const AccordionItem = ({ item, index }) => {
const isOpen = index === selectedItem;
return (
<div className="accordion-item">
<div
onClick={() => {
setSelectedItem(index);
}}
className="accordion-header"
>
<div>{item.heading}</div>
</div>
<div className={`accordion-body ${isOpen ? "show" : ""}`}>
<div className="accordion-content">{item.body}</div>
</div>
</div>
);
};
return (
<div className="accordion">
{items.map((item, i) => {
return <AccordionItem key={i} item={item} index={i} />;
})}
</div>
);
};
export default Accordion;
And here is a codepen illustrating the problem:
https://codesandbox.io/s/heuristic-heyrovsky-xgcbe
of course, its going to re-render. When ever you call setSelectedIem, state changes and hence react re-renders on state change to exhibit that change.
Now if you place this
const [selectedItem, setSelectedItem] = useState(0);
inside Accordion Item, it would just re-render accordion item, but would mess up your functionality.