Reaja métodos de ciclo de vida aos ganchos

Dec 16 2020

Tenho um blog simples com artigos. E eu quero reescrever classes para componentes funcionais e ganchos. Agora eu tenho essa lógica nos métodos de ciclo de vida da minha página com o formulário de edição / adição: funciona bem.

componentDidUpdate(prevProps, prevState) {
 if (this.props.match.params.id !== prevProps.match.params.id) {
    if (prevProps.match.params.id) {
        this.props.onUnload();
      }

      this.id = this.props.match.params.id;
        if (this.id) {
            return this.props.onLoad(userService.articles.get(this.id));
        }
        this.props.onLoad(null);
   }
   this.isLoading = false;
}

componentDidMount() {
  if (this.id) {
    this.isLoading = true;
    return this.props.onLoad(userService.articles.get(this.id));
  }
  this.isLoading = false;
  this.props.onLoad(null);
}
   
componentWillUnmount() {
   this.props.onUnload();
}
   
shouldComponentUpdate(newProps, newState) {
   if (this.props.match.params.id !== newProps.match.params.id) {
      this.isLoading = true;
   }
   return true;
}

Reescrevi tudo para ganchos como esse:

//componentDidMount
  useEffect(() => {
    if (id) {
      setIsloading(true);
      return props.onLoad(userService.articles.get(id));
    }
    setIsloading(false);
    props.onLoad(null);
  }, []);

  useEffect(()=> {
      prevId.current = id;
      }, [id]
  );

  //componentDidUpdate
  useEffect(() => {
    //const id = props.match.params.id;
    if (id !== prevId.current) {
      if (prevId.current) {
        props.onUnload();
      }
      if (id) {
        return props.onLoad(userService.articles.get(id));
      }
      props.onLoad(null);
    }
    setIsloading(false);
  });

  //componentWillUnmount
  useEffect(() => {
     return props.onUnload();
  }, []);

  1. Recebi o erro - "Muitas novas renderizações." em codesandbox código completo: codesandbox

É estranho, mas em localhost não há erro "Muitos re-renderizações."

  1. Não sei o que fazer com meu método de classe "shouldComponentUpdate" como reescrevê-lo para ganchos. Tentei 'memo', mas não tenho ideia de como escrever neste caso.

  2. E, de qualquer forma, estou perdendo algo, porque nem tudo funcionará - ele não está atualizando os campos do formulário corretamente.

Se você tem um bom conhecimento com ganchos de reação, por favor ajude ou dê algum conselho - como consertar?

Respostas

3 Mhmdrz_A Dec 18 2020 at 21:24

O efeito sem dependência está causando "Too many re-renders.": ele é executado após cada renderização, em seguida, ele chama setIsLoadingpara atualizar o estado ( loading) que fará com que o componente seja renderizado novamente, o que será executado effectnovamente e o setStateserá chamado novamente effecte assim por diante ...

//componentDidUpdate
  useEffect(() => {
    //const id = props.match.params.id;
    if (id !== prevId.current) {
      if (prevId.current) {
        props.onUnload();
      }
      if (id) {
        return props.onLoad(userService.articles.get(id));
      }
      props.onLoad(null);
    }
    setIsloading(false);
  })

para corrigir o problema, remova o setIsLoadingdele ou adicione-o IsLoadingcomo dependência.

//componentDidUpdate
  useEffect(() => {
    ...
    setIsloading(false);
  },[isLoading]);

você também pode mesclar dois efeitos de montagem em um como este (o seu também está funcionando, mas acho que é estilisticamente preferível):

//componentDidMount
  useEffect(() => {
    if (id) {
      setIsloading(true);
      return props.onLoad(userService.articles.get(id));
    }
    setIsloading(false);
    props.onLoad(null);

    //componentWillUnmount
    return function () {
      props.onUnload()
    }
  }, []);

para o segundo item: sobre como reescrever o do seu componente shouldComponentUpdate; primeiro devo apontar que sua existência shouldComponentUpdatenão parece razoável, uma vez que você sempre retorna true, e você só está usando isso para desencadear o loadingestado; e não é elegível para ser escrito com React.memo (que é equivalente a shouldComponentUpdatecomponentes in class); então você só precisa de algo a ser executado em cada mudança de props para determinar o loadingestado e para isso você pode usar um efeito com propscomo sua dependência como este:

//manually keeping old props id
const prevPropsId = useRef();

useEffect(() => {
   if (prevPropsId.current !== props.match.params.id) {
      setIsLoading(true);
   }
   prevPropsId.current = props.match.params.id;
 }, [props.match.params.id]);
// this hook only run if `props.match.params.id` change 

com base na minha percepção, isso fará o trabalho, (apesar de ter dificuldade em entender por que você escreve a lógica dessa forma em primeiro lugar) e se não estiver indo bem, você pode ajustar a lógica um pouco para atender às suas necessidades, você obtém o ideia de como o efeito de mudança de adereços funciona. você também precisa lidar typeErrorcom o caso id, paramsou matchnão existe, e o encadeamento opcional pode ser uma ferramenta útil aqui ->props.match?.params?.id

2 deckele Dec 23 2020 at 23:26

Se você perder os ganchos do antigo ciclo de vida, poderá sempre recriá-los como ganchos personalizados:

function useComponentDidMount(effect) {
  useEffect(() => {
    effect();
  }, []);
}
function useComponentWillUnmount(effect) {
  useEffect(() => {
    return effect;
  }, []);
}
function useComponentDidUpdate(effect, dependencies) {
  const hasMountedRef = useRef(false);
  const prevDependenciesRef = useRef(null)
  useEffect(() => {
    // don't run on first render, only on subsequent changes
    if (!hasMountedRef.current) {
      hasMountedRef.current = true;
      prevDependenciesRef.current = dependencies;
      return;
    }
    // run effect with previous dependencies, like in componentDidUpdate!
    effect(...prevDependenciesRef.current || []);
    prevDependenciesRef.current = dependencies;
  }, dependencies)
}

Uso (seu exemplo refatorado):

  //componentDidMount
  useComponentDidMount(() => {
    if (id) {
      setIsloading(true);
      props.onLoad(userService.articles.get(id));
      return;
    }
    setIsloading(false);
    props.onLoad(null);
  });

  //componentDidUpdate
  useComponentDidUpdate((prevId) => {
    if (prevId) {
      props.onUnload();
    }
    if (id) {
      props.onLoad(userService.articles.get(id));
      return;
    }
    props.onLoad(null);
    setIsloading(false);
  }, [id]);

  //componentWillUnmount
  useComponentWillUnmount(() => {
     props.onUnload();
  });