Search code examples
javascriptreactjsreact-hooksreact-custom-hooks

How to make a custom hook wait for another one that has to return the input parameter of the first one


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


Solution

  • 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.