I’m trying to get the width of an element but I get an error when I invoke a function from the container component Sidebar
: onClick={() => setActiveComponent(sidebarViews[1])}
. Why is this error occurring?
Sidebar.tsx
function Sidebar() {
const [activeComponent, setActiveComponent] = useState(PropertyList);
const sidebarViews = [<Filters/>, <PropertyList/>]; // sidebarViews : JSX.Element[]
return (
<div className="sidebar">
<div className="sidebar-top">
<Navigation
sidebarViews={sidebarViews}
setActiveComponent={setActiveComponent}
/>
</div>
<div className="sidebar-content">
{activeComponent}
</div>
</div>
);
}
function Navigation({
sidebarViews,
setActiveComponent
}: {
sidebarViews: JSX.Element[],
setActiveComponent: React.Dispatch<React.SetStateAction<JSX.Element>>
}) {
return (
<div style={{backgroundColor: "blue", color: "white"}}>
<div className="sidebar-navigation-bar">
<NavigationButton
onClick={() => setActiveComponent(sidebarViews[0])}
icon="tune"
key="0"
></NavigationButton>
<NavigationButton
onClick={() => setActiveComponent(sidebarViews[1])}
icon="pin_drop"
key="1"
></NavigationButton>
</div>
</div>
)
}
function NavigationButton(
{ onClick, icon }: { onClick: any, icon: string }
) {
return (
<span onClick={onClick} className="material-icons">
{icon}
</span>
)
}
PropertyList
import Property from './property.tsx';
import { useCallback, useEffect, useRef, useState } from 'react';
function PropertyList() {
const ref = useRef<HTMLHeadingElement>(null);
useEffect(() => {
console.log('width', ref.current ? ref.current.offsetWidth : 0);
}, [ref.current]);
return (
<div className="property-list">
<Property key="1"></Property>
<Property key="2"></Property>
</div>
);
}
export default PropertyList
I get this error:
Error handled by React Router default ErrorBoundary: Error: Rendered
fewer hooks than expected. This may be caused by an accidental early
return statement.DefaultErrorComponent@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=91faa1fe:3960:15 RenderErrorBoundary@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=91faa1fe:3992:5 DataRoutes@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=91faa1fe:5154:7 Router@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=91faa1fe:4421:7 RouterProvider@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=91faa1fe:4969:7 localhost:5173:13851:25 overrideMethod (index):13851 overrideMethod (index):13898 DefaultErrorComponent hooks.tsx:536 React 14 onClick sidebar.tsx:34 React 23 <anonymous> main.tsx:25 ```
I’m trying to set activeComponent
to the PropertyList
component to render it and get the width.
2
Answers
So this happening because you’re storing the component you want to render in state. In React’s strict mode, it will render twice to catch bugs where hooks are called in different orders.
Try this:
SideBar.tsx
Navigation
Issue
The issue here is that you are passing functions to the
useState
hook and state updater functions which are being interpreted as lazy initialization functions (in the case ofuseState(PropertyList);
) and functional state updates (in the case ofsetActiveComponent(sidebarViews[0])
).When you pass functions in either of the above cases React will invoke the function and pass the returned result value for the state. This is a problem because in React we never manually/directly call our React functions. The issue is that doing this is calling any internal React hooks outside the React component lifecycle.
On the initial render the
PropertyList
component is rendered which calls auseEffect
hook. When the state is updated to render theFilters
component, it appears no new hooks are called and there is now one less React hook called, thus producing the error.Solution
You could update the logic to use functions that return the React component you wish to store in the state (versus the result of calling the React function). We basically replace the state type from
JSX.Element
toReact.ComponentType
, and use functional updates.Example:
Sidebar
Navigation
An alternative solution would be to store the key that represents the component you wish to render at runtime and compute the component.
Sidebar
Navbar