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

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

combining useState with pop()

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.

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 Hook useMemo has a missing dependency: 'handleClearData'

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>
);
},
}

Data transfer between two pages in next js

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

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 :)

Resources