Компонент не рендерится при использовании потомков в качестве функции - PullRequest
2 голосов
/ 24 февраля 2020

Я использую react-apollo для запроса к серверу graphQL и могу успешно гидрировать клиент данными. Поскольку будет более одного места, я буду запрашивать данные, которые я пытаюсь создать контейнер (рефакторинг) для инкапсуляции хука useQuery, чтобы его можно было использовать в одном месте.

Первая попытка (работает должным образом)

const HomeContainer = () => {
  const { data, error, loading } = useQuery(GET_DATA_QUERY, {
    variables: DATA_VARIABLES
  });
  const [transformedData, setTransformedData] = useState();

  useEffect(() => {
    if(!!data) {
      const transformedData = someTransformationFunc(data);

      setTransformedData(...{transformedData});
    }
  }, [data]);

  if (loading) {
    return <div>Loading data ...</div>;
  }

  if (error) {
    return <p>Error loading data</p>;
  }
  if (!data) {
    return <p>Not found</p>;
  }

  return <Home transformedData={transformedData} />;
};

Я хотел инкапсулировать церемонию вокруг различных этапов запроса в новый контейнер (загрузка, состояние ошибки), чтобы уменьшить дублирование кода .

Первый удар при рефакторинге

  • Контейнер Query передается в query, variables и callback. Это берет на себя ответственность за возврат различных узлов на основе состояния запроса (загрузка, ошибка или когда данные не возвращаются).

const HomeContainer = () => {
  const {data, error, loading} = useQuery(GET_DATA_QUERY, {
    variables: DATA_VARIABLES
  });
  const [transformedData, setTransformedData] = useState();

  const callback = (data) => {
    const transformedData = someTransformationFunc(data);

    setTransformedData(...{
      transformedData
    });
  };

  return ( 
     <QueryContainer 
        query={GET_DATA_QUERY}
        variables={DATA_VARIABLES}
        callback ={callback} 
     >
        <Home transformedData={transformedData} />
     </QueryContainer>
  )
};

const QueryContainer = ({callback, query, variables, children }) => {
  const {data, error, loading } = useQuery(query, {
    variables: variables
  });

  // Once data is updated invoke the callback
  // The transformation of the raw data is handled by the child
  useEffect(() => {
    if (!!data) {
      callback(data);
    }
  }, [data]);

  if (loading) {
    return <div > Loading data... < /div>;
  }

  if (error) {
    return <p > Error loading data < /p>;
  }
  if (!data) {
    return <p > Not found < /p>;
  }

  return children;
};

QueryContainer использует useEffect и вызывает callback, когда data возвращается. Я чувствовал, что это немного грязно и побеждает цель инкапсуляции в родителе и использования callback для разговора и обновления потомка.

Третья попытка (использование детей как функции)

Избавился от обратного вызова и передал данные в качестве первого аргумента функции children.

const HomeContainer = () => {
    return (
        <QueryContainer
            query={GET_DATA_QUERY}
            variables={DATA_VARIABLES}
        >   
           {(data) => {
               const transformedData = someTransformationFunc(data);

                return <Home transformedData={transformedData} />;
           }}

        </QueryContainer>
    )
};

const QueryContainer = ({ query, variables, children }) => {
    const { data, error, loading } = useQuery(query, {
        variables: variables
    });

    if (loading) {
        return <div>Loading data ...</div>;
    }

    if (error) {
        return <p>Error loading data</p>;
    }
    if (!data) {
        return <p>Not found</p>;
    }

    return children(data);
};

Я ожидал, что это сработает, поскольку на самом деле ничего не изменилось, и новый рендер при data обновляется, вызывает детей как функцию с data в качестве аргумента. Но когда я иду по этому маршруту, я вижу черный экран (без ошибок, и я вижу правильные данные, записанные в консоль). Если я снова нажму на ссылку, я вижу компонент, зафиксированный в DOM.

Не совсем уверен, что здесь происходит, и задаюсь вопросом, может ли кто-то пролить свет на то, что здесь происходит.

Ответы [ 2 ]

0 голосов
/ 26 февраля 2020

Фрагменты кода, которые я добавил выше, работают, как и ожидалось, изолированно.

https://codesandbox.io/s/weathered-currying-4ohh3

Проблема была с некоторым другим компонентом в дереве иерархии это приводило к тому, что компонент не перерисовывался.

2-я реализация работает, как и ожидалось, так как компонент снова визуализируется из-за обратного вызова, вызываемого из родителя.

0 голосов
/ 25 февраля 2020

ммм, должно работать ...

Попробуйте что-то вроде этого (инъекция компонента, немного похоже на HO C - вдохновение ):

const HomeContainer = () => {
  return (
    <QueryContainer
        query={GET_DATA_QUERY}
        variables={DATA_VARIABLES}
        transformation={someTransformationFunc}
        renderedComponent={Home}
    />   
  )
};

const QueryContainer = ({ query, variables, transformation, renderedComponent: View }) => {
  const { data, error, loading } = useQuery(query, { variables });

  if (loading) {
    return <div>Loading data ...</div>;
  }

  if (error) {
    return <p>Error loading data</p>;
  }
  if (!data) {
    return <p>Not found</p>;
  }

  // let transformedData = transformation(data);
  // return <View transformedData={transformedData} />;

  return <View transformedData={transformation ? transformation(data) : data} />;
};

Если все еще не работает (!?), Передайте оба параметра data и transformation как реквизиты и используйте их для инициализации состояния (с useState или useEffect).

Вам действительно / все еще нужно <HomeContainer/> как абстракция? ;)

...