Я использую 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.
Не совсем уверен, что здесь происходит, и задаюсь вопросом, может ли кто-то пролить свет на то, что здесь происходит.