1

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

skyboyer
  • 22,209
  • 7
  • 57
  • 64
FlaskDev
  • 25
  • 6
  • Welcome to Stack Overflow! Please take the [tour] if you haven't already (you get a badge!), have a look around, and read through the [help], in particular [*How do I ask a good question?*](/help/how-to-ask) I also recommend Jon Skeet's [Writing the Perfect Question](https://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/) and [Question Checklist](https://codeblog.jonskeet.uk/2012/11/24/stack-overflow-question-checklist/). – T.J. Crowder Mar 03 '22 at 17:19
  • I'm afraid we can't help you with such fragmentary operation, but the phrase *"...after `hookChild1` has done its processing..."* suggests perhaps that processing is asynchronous? If so, then `hookFather` needs to be asynchronous. (Otherwise -- if `hookChild1` isn't asynchronous -- no issue comes up: `hookChild1` would do its processing and return a value, and `hookFather` could just use/return that value.) – T.J. Crowder Mar 03 '22 at 17:20
  • Yeah, hoodChild1 is asynchronous because it is a GraphQL query... By other way, how can i do a custom hook like `hookFather` asynchronous? Thanks a lot!! – FlaskDev Mar 03 '22 at 19:57

1 Answers1

1

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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875