Я пытаюсь понять новый API React-хуков (сейчас я использую React 16.8.x
).
Я обнаружил, что хук useEffect()
очень легко отбрасывает результатывызова сервера, когда пользователь делает что-то, что приводит к тому, что компонент больше не отображается, согласно (A) :
useEffect(()=>{
let mounted = true;
setInvocation("processing");
MuiServiceApi.instance.
invokeProcessingMethod(details.endpoint, parsedPayload).
then(result=> mounted && setInvocation(result)).
catch(e=> setInvocation({message: "while updating DB", problem: e}));
return ()=>{mounted = false};
}, []);
Но как мне добиться аналогичного поведения, когда явызов из обычного события формы, в соответствии с (B) :
<form onSubmit={()=>{
setInvocation("processing");
MuiServiceApi.instance.
invokeProcessingMethod(details.endpoint, parsedPayload).
then(result=> setInvocation(result)).
catch(e=> setInvocation({message: "while updating DB", problem: e}));
}}>
Если пользователь закрывает компонент во время его вызова при его первом отображении (то есть (A) логика), тогда результат будет полностью отброшен.
Если пользователь отклоняет компонент во время обработки, после нажатия кнопки фактической отправки ( (B) логика), появится консольное предупреждение вроде:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Это на самом деле не имеет большого значения - на самом деле, под API-интерфейсом pre-hooks class
, я никогда не удосужился отбросить результаты для размонтированных компонентов, потому чтоэто было слишком много хлопот.
Но цель этого упражнения состояла в том, чтобы узнать об API Hooks
, поэтому я хотел бы знать, как я могу сделать это для обработчика onSubmit
.
Я попытался определитьmounted
с useState()
, но я не вижу, как это будет работать, согласно:
const [mounted, setMounted] = React.useState(false);
useEffect(()=>{
setMounted(true);
setInvocation("processing");
MuiServiceApi.instance.
invokeProcessingMethod(details.endpoint, parsedPayload).
then(result=> {
if( mounted ){
console.log("result used");
setInvocation(result);
}
else {
console.log("result ignored because dismounted");
}
}).
catch(e=> setInvocation({message: "while updating DB", problem: e}));
return ()=>{
console.log("dismounted");
setMounted(false)
};
}, []);
Теперь я понимаю, что это не может работать, потому что mounted
значение false
захвачено закрытием;так что обработчик then
никогда не увидит mounted == true
.
Это где «редукторы» или «обратные вызовы» должны использоваться?Документация становится довольно расплывчатой после «базовых» зацепок, поэтому я не уверен, что именно так я и должен делать эту работу.
Повторю вопрос: как следует реорганизовать приведенный ниже компонент так, чтобыобработчик then()
внутри form
onSubmit
не приведет к предупреждению о состоянии обновления, если компонент уже был отключен?
Полный компонент ниже (в Typescript
)
function InvokeEndpoint(props:{}){
const [details, setDetails] = React.useState(
{endpoint: "testPayload", payload: '{"log":["help"]}'} );
const [invocation, setInvocation] = React.useState
<"init"|"processing"|ErrorInfo|ProcessingLogV1>("init");
let isValidEndpoint = !!details.endpoint;
let isValidPayload = true;
let payloadErrorText = "";
let parsedPayload = {};
try {
parsedPayload = JSON.parse(details.payload);
}
catch( e ) {
isValidPayload = false;
payloadErrorText = e.toString();
}
useEffect(()=>{
let mounted = true;
setInvocation("processing");
MuiServiceApi.instance.
invokeProcessingMethod(details.endpoint, parsedPayload).
then(result=> mounted && setInvocation(result)).
catch(e=> setInvocation({message: "while updating DB", problem: e}));
return ()=>{mounted = false};
}, []);
const isProcessing = invocation == "processing";
let result = undefined;
if( invocation != "init" && invocation != "processing" ){
if( isErrorInfo(invocation) ){
result = <MuiCompactErrorPanel error={invocation}/>
}
else {
result = <ul>{
invocation.log.map((it,index)=> <li key={index}>{it}</li>)
}</ul>
}
}
return <Card><CardContent> <form onSubmit={()=>{
setInvocation("processing");
MuiServiceApi.instance.
invokeProcessingMethod(details.endpoint, parsedPayload).
then(result=> {
console.log("resulted", result);
setInvocation(result);
}).
catch(e=> {
console.log("errored");
setInvocation({message: "while updating DB", problem: e});
} );
}}>
<Typography variant={"h5"}>Invoke endpoint</Typography>
<TextField id="endpointInput" label="Endpoint"
margin="normal" variant="outlined" autoComplete="on" fullWidth={true}
inputProps={{autoCapitalize:"none"}}
value={details.endpoint}
onChange={( event: ChangeEvent<HTMLInputElement> )=>{
setDetails({...details, endpoint: event.currentTarget.value});
}}
disabled={isProcessing}
error={!isValidEndpoint}
/>
<TextField id="payloadInput" label="Payload"
margin="normal" variant="outlined" autoComplete="on" fullWidth={true}
inputProps={{autoCapitalize:"none"}}
multiline={true}
value={details.payload}
onChange={( event: ChangeEvent<HTMLInputElement> )=>{
setDetails({...details, payload: event.currentTarget.value});
}}
disabled={isProcessing}
error={!isValidPayload}
helperText={payloadErrorText}
/>
<PrimaryButton type="submit" color="primary"
disabled={isProcessing || !isValidPayload || !isValidEndpoint}
>
<ButtonLabel isLoading={isProcessing}>Invoke</ButtonLabel>
</PrimaryButton>
{ result }
</form> </CardContent></Card>
}