combining useState with pop() - arrays

I'm trying to update my list using the pop() method.
But the page doesn't render, and I couldn't find the issue.
I would appreciate any help.
this is my Code
import classes from "./Navigation.module.scss";
import { useState } from "react";
function Navigation(props) {
const [navItems, setNavItems] = useState([
{ id: 0, link: "HOME" },
{ id: 1, link: "ABOUT" },
{ id: 2, link: "PORTFOLIO" },
{ id: 3, link: "MUSIC" },
{ id: 4, link: "CONTACT" },
]);
const nextHandler = () => {
let a = navItems;
a.pop();
return setNavItems(a);
};
return (
<div className={classes.wrapper}>
<div onClick={nextHandler} className={classes.nextButton}>
NEXT
<div>
<div className={classes.listWrapper}>
<div
className={classes.container}
>
<ul>
{navItems.map((item) => {
return <li key={item.id}>{item.link}</li>;
})}
</ul>
<div>
<div>
</div>
);
}
export default Navigation;
So basically, I want to remove the last item when I click the button and want to update the list.

Try it like this:
const nextHandler = () => {
let a = [...navItems];
a.pop();
return setNavItems(a);
};
Otherwise you just copy the reference to the array to a and react will not know that the state has changed.

Related

Click and insert object in one array to another empty array using react hooks

I have array of objects (items) with button clickHandler function. When you click on button, it should add that object to new array named ‘myNewArray’. Please help me to achieve this. I added demo object inside array ‘myNewArray’.
Explaination: If i click on category button '1-furniture', that object will added to new array named 'myNewArray'
import React, { useState } from "react";
const App = () => {
const [items, setItems] = useState([
{ name: "Furniture", categoryKey: 1 },
{ name: "Shoes", categoryKey: 2 },
{ name: "Electronics", categoryKey: 3 },
{ name: "Clothes", categoryKey: 4 },
{ name: "Grocery", categoryKey: 5 },
]);
const [myNewArray, setMyNewArray] = useState([{ name: "demo-item", categoryKey: 100 }]);
const clickHandler = (categoryKey: any) => {
console.log(categoryKey);
};
return (
<div>
{items.map((item) => (
<button onClick={() => clickHandler(item.categoryKey)} key={item.categoryKey}>
{item.categoryKey}-{item.name}
</button>
))}
<h4>New array after clicking on item from above array[items]</h4>
{myNewArray.map((item) => (
<button onClick={() => clickHandler(item.categoryKey)} key={item.categoryKey}>
{item.categoryKey}-{item.name}
</button>
))}
</div>
);
};
export default App;
just use set method in the useState
const clickHandler = (item: any) => {
setMyNewArray(prev => [...prev, {name:item.name,categoryKey: item.categoryKey}])
};
and pass item in the click
onClick={() => clickHandler(item)}
Here's the working solution:
import React, { useState, useEffect } from "react";
const App = () => {
const [items, setItems] = useState([
{ name: "Furniture", categoryKey: 1 },
{ name: "Shoes", categoryKey: 2 },
{ name: "Electronics", categoryKey: 3 },
{ name: "Clothes", categoryKey: 4 },
{ name: "Grocery", categoryKey: 5 }
]);
const [myNewArray, setMyNewArray] = useState([
{ name: "demo-item", categoryKey: 100 }
]);
useEffect(() => {
console.log(myNewArray);
}, [myNewArray]);
const clickHandler = (item) => {
setMyNewArray([...myNewArray, item]);
};
return (
<div>
{items.map((item) => (
<button onClick={() => clickHandler(item)} key={item.categoryKey}>
{item.categoryKey}-{item.name}
</button>
))}
<h4>New array after clicking on item from above array[items]</h4>
{myNewArray.map((item, i) => (
<button key={i}>
{item.categoryKey}-{item.name}
</button>
))}
</div>
);
};
export default App;
The live demo is here: https://codesandbox.io/s/determined-solomon-phyy9u?file=/src/App.js:0-1163
You can have a look at the console to check the myNewArray updates.
You could also do like this.
if Item is with the matching category Key. Then, it's Update the myNewArray state with the new item
const clickHandler = (categoryKey: any) => {
const item = items.find((i) => i.categoryKey === categoryKey);
setMyNewArray([...myNewArray, item]);
};
Here is Codesandbox

React Hook useEffect has a missing dependency. Either include it or remove the dependency array react-hooks/exhaustive-deps

