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.
Related
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.
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.
This is my code below
const Search = ({ results }) => {
const dispatch = useDispatch();
const handleClearData = () => {
dispatch(clearData());
};
const columns = useMemo(
() => [
{
Header: 'G35 Number',
accessor: 'g35Number',
Cell: props => {
const { original } = props.row;
return (
<React.Fragment>
<Link data-tip data-for={`link-${original.sNumber}`}
to={{ pathname: `/search/${original.sNumber}`, state: { s: props.row.original } }} onClick={handleClearData}>
{original.g35Number}
</Link>
</React.Fragment>
);
},
},
{
Header: 'Regn Number',
accessor: 'regNumber',
},
{
Header: 'File',
accessor: 'file',
},
{
Header: 'Details',
accessor: 'details',
}
],
[]
);
const data = useMemo(() => results, [results]); // eslint-disable-line react-hooks/exhaustive-deps
const renderTable = () => {
return (
<ReactTable columns={columns} data={data} />
);
}
return (
<div className="card py-3 px-4">
{
results?.length ?
renderTable() :
<div>No results.</div>
}
</div>
);
}export default Search;
I am getting the below warning:
React Hook useMemo has a missing dependency: 'handleClearData'. Either include it or remove the dependency array
I tried to add handleClearData in the dependency array of the useMemo, which gave me the below warning:
The 'handleClearData' function makes the dependencies of useMemo Hook change on every render. Move it inside the useMemo callback. Alternatively, wrap the 'handleClearData' definition into its own useCallback() Hook
I did not understand what it meant when it said that I need to wrap it in its own useCallback() hook.
Can anyone help me what I am missing? I am not sure if I want to add anything in the dependency array if I just want to load it only for the first time (That if it works the same way in useEffect).
I was able to do resolve the warning by moving the function inside useMemo like below:
{
Header: 'G35 Number',
accessor: 'g35Number',
Cell: props => {
const { original } = props.row;
const dispatch = useDispatch();
const handleClearData = () => {
dispatch(clearData());
};
return (
<React.Fragment>
<Link data-tip data-for={`link-${original.sNumber}`}
to={{ pathname: `/search/${original.sNumber}`, state: { s: props.row.original } }} onClick={handleClearData}>
{original.g35Number}
</Link>
</React.Fragment>
);
},
}
I am using next js and I am new.I have three buttons on page A and I want to transfer '1', '2' or '3' to page B by clicking on each button.I am currently using router.push but it does not work.Because when I want to put the value in useEffect on page B, it is done with a delay.
export default function A() {
return (
<Button1 onClick={() => {router.push({
pathname: "/B",
query: {
value: "1",
},
});
}}
/>
<Button2 onClick={() => {router.push({
pathname: "/B",
query: {
value: "2",
},
});
}}
/>
<Button3 onClick={() => {router.push({
pathname: "/B",
query: {
value: "3",
},
});
}}
/>
)
}
and B page is
export default function B() {
const { value } = router.query;
useEffect(() => {
console.log("value is :", value);
}
},[])
Did I choose the wrong method, or is it better to change useEffect?
Thank you for your support
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 :)