Javascript loop with dynamic react component handler binding - javascript

I am new to Javascript/ReactJs world. I am building a side nav. Below are some part of my code -
import React, {Component} from "react";
import Link from "./Link/Link";
class Sidebar extends Component {
state = {
dashboard: {
name: "Dashboard",
href: "#",
active: true,
svgPath: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6",
children: []
},
team: {
name: "Team",
href: "#",
active: false,
svgPath: "M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z",
children: [
{
name: "Overflow",
href: "#",
},
{
name: "Members",
href: "#",
},
{
name: "Calender",
href: "#",
},
{
name: "Settings",
href: "#",
}
]
}
}
navClickHandler = (e, key) => {
console.log(e, key)
}
render() {
let navLinks = [];
for (const item in this.state) {
navLinks.push(<Link key={item} navClicked={(e, item) => this.navClickHandler(e, item)} config={this.state[item]} />)
}
return (
<div className="hidden bg-indigo-700 md:flex md:flex-shrink-0">
<div className="flex flex-col w-64 border-r border-gray-200 pt-5 pb-4 bg-white overflow-y-auto">
<div className="flex items-center flex-shrink-0 px-4">
<img className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-indigo-600-mark-gray-800-text.svg"
alt="Workflow" />
</div>
<div className="mt-5 flex-grow flex flex-col">
<nav className="flex-1 px-2 space-y-1 bg-white" aria-label="Sidebar">
{navLinks}
</nav>
</div>
</div>
</div>
);
}
}
export default Sidebar;
How can I get the item value inside the navClickedHandler function -
let navLinks = [];
for (const item in this.state) {
navLinks.push(<Link key={item} navClicked={(e, item) => this.navClickHandler(e, item)} config={this.state[item]} />)
}
Actually, I want to use that key(item) to check which of the side-nav is active. It will change the state object based on the click.
console.log in nacClickHandler is giving undefined.

You are not passing item to Link, so you cannot get it as an argument back from the click event.
So change this:
navClicked={(e, item) => this.navClickHandler(e, item)}
to this:
navClicked={e => this.navClickHandler(e, item)}
Or, you can change your <Link/> component to something like this:
const Link = ({navClicked, item}) => (
<div onClick={e => navClicked(e, item)}></div>
);
And pass item as a prop:
<Link key={item} item={item} navClicked={this.navClickHandler} config={this.state[item]} />
The second option is preferable since it allows you to pass the same function reference always, thus avoiding redundant renders (provided that Link is a PureComponent, or a functional component wrapped in memo)

Hello other problems I see is that key={item} will be translated to key=[object Object] therefore your behaviour will fail due to the check key1 !== key2 because the actual value will be [object Object] !=== [object Object] which fails.
An alternative I would suggest to either use some unique data item.id for instance or generate some using a basic package like uuid
Back to your issue, in order to check which item is active I would suggest to you to do this:
<Link key={item.id} .... active={state.selected.id === item.id}/>

Related

React How to Individually Target li State