If i add the dependency array "fitems" in the dependecy array like its telling me to do, then it causes infinite loop. also if i dont use the spread operator on the array then the warning doesnt show but then the state change doesnt rerender.
Sidebar.tsx
import { useState, useEffect } from "react";
import { Link, useLocation } from "react-router-dom";
import axios from "axios";
import getItems from "./../services/SidebarItems";
import { sidebarInfoUrl } from "./../services/ApiLinks";
function Sidebar() {
const fItems = getItems();
const location = useLocation();
const paths = location.pathname.split("/");
const [items, setItems] = useState(fItems);
useEffect(() => {
axios.get(sidebarInfoUrl).then((response) => {
const updatedItems = [...fItems]
updatedItems.forEach((item) => {
if (item.match === "projects") item.value = response.data.projects;
else if (item.match === "contacts") item.value = response.data.contacts;
});
setItems(updatedItems);
console.log("here")
});
}, []);
return (
<div className="sidebar shadow">
{items &&
items.map((item) => (
<Link
key={item.match}
to={item.link}
className={
paths[2] === item.match ? "sidebar-item active" : "sidebar-item"
}
>
<span>
<i className={item.icon}></i> {item.title}
</span>
{item.value && <div className="pill">{item.value}</div>}
</Link>
))}
</div>
);
}
export default Sidebar;
Here is the sidebar items i am getting from getItems().
sidebarItems.ts
const items = () => {
return [
{
title: "Dashboard",
icon: "fas fa-home",
link: "/admin/dashboard",
match: "dashboard",
value: "",
},
{
title: "Main Page",
icon: "fas fa-star",
link: "/admin/main-page",
match: "main-page",
value: "",
},
{
title: "Projects",
icon: "fab fa-product-hunt",
link: "/admin/projects",
match: "projects",
value: "00",
},
{
title: "Contacts",
icon: "fas fa-envelope",
link: "/admin/contacts",
match: "contacts",
value: "00",
},
];
};
export default items;
Thank to AKX. I found my problem. I had to use useMemo Hook so that my getItem() function doesnt cause infinte loop when i add it to dependency array.
const fItems = useMemo(() => {
return getItems();
}, []);
instead of
const fItems = getItems();
Another fix is that,
If i dont send the items from SidebarItems.ts as function but as an array then it wont cause the infinte loop even if i dont use useMemo hook.

React.js: How to call hook first before first render?

I'm trying to create a dynamic menu using React
I have a JSON response which contains how my menu should look like:
[
{ id: 0,
label: "Dashboard",
link: "/app/dashboard",
icon: <HomeIcon /> },
{
id: 1,
label: "Inward",
link: "/app/inward",
icon: <InboxIcon />,
children: [
{ label: "PESONet", link: "/app/inward/pesonet" },
{ label: "PESONet Inquiry", link: "/app/inward/pesonetinquiry" },
{ label: "PDDTS", link: "/app/inward/pddts" },
{ label: "SWIFT", link: "/app/inward/swift" },
{ label: "Philpass", link: "/app/inward/philpass" },
],
}
]
I'm able to put this JSON response in the state with this:
Sidebar.js
const [sideBar, setSideBar] = useState([])
useEffect(() => {
const sidebar = customizeSidebar()
setSideBar(sidebar)
}, [])
The function customizeSidebar() can be found in my context:
UserContext.js
function customizeSidebar(dispatch, profileId, history){
ProfileMaintenanceService.retrieveSideMenu()
.then((response) => {
return response.data
}).catch((err) => {
// check first if api is down
})
}
As you can see, whenever I get a response, I return it as well.
Therefore, I can get it in the Sidebar.js.
However, problem arises when render happens first before the useEffect function.
const [sideBar, setSideBar] = useState([])
useEffect(() => {
const sidebar = customizeSidebar()
setSideBar(sidebar)
}, [])
return (
<List className={classes.sidebarList}>
{sideBar.map(link => (
<SidebarLink
key={link.id}
location={location}
isSidebarOpened={isSidebarOpened}
{...link}
/>
))}
</List>
)
Already tried using useLayoutEffect but render still happens first before my API call and assigning to state.
Is there any way I can do first my API call and assign to state before the first render?
Thank you for those who would help.
Either you return a placeholder is there is no sidebar data:
if (!sideBar.length) {
return (
<div className="sidebar-placeholder">
<Spinner/> || Loading text...
</div>
);
}
return (
<List>...
);
Or you wrap your Sidebar inside a provider component, and pass your sidebar configuration as a prop.
the best imo is to do something like this
return (
{sideBar.length&&<List className={classes.sidebarList}>
{sideBar.map(link => (
<SidebarLink
key={link.id}
location={location}
isSidebarOpened={isSidebarOpened}
{...link}
/>
))}
</List>}
)
So your sidebar will be rendered only when the sidebar data array is full.

Reactjs Jsx Conditional and useState logic

