React doesnt update state immidiately after dropdown selected - reactjs

I have some update state problems when the dropdown is selected.
The useEffect doesnt update the state immidiately when its selected.
I want my listngs array updated immidiately whenever the selected filter changes.
Thanks
Code
const dropdowns = [
{ icon: '', title: 'Top ratings', color: 'warning' },
{ icon: '', title: 'Worst ratings', color: 'warning' },
]
const [selectedFilter, setSelectedFilter] = useState("")
useEffect(() => {
if (selectedFilter === 0) {
var sorted = listings.sort((a, b) => { return b.rating - a.rating })
setListings(sorted)
console.log(listings)
} else {
var sorted = listings.sort((a, b) => { return a.rating - b.rating })
setListings(sorted)
console.log(listings)
}
}, [selectedFilter])
<Dropdown.Menu className='w-100'>
{dropdowns.map(({ icon, title }, indx) => (
<Dropdown.Item
key={indx}
as='button'
eventKey={title}
onClick={() => {
setSelectedFilter(indx)
}}>
<i className={`${icon} fs-lg opacity-60 me-2`}></i>
{title}
</Dropdown.Item>
))}
</Dropdown.Menu>

You probably will find it useful to read through the "You might not need an effect", in particular the "Updating state based on props or state" section.
Ultimately you don't need an effect to compute derived data, you should just do that directly in render.
const dropdowns = [
{ icon: '', title: 'Top ratings', color: 'warning' },
{ icon: '', title: 'Worst ratings', color: 'warning' },
]
const [selectedFilter, setSelectedFilter] = useState("")
const sortedListings = selectedFilter === 0 ?
[...listings].sort((a, b) => b.rating - a.rating) :
[...listings].sort((a, b) => a.rating - b.rating);
<Dropdown.Menu className='w-100'>
{dropdowns.map(({ icon, title }, indx) => (
<Dropdown.Item
key={indx}
as='button'
eventKey={title}
onClick={() => {
setSelectedFilter(indx)
}}>
<i className={`${icon} fs-lg opacity-60 me-2`}></i>
{title}
</Dropdown.Item>
))}
</Dropdown.Menu>
The other thing worth noting is in your useEffect you're mutating listings which is most likely not desirable; instead you should clone the array then sort (hence [...listings].sort() rather than listings.sort()).

Related

Updating array of objects by using useState hook in reactjs