I am somewhat new to react and I am building a navigation menu where I target the ul and li components using the map() function with index to avoid duplicating code. Whenever I click to open one component all components open instead of an individual one. I know the issue is probably in me not targeting the components correctly so any help will be appreciated.
Here is the code:
...react icon and state imports
const Navbar = () => {
const [subMenuOpen, setSubmenuOpen] = useState(false);
const menu = [
{
title: "Management",
submenu: true,
icon: <FaUserTie/>,
submenuItems: [
{ title: "MGMT Cockpit" },
{ title: "P&L by Month" },
{ title: "B/O Report" },
{ title: "User List" },
],
},
{
title: "Tools",
submenu: true,
icon: <BsTools/>,
submenuItems: [
{ title: "Inventory" },
{ title: "Damages" },
{ title: "MGMGT Tools" },
{ title: "Plan Tools" },
{ title: "Sales Tools" },
{ title: "Planning Tools" },
]
},
];
return (
<div className="flex">
<div className="bg-primary h-screen w-64 p-3 relative overflow-y-scroll">
{menu.map((item, index) => <sidebarItem key={index} sidebarItem = {index}/>)}
<ul className="pt-2">
{menu.map((menu, index) => (
<>
<li className="
text-gray-300 text-lg flex item-center gap-x-4 cursor-pointer p-2 my-4
hover:bg-slate-50 hover:text-primary hover:rounded-lg duration-500"
onClick={() => setSubmenuOpen(!subMenuOpen)}> //Fix this???
<span className="text-2xl block float-left">
{menu.icon ? menu.icon : <RiDashboardFill/>}
</span>
<span className="text-base font-medium flex-1">{menu.title}</span>
{menu.submenu && (
<BsChevronDown className={`${subMenuOpen && "rotate-180"} duration-300`}/>
)}
</li>
{menu.submenu && subMenuOpen && (
<ul>
{menu.submenuItems.map((submenuItem, index) => (
<li key={index} className="text-gray-300 text-md gap-x-4 px-5 my-3">
{submenuItem.title}
</li>
))}
</ul>
)}
</>
))}
</ul>
</div>
</div>
)
}
export default Navbar
App.js just imports this Navbar so I didn't include the code for that.
You have different possibilities of open submenus (since you have multiple menu types), so you should have state that reflects that. A plain true/false state won't be able to store enough information. One approach would be an array of submenu indices that are open.
const [submenuIndicesOpen, setSubmenuIndicesOpen] = useState([]);
Then examine the indices when iterating to determine what should be shown and how to call setSubmenuItemsOpen when toggling.
<ul className="pt-2">
{menu.map((menuItem, index) => ( /* don't shadow the menu variable here */
<>
<li className="
text-gray-300 text-lg flex item-center gap-x-4 cursor-pointer p-2 my-4
hover:bg-slate-50 hover:text-primary hover:rounded-lg duration-500"
onClick={() => setSubmenuOpen(
submenuIndicesOpen.includes(index)
? submenuIndicesOpen.filter(i => i !== index)
: [...submenuIndicesOpen, index]
)}>
{menuItem.submenu && submenuIndicesOpen.includes(index) && (
If only one submenu can be open at a time, you could instead have a state that's just a single number, the index of the open submenu.
your subMenuOpen state should be an array with index and in onClick handler you should pass index of li that you have in map , to control which menu should be open
or
you can set your menu as a state with a property of open on each submenu to control open of it

Svelte click handler toggle boolean in array from div

The toggle works only if clicking on a button, ignores the div, seems button triggers some sort of state change, how do I get this to work when clicking anywhere ?
ToolBar.ts
export default class ToolBar {
options:Array<ToolBarOptions>;
constructor() {
this.options = [
new ToolBarOptions(ToolButton.sideMenu,SideMenuIcon,false,true, []),
new ToolBarOptions(ToolButton.mainMenu,MainMenuIcon,false,true, [ new ToolBarOptions(ToolButton.export,exportIcon,true,true,[])]),
new ToolBarOptions(ToolButton.entities,EntityIcon,false,true,[]),
new ToolBarOptions(ToolButton.setting,settingsIcon,false,true,[]),
];
}
}
class ToolBarOptions {
disabled:boolean;
name:ToolButton;
icon:string;
show:boolean;
options:Array<ToolBarOptions>;
constructor(name: ToolButton,icon:string,disabled:boolean,show:boolean, options:Array<ToolBarOptions>) {
this.name = name;
this.disabled = disabled;
this.icon = icon;
this.show=show;
this.options=options;
}
}
export const enum ToolButton{
mainMenu="mainMenu",
export="export",
entities="entities",
sideMenu="sideMenu",
setting="setting",
}
App.svelte
let toolbarOptions = new ToolBar();
function handleClickOutSide() {
console.log(toolbarOptions.options)
toolbarOptions.options.forEach((o) => {
o.show=!o.show;
});
console.log(toolbarOptions.options)
<div on:click={handleClickOutSide } class="toolbar">
<ul class="">
{#each toolbarOptions.options as {name, icon,options, show }, i}
<li>
<button on:click={()=>show=!show} name={name} class="flex items-center justify-center relative {toolbarOptions.options.length-1 === i ? "h-10":""}">
{#if toolbarOptions.options.length-1 ===i}
<div>100%</div>
{/if}
<icon> {#html icon}</icon>
<span>
<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
</span>
{#if options.length >0 }
<div class="absolute top-10 w-32 bg-black h-10 cursor-pointer {show ? "hidden":""}">
<ul class="w-full flex">
{#each options as {name, icon,show }}
<li class="min-w-full flex items-center h-10 px-2">
<span class=""> {#html icon} </span>
<span class="left-4 w-1/2"> {name}</span>
</li>
{/each}
</ul>
</div>
{/if}
</button>
</li>
{/each}
</ul>
</div>
When interacting with each item in a list there are several options that take reactivity into account:
Use item index access
Map all items
Use a dummy assignment
Examples for each:
let items = [
{ name: 'Item 1', checked: false },
{ name: 'Item 2', checked: false },
{ name: 'Item 3', checked: false },
];
const toggleViaIndex = () =>
items.forEach((e, i) => items[i].checked = !items[i].checked);
const toggleViaMap = () =>
items = items.map(item => ({ ...item, checked: !item.checked }));
const toggleViaDummyAssignment = () => {
items.forEach(item => item.checked = !item.checked);
items = items;
}
REPL
Personally, I am not a fan of the dummy assignment, because it looks like a useless statement. You can of course add comments to make it clearer why the statement exists.
I would not recommend using classes unless necessary, by the way. It breaks things like the map approach, if the class defines any functions or the prototype matters, because those aspects get lost in the spread.
Also: click events on things that are not button elements should always have a keyboard equivalent for accessibility; in this case probably an escape press.
You also probably should just set show to false rather than invert it on click outside.
By the way, ToolBarOptions could be shortened significantly via TypeScript:
class ToolBarOptions {
constructor(
public name: ToolButton,
public icon: string,
public disabled: boolean,
public show: boolean,
public options: Array<ToolBarOptions>,
) { }
}

filtering out users in react

so im new to react and there is a book and a task given i need to filter out users by the upvotes the one user with most upvotes must be on the top but i have no idea how to do it also when i press on one user the state of upvote changes on all users can anyone help ? what am i doing wrong or am i doing everything wrong
import React from 'react';
import Nav from './Nav';
import './App.css';
import react,{Component} from'react'
const Items = [
{
img: "https://pbs.twimg.com/profile_images/1219033860991848448/UKWAPwfG_400x400.jpg",
header:"Netlify, our Conversion from Angular to React",
website:"netlify.com",
timeAuthor:"Submitted 9 hours ago by brianlammar",
},
{
img:"https://pbs.twimg.com/profile_images/1825094360/random_dude_400x400.jpg",
header:"React in patterns - List of design patterns ragaca",
website:"github.com",
timeAuthor:"Submitted 9 hours ago by magenta_placenta",
},
{
img:"https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/c8366146-25b7-49b3-a640-58439d2a2baa/d5gs9sv-0c98ab64-0f32-4c6d-90ed-39d38d2bf0ba.jpg/v1/fill/w_900,h_675,q_75,strp/random_dude_who_lives_near_me_by_misa_amane_17_d5gs9sv-fullview.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9Njc1IiwicGF0aCI6IlwvZlwvYzgzNjYxNDYtMjViNy00OWIzLWE2NDAtNTg0MzlkMmEyYmFhXC9kNWdzOXN2LTBjOThhYjY0LTBmMzItNGM2ZC05MGVkLTM5ZDM4ZDJiZjBiYS5qcGciLCJ3aWR0aCI6Ijw9OTAwIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.YP5o5wapk-q4-6vpQIKaERchdyvNl8MOAs_cbG7ThfU",
header:"Redux vs Mobx vs Flux vs... Do you even...",
website:"goshakkk.name",
timeAuthor:"Submitted 8 hours ago by goshakk",
}
]
class App extends Component{
constructor(props){
super(props)
this.state= {
count:0
}
}
incremento(){
this.setState({
count:this.state.count + 1
})
}
decremento(){
this.setState({
count:this.state.count -1
})
}
render(){
return (
Items.map(item =>{
return (
<div>
<div className='section'>
<span className='Votes'>
<i onClick={() => this.incremento()} className="fas fa-arrow-up"></i>
<p>{this.state.count}</p>
<i onClick={() => this.decremento()} className="fas fa-arrow-down"></i>
</span>
<img src={item.img} />
<div className='Content'>
<h1 className='h'>{item.header}</h1>
<p>{item.website}</p>
<p>{item.timeAuthor}</p>
<span className='lil'>
<p className='red'>10 Comments</p>
<p>share</p>
<p>save</p>
<p>hide</p>
<p>report</p>
<p>pocket</p>
</span>
</div>
</div>
</div>
)
})
)
}
}
export default App;
It should look like this:
https://codesandbox.io/s/gracious-glitter-jw34l?file=/src/App.js
import React { useState } from "react";
import Nav from './Nav';
import './App.css';
const Items = [
{
id: 1,
count: 0,
img:
"https://pbs.twimg.com/profile_images/1219033860991848448/UKWAPwfG_400x400.jpg",
header: "Netlify, our Conversion from Angular to React",
website: "netlify.com",
timeAuthor: "Submitted 9 hours ago by brianlammar"
},
{
id: 2,
count: 0,
img:
"https://pbs.twimg.com/profile_images/1825094360/random_dude_400x400.jpg",
header: "React in patterns - List of design patterns ragaca",
website: "github.com",
timeAuthor: "Submitted 9 hours ago by magenta_placenta"
},
{
id: 3,
count: 0,
img:
"https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/c8366146-25b7-49b3-a640-58439d2a2baa/d5gs9sv-0c98ab64-0f32-4c6d-90ed-39d38d2bf0ba.jpg/v1/fill/w_900,h_675,q_75,strp/random_dude_who_lives_near_me_by_misa_amane_17_d5gs9sv-fullview.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9Njc1IiwicGF0aCI6IlwvZlwvYzgzNjYxNDYtMjViNy00OWIzLWE2NDAtNTg0MzlkMmEyYmFhXC9kNWdzOXN2LTBjOThhYjY0LTBmMzItNGM2ZC05MGVkLTM5ZDM4ZDJiZjBiYS5qcGciLCJ3aWR0aCI6Ijw9OTAwIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.YP5o5wapk-q4-6vpQIKaERchdyvNl8MOAs_cbG7ThfU",
header: "Redux vs Mobx vs Flux vs... Do you even...",
website: "goshakkk.name",
timeAuthor: "Submitted 8 hours ago by goshakk"
}
],
User = ({ counterChange, item }) => (
<div>
<div className="section">
<span className="Votes">
<i
onClick={() => {
counterChange(1);
}}
className="fas fa-arrow-up"
>
UP
</i>
<p>{item.count}</p>
<i
onClick={() => {
counterChange(-1);
}}
className="fas fa-arrow-down"
>
DOWN
</i>
</span>
<img src={item.img} />
<div className="Content">
<h1 className="h">{item.header}</h1>
<p>{item.website}</p>
<p>{item.timeAuthor}</p>
<span className="lil">
<p className="red">10 Comments</p>
</span>
</div>
</div>
</div>
);
const App = () => {
const [items, setItems] = useState(Items);
return (
<>
{items
.sort((a, b) => (a.count < b.count ? 1 : -1))
.map((item) => (
<User
item={item}
counterChange={(value) => {
const index = items.findIndex((d) => d.id === item.id),
a = [...items];
a[index].count = a[index].count + value;
setItems(a);
}}
/>
))}
;
</>
);
};
export default App;
The count is increasing for each user because, the count state that you are maintaining is not specific to a user. It is a general, common counter.
What you could instead do is,
Get rid of the count state.
Add a users state, which will be an array of users, with a count for each user. Something like:
this.state = Items.map(i => ({...i, count: 0}))
Pass an id to the incremento(id) and decremento(id) methods.
This way now, you can update the count of only the user for which the up/down button was clicked inside your incremento() and decremento() methods
And in the render method, you can sort the Items array by the count field, and then render each user.
This way, you can have the count increased only for the clicked user, and users sorted by counts in descending order.

ReactJS: Unable to read event.target.value while creating a select menu

I am trying to create a custom menu select for my Algolia search based on the documentation here with a barebones example. I am using Tailwind and Headless UI for my component styling.
More specifically, I am trying to make this work with the ListBox component by Headless UI, which is documented here. My code is below:
import { connectMenu } from "react-instantsearch-dom";
import React, { useState, Fragment, useEffect } from "react";
import { Listbox, Transition } from "#headlessui/react";
import { CheckIcon, SelectorIcon } from "#heroicons/react/solid";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
const MobileDropdownS = ({ label, currentRefinement, refine, hide }) => {
const [selected, setSelected] = useState(true);
const items= [
{ value: 'category', label: 'Category' },
{ value: 'brand', label: 'Brand' },
{ value: 'color', label: 'Color' },
{ value: 'size', label: 'Size' },
]
if (hide) return null;
return (
<Listbox
onChange={(event) => {
refine(event.target.value);
setSelected(event.target.value);
}}
value={currentRefinement || ""}
>
{({ open }) => (
<div>
<Listbox.Label>{label}</Listbox.Label>
<div>
<Listbox.Button>
<span>{label}</span>
</Listbox.Button>
<Listbox.Options>
{items.map((item) => (
<Listbox.Option
key={item.label}
value={item.isRefined ? currentRefinement : item.value}
key={item.label}
className={({ active }) =>
classNames(
active ? "text-white bg-indigo-600" : "text-gray-900",
"cursor-default select-none relative py-2 pl-3 pr-9"
)
}
>
{({ selected, active }) => (
<>
<div className="flex items-center">
<span
className={classNames(
selected ? "font-semibold" : "font-normal",
"ml-3 block truncate"
)}
>
{item.label}
</span>
</div>
{selected ? (
<span
className={classNames(
active ? "text-white" : "text-indigo-600",
"absolute inset-y-0 right-0 flex items-center pr-4"
)}
>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</div>
</div>
)}
</Listbox>
);
};
const MobileDropdownSelect = connectMenu(MobileDropdownS);
export default MobileDropdownSelect;
The way I understand it, ListBox becomes the <select> tag from Algolia's documentation, where I should add this:
refine(event.target.value);
setSelected(event.target.value);
However, this gives me the following error:
×
TypeError: Cannot read properties of undefined (reading 'value')
onChange
D:/Gatsby/XTheme/src/components/search/MobileDropdown.jsx:22
19 | <Listbox
20 | onChange={
21 | event => {
> 22 | refine(event.target.value)
23 | setSelected(event.target.value)
24 | }
25 | }
Is this not correct? How do I fix this error and port over Algolia's example with the <select> input to the <ListBox> component such that when I select an item from the Listbox, it refines the search based on the item?
Tru using event.currentTarget.value in place of event.target.value . You can find difference between both in this link

React is duplicating my object value giving me a Warning: Each child in a list should have a unique "key" prop

Im trying to make a navigation bar for a website and it's giving me the "Warning: Each child in a list should have a unique "key" prop." inside my props.dropList.map
I have two files:
NavigationItems.js -> where I render my navigation bar
const NavigationItems = () => {
const projectDropdown = [
{ id: 0, value: "architecture" },
{ id: 1, value: "land" },
{ id: 2, value: "design" },
{ id: 3, value: "list" },
];
const officeDropdown = [
{ id: 4, value: "contact" },
{ id: 5, value: "team" },
];
return (
<div>
<ul className={styles.NavigationItems}>
<NavigationItem
link={`/projects`}
name="projects"
dropList={projectDropdown}
/>
<NavigationItem link={`/news`} name="news" exact />
<NavigationItem
link={`/office`}
name="office"
dropList={officeDropdown}
/>
</ul>
</div>
);
};
export default NavigationItems;
NavigationItem.js -> where I use the map function
const NavigationItem = (props) => {
let i = 0;
return (
<li className={styles.NavigationItem}>
<NavLink to={props.link} activeClassName={styles.active}>
{props.name}
</NavLink>
{props.dropList && (
<div className={styles.DropdownItems}>
<ul className={styles.DropdownItem}>
{props.dropList.map((drop) => {
console.log("i " + i);
console.log("id " + drop.id);
console.log("value " + drop.value);
i++;
return (
<li key={drop.id}>
<NavLink
exact
to={`${props.link}/${drop.value}`}
activeClassName={styles.active}
>
{drop.value}
</NavLink>
</li>
);
})}
</ul>
</div>
)}
</li>
);
};
export default NavigationItem;
So what happens is that the code loops twice duplicating the key values. It should be looping only once. I don't know why it loops twice, I'm only mapping my values once. For reference
this is what my console shows when I click my links
So your problem doesn't occure in either of the components you provided, but in your "Land" component. (Check the render method of Land)

Categories