React Hooks: как отменить асинхронные результаты при запуске из обычного обработчика событий? - PullRequest
0 голосов
/ 13 мая 2019

Я пытаюсь понять новый 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>
}

1 Ответ

0 голосов
/ 21 июня 2019

Один из способов справиться с этим - изменить mounted с переменной на ref с помощью API React useRef .

Объявите ссылку с const mounted = React.useRef(false), затем используйте mounted.current везде, где предыдущий код использовал переменную.

Постоянный доступ к значению Ref через current означает, что все эффекты и обработчики событий компонента привязаны к единственному «текущему» значению, на которое указывает Ref, а не к копии переменной. был когда компонент был визуализирован.

...