【发布时间】:2021-10-26 11:05:21
【问题描述】:
当我尝试渲染不同的组件时,React 会抛出以下错误
警告:React 检测到 GenericDialog 调用的 Hook 的顺序发生了变化。如果不修复,这将导致错误和错误。
| Previous render | Next render |
|---|---|
| useRef | useRef |
| useState | useState |
| useState | useState |
| useState | useState |
| useState | useState |
| useState | useState |
| useContext | useState |
我同意这在我每次渲染相同的组件但使用不同的钩子顺序时是不合适的。我想要实现的是每次渲染一个不同的组件,所以很明显钩子的顺序不会相同。
我创建了这个GenericDialog 组件,它呈现一个多步对话框。
import React, { useRef, useState, useEffect } from 'react';
import { DialogFooterNavigation } from './DialogFooterNavigation';
import { Dialog } from '../../../../Dialog';
import { Subheader } from '../../../../Subheader';
import { Loading } from '../../../../Loading';
export interface FooterConfiguration {
onContinue?: () => Promise<boolean | void>;
isContinueDisabled?: boolean;
continueLabel?: string;
isBackHidden?: boolean;
isCancelHidden?: boolean;
}
export interface HeaderConfiguration {
subheader?: string;
}
export interface DialogStepProps {
setHeaderConfiguration: (config: HeaderConfiguration) => void;
setFooterConfiguration: (config: FooterConfiguration) => void;
}
export type DialogStep = (props: DialogStepProps) => JSX.Element;
interface GenericDialogProps {
isShown: boolean;
hideDialog: () => void;
steps: DialogStep[];
header: string;
}
export const GenericDialog = ({
isShown,
hideDialog,
steps,
header,
}: GenericDialogProps) => {
const buttonRef = useRef(null);
const [step, setStep] = useState<number>(0);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [headerConfiguration, setHeaderConfiguration] = useState<HeaderConfiguration | undefined>(
undefined,
);
const [footerConfiguration, setFooterConfiguration] = useState<FooterConfiguration | undefined>(
undefined,
);
const [loadingMessage, setLoadingMessage] = useState<string>('');
const dialogBody = steps[step]({
setHeaderConfiguration,
setFooterConfiguration,
});
const nextStep = () => {
if (step < steps.length - 1) {
setStep(step + 1);
}
};
const prevStep = () => step > 0 && setStep(step -1);
const isBackPossible = step > 0;
const onBack = () => (isBackPossible || footerConfiguration?.isBackHidden ? undefined : prevStep);
const onContinue = async () => {
setIsLoading(true);
const result = await footerConfiguration?.onContinue?.call(undefined);
setIsLoading(false);
if (result === false) {
return;
}
nextStep();
};
return (
<Dialog isShown={isShown} onHide={hideDialog}>
<div>
{header}
{headerConfiguration?.subheader && (
<Subheader>{headerConfiguration.subheader}</Subheader>
)}
</div>
{isLoading && loadingMessage ? <Loading msg={loadingMessage} /> : dialogBody}
{!isLoading && (
<DialogFooterNavigation
onBack={isBackPossible ? onBack : undefined}
onContinue={onContinue}
isContinueDisabled={footerConfiguration?.isContinueDisabled}
/>
)}
</Dialog>
);
};
const FirstStep = (props: DialogStepProps) => {
// Here I need useContext
const { id, name } = useCustomerContext();
useEffect(() => {
props.setFooterConfiguration({
isContinueDisabled: !id || !name,
})
}, [id, name]);
return (
<>
<div>ID: {id}</div>
<div>Name: {name}</div>
</>
);
};
const SecondStep = (props: DialogStepProps) => {
// Here I don't need useContext but I do need useState
const [inputValue, setInputValue] = useState({});
useEffect(() => {
props.setFooterConfiguration({
isContinueDisabled: !inputValue,
});
}, [inputValue]);
return <input value={inputValue} onChange={(event) => setInputValue(event.target.value)} />;
}
const MyDialogExample = () => {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const steps: DialogStep[] = [
FirstStep,
SecondStep,
];
return (
<>
<button onClick={() => setIsDialogOpen(true)}>Open Dialog</button>
<GenericDialog
isShown={isDialogOpen}
hideDialog={() => setIsDialogOpen(false)}
steps={steps}
header="Dialog example"
/>
</>
);
};
【问题讨论】:
标签: javascript reactjs typescript