I'm trying to do the following
I have a dropdown menu where I created state to have only one menu item active at a time and I will also use this to make the active page effect
And then I need to do this check on my jsx
I move to my menu in all my states
I need to check the following:
const { name, link, dropdownItems } = tag;
if my name exists in my visibleMenu array
and if it is true and I also need to check if my dropdownItems is true for there yes render my dropdown menu, basically i'm a little confused on how to do these checks in jsx
code:
const MenuBar = props => {
const MenuTags = [
{
name: 'home',
link: '/',
dropdownItems: {
names: ['one', 'two', 'three'],
link: ['/aa', '/b'],
},
},
{
name: 'about',
link: '../abovisibleMenuut',
dropdownItems: {
names: ['one', 'two', 'three'],
link: ['/aa', '/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 (
<>
{MenuTags.map(item => (
<MenuItem
tag={item}
visibleMenu={visibleMenu}
onClick={() => onUpdateVisibility(item)}
/>
))}
</>
);
};
and this is my menu items: ( here i need jsx conditions )
const MenuItem = ({ tag, visibleMenu }) => {
const { name, link, dropdownItems } = tag;
console.log(visibleMenu);
return (
<NavLi>
<Link to={link}>{name}</Link>
</NavLi>
);
};
I don't know if this is the correct logic, but it was the only way I managed to get only one state of my array to be true at a time
to render my dropdown menu or apply a css to my active item
Hey I answered you other question didn't know you only wanted one visible menu but, I'll use the code I had yesterday, I still think you should change the way you are managing your visibleMenu.
Instead of setting the visible inside the MenuItem class you can set it on the MenuBar
const MenuBar = props => {
const menuTags = [
{ name: 'home', link: '/', dropdownItems: ['one', 'two', 'three'] },
// ... other menu tags you had.
]
const [visibleIndex, setVisibleIndex] = useState(null)
const = handleClick = index => {
if (visibleIndex === index) return setVisibleIndex(null)
return setVisibleIndex(index)
}
return (
<NavUl isOpen={props.isOpen}>
{menuTags.map((menuTag, index) => {
return (
<MenuTag
tag={menuTag}
visibility={index === visibleIndex}
onClick={() => handleClick(index)}
/>
)
})}
<li>
<FontAwesomeIcon
// Move the logic to only be in the Parent, this component shouldn't have to
// pass it's parents variables.
onClick={props.toggleOpen}
// ... the rest of what you had
/>
</li>
</NavUl >
)
}
// Handle visibility through props instead!
const MenuItem = ({ tag, visibility }) => {
const { name, link, dropdownItems } = tag;
return (
<NavLi >
<Link to={link}>{name}</Link>
// If these are true dropdown items will appear.
{visibility && dropdownItem && (
{dropdownItems.map(item => (
<ul>
<li>
<a>{item}</a>
</li>
</ul>
))}
)}
</NavLi>
);
};
Hope that helps and happy coding :)

How to add a class to an image by click?

How to add a class to an image if the flag is done: true? As I have not tried, the class is added to all images, and not to those with true...
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [avatarArr, setAvatarArr] = useState({
avatar: [
{
id: 1,
url: "https://starwars-visualguide.com/assets/img/characters/1.jpg"
},
{
id: 2,
url: "https://starwars-visualguide.com/assets/img/characters/2.jpg"
},
{
id: 3,
url: "https://starwars-visualguide.com/assets/img/characters/3.jpg"
}
]
});
const [classUser, setClassUser] = useState(null);
const [selectUser, setSelectUser] = useState(false);
const onAddClass = id => {
if (avatarArr.avatar.find(items => items.id === id)) {
const index = avatarArr.avatar.findIndex(items => items.id === id);
setAvatarArr([
...avatarArr.slice(0, index),
...avatarArr.slice(index + 1)
]);
} else {
setAvatarArr([...avatarArr, { done: true }]);
setSelectUser(avatarArr.avatar.map(items => items.done));
if (selectUser) {
setClassUser("active__user");
}
}
};
const blockCreate = () => {
return avatarArr.avatar.map(items => {
return (
<div key={items.id}>
<img
src={items.url}
alt="avatar"
width="150px"
onClick={() => onAddClass(items.done, items.id)}
className={selectUser ? classUser : null}
/>
</div>
);
});
};
return (
<div className="App">
<div>{blockCreate()}</div>
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I'm trying to set true on click, to tell the user that the avatar that was clicked on is selected, and add some kind of styling class.
And if you click a second time, then true - becomes false, in short - the choice
Are you looking like this
export default function App() {
const [avatarArr, setAvatarArr] = useState({
avatar: [
{
id: 1,
url: "https://starwars-visualguide.com/assets/img/characters/1.jpg"
},
{
id: 2,
url: "https://starwars-visualguide.com/assets/img/characters/2.jpg"
},
{
id: 3,
url: "https://starwars-visualguide.com/assets/img/characters/3.jpg"
}
]
});
const [selectUser, setSelectUser] = useState(false);
const onAddClass = item => {
setSelectUser(item);
};
const blockCreate = () => {
return avatarArr.avatar.map(items => {
return (
<div key={items.id}>
<img
src={items.url}
alt="avatar"
width="150px"
onClick={() => onAddClass(items)}
className={selectUser.id === items.id ? "myClass" : ""}
/>
</div>
);
});
};
return (
<div className="App">
<div>{blockCreate()}</div>
</div>
);
}
Live working demo https://codesandbox.io/s/vigilant-almeida-169zj

Resources