It looks like a tongue twister.
The case is that I have a custom hook let's call it hookFather
and others within it hookChild1
, hookChild2
, ... the case that I need the value of hookChild2
but a value that should be returned to me after hookChild1
has done its processing and given it passed to said hook. And both values (of hookChild1
and hookChild2
) will already be processed later in the hookFather.
How can I make a structure that they wait for each other. I can't put the hooks inside useEffect or useMemo
Yeah,
hoodChild1
is asynchronous because it is a GraphQL query... By other way, how can i do a custom hook likehookFather
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:
loading
)null
for none in this case) indicating that an error occurredThere 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.