Я пытаюсь получить ширину элемента, но получаю ошибку при вызове функции из компонента контейнера Sidebar
: onClick = {() => setActiveComponent(sidebarViews[1])}
. Почему возникает эта ошибка?
Боковая панель.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>
)
}
Список свойств
import Property from './property.tsx';
import { useCallback, useEffect, useRef, useState } from 'react';
function PropertyList() {
const ref = useRef<HTMLHeadingElement>(null);
useEffect(() => {
console.info('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
Я получаю эту ошибку:
Ошибка, обрабатываемая React Router по умолчанию ErrorBoundary: Ошибка: Отрисовано меньше крючков, чем ожидалось. Это может быть вызвано случайным ранним заявление о возврате.
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 ```
Я пытаюсь установить activeComponent
к компоненту PropertyList
, чтобы отобразить его и получить ширину.
Это происходит потому, что вы сохраняете компонент, который хотите отобразить, в состоянии. В строгом режиме React он будет отображаться дважды, чтобы выявить ошибки, при которых хуки вызываются в разном порядке.
Попробуйте это:
SideBar.tsx
const [activeComponent, setActiveComponent] = useState<'list' | 'filters'>('list');
...
<div className = "sidebar-content">
{activeComponent === 'list' && <ProperyList />}
{activeComponent === 'filters' && <Filters />}
</div>
Навигация
<NavigationButton onClick = {() => setActiveComponent('filters')} icon = "tune" key = "0"></NavigationButton>
<NavigationButton onClick = {() => setActiveComponent('list')} icon = "pin_drop" key = "1"></NavigationButton>
Проблема здесь в том, что вы передаете функции перехватчику useState
и функциям обновления состояния, которые интерпретируются как функции ленивой инициализации (в случае useState(PropertyList);
) и обновления функционального состояния (в случае setActiveComponent(sidebarViews[0])
).
Когда вы передаете функции в любом из вышеперечисленных случаев, React вызовет функцию и передаст возвращаемое значение результата для состояния. Это проблема, потому что в React мы никогда вручную или напрямую не вызываем наши функции React. Проблема в том, что при этом вызываются любые внутренние перехватчики React вне жизненного цикла компонента React.
При первоначальном рендеринге отображается компонент PropertyList
, который вызывает хук useEffect
. Когда состояние обновляется для рендеринга компонента Filters
, оказывается, что новые хуки не вызываются, и теперь вызывается на один крючок React меньше, что приводит к ошибке.
Вы можете обновить логику, чтобы использовать функции, которые возвращают компонент React, который вы хотите сохранить в состоянии (а не результат вызова функции React). По сути, мы заменяем тип состояния с JSX.Element
на React.ComponentType
и используем функциональные обновления.
Пример:
Боковая панель
function Sidebar() {
// Initializer function that returns the React function
const [ActiveComponent, setActiveComponent] = useState<React.ComponentType>(
() => PropertyList
);
// Array of function component references
const sidebarViews = [Filters, PropertyList]; // inferred React.ComponentType[]
return (
<div className = "sidebar">
<div className = "sidebar-top">
<Navigation
sidebarViews = {sidebarViews}
setActiveComponent = {setActiveComponent}
/>
</div>
<div className = "sidebar-content">
{/* Render component as JSX 🙂 */}
<ActiveComponent />
</div>
</div>
);
}
Навигация
function Navigation({
sidebarViews,
setActiveComponent,
}: {
sidebarViews: React.ComponentType[];
setActiveComponent: React.Dispatch<React.SetStateAction<React.ComponentType>>;
}) {
return (
<div style = {{ backgroundColor: "blue", color: "white" }}>
<div className = "sidebar-navigation-bar">
<NavigationButton
// Functional state update to return the React function
onClick = {() => setActiveComponent(() => sidebarViews[0])}
icon = "tune"
key = "0"
/>
<NavigationButton
// Functional state update to return the React function
onClick = {() => setActiveComponent(() => sidebarViews[1])}
icon = "pin_drop"
key = "1"
/>
</div>
</div>
);
}
Альтернативным решением было бы сохранить ключ, представляющий компонент, который вы хотите отобразить, во время выполнения, и вычислить этот компонент.
const componentsMap = {
Filters,
PropertyList,
};
Боковая панель
function Sidebar() {
const [activeComponent, setActiveComponent] =
useState<keyof typeof componentsMap>("PropertyList");
const sidebarViews: (keyof typeof componentsMap)[] = [
"Filters",
"PropertyList",
];
// Compute the component to render
const ActiveComponent = componentsMap[activeComponent];
return (
<div className = "sidebar">
<div className = "sidebar-top">
<Navigation
sidebarViews = {sidebarViews}
setActiveComponent = {setActiveComponent}
/>
</div>
<div className = "sidebar-content">
{/* Render component as JSX 🙂 */}
<ActiveComponent />
</div>
</div>
);
}
Навбар
function Navigation({
sidebarViews,
setActiveComponent,
}: {
sidebarViews: (keyof typeof componentsMap)[];
setActiveComponent: React.Dispatch<
React.SetStateAction<keyof typeof componentsMap>
>;
}) {
return (
<div style = {{ backgroundColor: "blue", color: "white" }}>
<div className = "sidebar-navigation-bar">
<NavigationButton
onClick = {() => setActiveComponent("Filters")}
icon = "tune"
key = "0"
/>
<NavigationButton
onClick = {() => setActiveComponent("PropertyList")}
icon = "pin_drop"
key = "1"
/>
</div>
</div>
);
}
@BushaSmith Вот работающая песочница для первого примера, а вот работающая песочница для второго примера.
@BushaSmith В первом комментарии вы, возможно, пропустили что-то незначительное. Я еще раз проверю свою песочницу, но у меня все работало без проблем. Для второго комментария я просто дублирую ваш код, вы также указали только компонент
PropertyList
сuseState(PropertyList);
.