I have this state of array of object. I use them to create three cards.
const [option, setOption] = useState([{
id: 1,
label: "Borrowers",
icon: FcApprove,
counter: 2,
link: "borrowers",
color: "#5d8c6d",
isActive: false,
},
{
id: 2,
label: "My Offer",
icon: FaHandsHelping,
counter: 2,
link: "offer",
color: "#04738a",
isActive: false,
},
{
id: 3,
label: "Promise",
icon: FaPrayingHands,
counter: 2,
link: "promise",
color: "#40437a",
isActive: false,
}
]);
Whenever one of these cards is clicked, I would like to update the field isActive to true as follows.
function FixedHeader() {
const [options, setOptions] = useState(option); //option is the above mentioned array of object
const handleChange = (opt) => {
setOption([option[index].isActive: true])
}
return < > {
options.map((opt, index) => {
<Card onClick={handleChange(opt,index)} key={index} className={opt.isActive ? "activecard": ""}>
<CardContent>
<Stack direction="column">
<opt.icon size={20}/>
<Typography>opt.label</Typography>
</Stack>
</CardContent>
</Card>
})
}
My code somewhat looks the above one. but it is not literally changing isActive filed
You are mutating the options array. When you want to update an array stored in state, you need to create a new one (or make a copy of an existing one), and then set state to use the new array.
Below will fix your issue
const handleChange = (options, index) => {
setOption(options.map((option, i) => ({...option, isActive: i === index }));
}
options is an array. You will want to map the previous state array to the next state array, updating the specific element object by the id property.
const [options, setOptions] = useState(option);
// Curried function to close over option id in callback scope
const handleChange = (id) => () => {
setOption(options => options.map(option => option.id === id
? { ...option, isActive: true }
: option
);
}
options.map((opt) => (
<Card
key={opt.id}
onClick={handleChange(opt.id)}
className={opt.isActive ? "activecard" : ""}
>
<CardContent>
<Stack direction="column">
<opt.icon size={20} />
<Typography>opt.label</Typography>
</Stack>
</CardContent>
</Card>
)
There are several issues going on here, the first being you should be using the useCallback hook when you are updating anything set in useState:
export default function FixedHeader() {
const [options, setOptions] = useState(myOptions);
const onClick = useCallback((id) => {
setOptions(options.map((opt) => {
if (opt.id === id) {
opt.isActive = !opt.isActive
}
return opt
})
)}, [options])
return (
<div>
{
options.map((option) => (
<div style={{ backgroundColor: option.isActive ? 'blue' : 'red' }}>
<button onClick={() => onClick(option.id)}>{option.label}</button>
</div>
))
}
</div>
);
}
Note that I was rendering something different than you to test this out, so make sure instead of passing in the index you pass the option.id like so:
onClick={() => onClick(option.id)}

Can I change a element state in react without changing every element state?

im making a portfolio website and have multiple different buttons with skills which contain an img and p tag. I want to show the description of each tag everytime a user clicks on the button. how can I do this? right now everytime user clicks it, all buttons show description.
const Skills = () => {
const [state, setState] = useState(false)
let skills = [
{ id: 1, desc: 'HTML5', state: false, img: htmlIcon },
{ id: 2, desc: 'CSS3', state: false, img: cssIcon },
{ etc....}
const showDesc = (id) => {
console.log(skills[id-1] = !state);
setState(!state)
}
return (
{skills.map(skill => (
<button onClick={(id) => showDesc(skill.id)}>
<img style={ state ? {display:'none'} : {display:'block'}} src={skill.img} />
<p style={ state ? {display:'block'} : {display:'none'}}>{skill.desc}</p>
</button>
))}
I recommend to manipulate element state instead of entry list. But if you really need to manipulate entry list you should add that list to your state. Then when you want to show/hide specific item, you need to find that item in state and correctly update entry list by making a copy of that list (with updated item). For example you can do it like this:
import React, { useState } from 'react';
const Skills = () => {
const [skills, setSkills] = useState([
{
id: 1,
desc: 'HTML5',
state: false,
img: htmlIcon, // your icon
},
{
id: 2,
desc: 'CSS3',
state: false,
img: cssIcon, // your icon
},
]);
const showDesc = (id) => {
const newSkills = skills.map((item) => {
if (item.id === id) {
return {
...item,
state: !item.state,
};
}
return item;
});
setSkills(newSkills);
};
return (
<div>
{skills.map(({
id,
img,
state,
desc,
}) => (
<button type="button" key={id} onClick={() => showDesc(id)}>
<img alt="img" style={state ? { display: 'none' } : { display: 'block' }} src={img} />
<p style={state ? { display: 'block' } : { display: 'none' }}>{desc}</p>
</button>
))}
</div>
);
};
Instead of manipulating all list, you can try to move show/hide visibility to list item itself. Create separate component for item and separate component for rendering that items. It will help you to simplify logic and make individual component responsible for it visibility.
About list rendering you can read more here
For example you can try something like this as alternative:
import React, { useState } from 'react';
const skills = [
{
id: 1,
desc: 'HTML5',
img: htmlIcon, // your icon
},
{
id: 2,
desc: 'CSS3',
img: cssIcon, // your icon
},
];
const SkillItem = ({
img,
desc = '',
}) => {
const [visibility, setVisibility] = useState(false);
const toggleVisibility = () => {
setVisibility(!visibility);
};
const content = visibility
? <p>{desc}</p>
: <img alt="img" src={img} />;
return (
<div>
<button type="button" onClick={toggleVisibility}>
{content}
</button>
</div>
);
};
const SkillList = () => skills.map(({
id,
img,
desc,
}) => <SkillItem img={img} desc={desc} key={id} />);

How do I disable a button in react hooks that has a setState function already inside the onClick?

I am trying to disable a like button that already has an useState for incrementing the likes. And I wanted to disable the button once its clicked.
I would appreciate some help. Thank You!
const allItems = [
{
id: 1,
name: "The Rise and Decline of Patriarchal Systems",
image: "https://img.thriftbooks.com/api/images/i/m/8ECA8C9BAF351D13622ADFFBFA8A5D4E2BAABAFF.jpg",
likes: 3359,
price: 1
}
]
const Shop = () => {
const [items, setItems] = React.useState({allItems, disable: false})
const updateLike = (item) => setItems(items.map(indItem => {
if (indItem !== item) {
return indItem
}
else {
return {...item, likes: item.likes + 1}
}
}))
const listItemsToBuy = () => items.map((item) => (
<div key={item.id} className="card"></div>
<div className="font-text"><h2>{`${item.name}`}</h2>
</div>
<h2>Likes: {item.likes}</h2>
<div>
<button items={items} onClick={()=> updateLike(item, true)}> Like </button>
));```
Inside updateLike function update your state
setItems(prevState => { ...prevState, disable: true, })
Then your button will be look like
<button disabled={items.disabled} >
But preferable to have separate state for this purpose

Toggle issue in React Js using Hooks

I am trying to implement toggle functionality, by using this functionality user can select desired single preference, and also the user can select all preferences by using the "Select All" button. I have implemented the code that is supporting a single selection I want to make select all functionality.
This is how i am handling toggle
const toggleItem = useCallback((isToggled, value) => {
if (isToggled) {
setToggledItems((prevState) => [...prevState, value]);
} else {
setToggledItems((prevState) => [...prevState.filter((item) => item !== value)]);
}
}, []);
const [toggledItems, setToggledItems] = useState([]);
var eventsnfo = [
{
icon: '',
title: 'select All',
subTitle: '',
},
{
icon: 'event1',
title: 'event1',
subTitle: 'event1desc',
},
{
icon: 'event2',
title: 'event2',
subTitle: 'event2desc',
},
{
icon: 'event3',
title: 'event3',
subTitle: 'event3desc',
},
{
icon: 'event4',
title: 'event4',
subTitle: 'event4desc',
},
];
this is how i am loading all toggle sections
<div>
{eventsnfo?.map(({ icon, title, subTitle }, index) => {
return (
<>
<div key={index} className='events__firstSection'>
<div className='events__agreeToAllContainer'>
{icon && (
<Icon name={icon} className='events__noticeIcon' isForceDarkMode />
)}
<div className={icon ? 'events__text' : 'events__text events__leftAlign '}>
{title}
</div>
</div>
<Toggle
containerClass='events__toggle'
checked={toggledItems.includes(title)}
onToggle={(isToggled) => toggleItem(isToggled, title)}
/>
</div>
{subTitle && <div className='events__description'>{subTitle}</div>}
<div className={index !== eventsnfo.length - 1 && 'events__divider'}></div>
</>
);
})}
</div>;
I think you can toggle all by changing your toggleItem function
const toggleItem = (isToggled, value) => {
let items = [...toggledItems];
if (isToggled) {
items =
value === "select All"
? eventsnfo?.map((events) => events.title)
: [...items, value];
if (items?.length === eventsnfo?.length - 1) {
items.push("select All");
}
} else {
items =
value === "select All"
? []
: [...items.filter((item) => item !== value && item !== "select All")];
}
setToggledItems(items);
};
Working Demo

React Js / Menu with menu item drop down and menu item active State

hi i have a menu with tag structure < li> < a> and some of these menu items will be dropdown
then I created a state array to store all my menu items and when one is clicked it needs to be true and all the others are false, that is, in this array only one item can be true, with that I can show my dropdown item or even even apply a css effect to my active item, but for some reason my state never changes to true when clicked:
code:
const MenuItem = ({ tag, visibleMenu }) => {
const { name, link, dropdownItems } = tag;
return (
<NavLi>
<a>{name}</a>
{visibleMenu[name] && dropdownItems ? (
<DropDown>
{dropdownItems.map(item => (
<li>
<a href={item.link}>{item.name}</a>
</li>
))}
</DropDown>
) : (
""
)}
</NavLi>
);
};
const MenuBar = props => {
const MenuTags = [
{
name: "home",
link: "/",
dropdownItems: [
{ name: "one", link: "/aa" },
{ name: "two", link: "/b/" }
]
},
{
name: "about",
link: "../abovisibleMenuut",
dropdownItems: [
{ name: "one", link: "/aa" },
{ name: "two", link: "/b/" }
]
},
{ name: "not dropdown", link: "../dashboard" },
{ name: "not dropdown", link: "../dashboard/about" }
];
const [visibleMenu, setVisibleMenu] = useState(
MenuTags.reduce((r, e) => ((r[e.name] = false), r), {})
),
onUpdateVisibility = item => {
const visibleMenuCopy = { ...visibleMenu };
Object.keys(visibleMenuCopy).forEach(
key => (visibleMenuCopy[key] = key === item)
);
setVisibleMenu(visibleMenuCopy);
};
console.log(visibleMenu);
return (
<NavUl isOpen={props.isOpen}>
{MenuTags.map(item => (
<MenuItem
tag={item}
visibleMenu={visibleMenu}
onClick={() => onUpdateVisibility(item)}
/>
))}
<li>
<FontAwesomeIcon
onClick={() => props.setOpenBox(!props.isOpen)}
className="searchIcon"
rotation={90}
icon={faSearch}
size="1x"
fixedWidth
color="rgba(0, 0, 0, 0.08);"
/>
</li>
</NavUl>
);
};
example:
https://codesandbox.io/s/lucid-sammet-9qim9
I believe my problem is in my state:
const [visibleMenu, setVisibleMenu] = useState(
MenuTags.reduce((r, e) => ((r[e.name] = false), r), {}),
),
onUpdateVisibility = item => {
const visibleMenuCopy = { ...visibleMenu };
Object.keys(visibleMenuCopy).forEach(
key => (visibleMenuCopy[key] = key === item),
);
setVisibleMenu(visibleMenuCopy);
};
{MenuTags.map(item => (
<MenuItem
tag={item}
visibleMenu={visibleMenu}
onClick={() => onUpdateVisibility(item)}
/>
))}
But it was the only way I found to make only one state true at a time when it is clicked
The issue was that the event handler was on a component that didn't return any concrete HTML elements. MenuTag returns NavLI, which is a styled component, which returns an li. I'm no master of styles components, but I think in general, event handlers need to be attached to DOM elements, not abstract React components.
Also, I think you can simplify this dramatically. What about this:
const [visible, setVisible] = useState('none'),
{MenuTags.map( (item, index) => (
<MenuItem
key={index}
tag={item}
visibleMenu={visible === index ? true : false}
onClick={() => setVisible(index)}
/>
))}
So what this does is say that within this component, we keep track of which item is visible with the visible state variable, which will be the index of the visible item. The onClick handler changes this value to the index of the clicked item. Then your prop visibleMenu will check this condition, making sure the index of that MenuTag is the same as the one in state.
Edit
I changed a few things. Instead of controlling the state from within MenuItem, we just pass a few props to MenuItem, and control them inside of MenuItem's subcomponent, NavLI, which accepts event handlers
const MenuBar = props => {
const [visible, setVisible] = useState('none')
console.log(visible);
return (
<NavUl isOpen={props.isOpen}>
{MenuTags.map( (item, index) => (
<MenuItem
key={index}
index={index}
tag={item}
setVisible={setVisible}
visibleMenu={visible === index ? true : false}
/>
))}
// Inside NavLI
const MenuItem = ({ tag, visibleMenu, setVisible, index }) => {
...
return (
<NavLi onMouseEnter={ () => { console.log(index); setVisible(index) }}
onMouseLeave={() => { setVisible('none')}} >
...
I changed the handlers to onMouseEnter and onMouseLeave. onClick will work, but you'd need a global event handler for the page to setVisible back to 'none', so that when you click anywhere else on the page, the tabs close. There's more nuance there than that, but my example should work.
Working Codesandbox

Resources