Yeah, hoodChild1
is asynchronous because it is a GraphQL query... By other way, how can i do a custom hook like hookFather
asynchronous?
Hooks are very much like component functions. If the hook is going to start something that will complete later and update the state managed by the hook, you do that in a useEffect
callback and use useState
to track the state.
Here's a basic example using setTimeout
in place of the GraphQL query:
function useSomething(value) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [result, setResult] = useState(null);
useEffect(() => {
// Clear result, set loading, start query
setResult(null);
setLoading(true);
setError(null);
const handle = setTimeout(() => {
// Query complete, save the result and clear the loading flag
if (Math.random() < 0.333333333333333) {
// About a third of the time, fail for demonstration purposes
setError(new Error("Failed to load stuff"));
} else {
setResult(value * 2);
}
setLoading(false);
}, 500);
// Return a cleanup callback that cancels the timer if `value`
// changes or the component using this hook is unmounted
return () => {
clearTimeout(handle);
};
}, [value]);
// Return the loading flag, error, and the current result
return [loading, error, result];
}
const {useState, useEffect} = React;
function useSomething(value) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [result, setResult] = useState(null);
useEffect(() => {
// Clear result, set loading, start query
setResult(null);
setLoading(true);
setError(null);
const handle = setTimeout(() => {
// Query complete, save the result and clear the loading flag
if (Math.random() < 0.333333333333333) {
// About a third of the time, fail for demonstration purposes
setError(new Error("Failed to load stuff"));
} else {
setResult(value * 2);
}
setLoading(false);
}, 500);
// Return a cleanup callback that cancels the timer if `value`
// changes or the component using this hook is unmounted
return () => {
clearTimeout(handle);
};
}, [value]);
// Return the loading flag, error, and the current result
return [loading, error, result];
}
const Example = () => {
const [value, setValue] = useState(1);
const [loading, error, result] = useSomething(value);
return <div>
<div>Value: {value} <input type="button" onClick={() => setValue(v => v + 1)} value="+" /></div>
<div>Result for {value}: {
loading
? <em>Loading...</em>
: error
? <strong>Error: {error.message}</strong>
: result
}</div>
</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Notice how it returns three pieces of information the component using it needs:
- A flag indicating its asynchronous work is in progress (
loading
)
- An error value (
null
for none in this case) indicating that an error occurred
- The result data
There are other ways to return those three pieces of information. For instance, you could return an object with a state
("loading"
, "error"
, or "complete"
) and either error
(if in the error state) or value
(if in the completed
state):
const {useState, useEffect} = React;
const states = {
loading: "loading",
error: "error",
success: "success",
};
function useSomething(value) {
const [state, setState] = useState({
state: states.loading,
});
useEffect(() => {
// Clear result, set loading, start query
setState({
state: states.loading,
});
const handle = setTimeout(() => {
// Query complete, save the result and clear the loading flag
if (Math.random() < 0.333333333333333) {
// About a third of the time, fail for demonstration purposes
setState({
state: states.error,
error: new Error("Failed to load stuff"),
});
} else {
setState({
state: states.success,
value: value * 2,
});
}
}, 500);
// Return a cleanup callback that cancels the timer if `value`
// changes or the component using this hook is unmounted
return () => {
clearTimeout(handle);
};
}, [value]);
// Return the state
return state;
}
const Example = () => {
const [value, setValue] = useState(1);
const something = useSomething(value);
return <div>
<div>Value: {value} <input type="button" onClick={() => setValue(v => v + 1)} value="+" /></div>
<div>Result for {value}: {
something.state === "loading"
? <em>Loading...</em>
: something.state === "error"
? <strong>Error: {something.error.message}</strong>
: something.value
}</div>
</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
But the fundamental thing is still the same: The hook has at least three states: Loading, Error, and Success.