Search code examples
javascriptreactjsreact-hooksusecallback

How to convert functional component using hooks to class component


I'm trying to challenge myself to convert my course project that uses hooks into the same project but without having to use hooks in order to learn more about how to do things with class components. Currently, I need help figuring out how to replicate the useCallback hook within a normal class component. Here is how it is used in the app.

export const useMovieFetch = movieId => {
    const [state, setState] = useState({});
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(false);

    const fetchData = useCallback(async () => {
        setError(false);
        setLoading(true);

        try{
            const endpoint = `${API_URL}movie/${movieId}?api_key=${API_KEY}`;
            const result = await(await fetch(endpoint)).json();
            const creditsEndpoint = `${API_URL}movie/${movieId}/credits?api_key=${API_KEY}`;
            const creditsResult = await (await fetch(creditsEndpoint)).json();
            const directors = creditsResult.crew.filter(member => member.job === 'Director');
            
            setState({
                ...result,
                actors: creditsResult.cast,
                directors
            });

        }catch(error){
            setError(true);
            console.log(error);
        }
        setLoading(false);
    }, [movieId])

useEffect(() => {
        if(localStorage[movieId]){
            // console.log("grabbing from localStorage");
            setState(JSON.parse(localStorage[movieId]));
            setLoading(false);
        }else{
            // console.log("Grabbing from API");
            fetchData();
        }
    }, [fetchData, movieId])

    useEffect(() => {
        localStorage.setItem(movieId, JSON.stringify(state));
    }, [movieId, state])

    return [state, loading, error]
}

I understand how to replicate other hooks such as useState and useEffect but I'm struggling to find the answer for the alternative to useCallback. Thank you for any effort put into this question.


Solution

  • TL;DR

    In your specific example useCallback is used to generate a referentially-maintained property to pass along to another component as a prop. You do that by just creating a bound method (you don't have to worry about dependencies like you do with hooks, because all the dependencies are maintained on your instance as props or state.

    class Movie extends Component {
    
        constructor() {
            this.state = {
                loading:true,
                error:false,
            }
        }
    
        fetchMovie() {
            this.setState({error:false,loading:true});
    
            try {
                // await fetch
                this.setState({
                    ...
                })
            } catch(error) {
                this.setState({error});
            }
        }
    
        fetchMovieProp = this.fetchMovie.bind(this); //<- this line is essentially "useCallback" for a class component
    
        render() {
            return <SomeOtherComponent fetchMovie={this.fetchMovieProp}/>
        }
    
    }
    

    A bit more about hooks on functional vs class components

    The beautiful thing about useCallback is, to implement it on a class component, just declare an instance property that is a function (bound to the instance) and you're done.

    The purpose of useCallback is referential integrity so, basically, your React.memo's and React.PureComponent's will work properly.

    const MyComponent = () => {
      const myCallback = () => { ... do something };
      return <SomeOtherComponent myCallback={myCallback}/> // every time `MyComponent` renders it will pass a new prop called `myCallback` to `SomeOtherComponent`
    }
    
    const MyComponent = () => {
      const myCallback = useCallback(() => { ... do something },[...dependencies]);
      return <SomeOtherComponent myCallback={myCallback}/> // every time `MyComponent` renders it will pass THE SAME callback to `SomeOtherComponent` UNLESS one of the dependencies changed
    }
    

    To replicate useCallback in class components you don't have to do anything:

    class MyComponent extends Component {
    
       method() { ... do something }
    
       myCallback = this.method.bind(this); <- this is essentially `useCallback`
    
       render() {
         return <SomeOtherComponent myCallback={this.myCallback}/> // same referential integrity as `useCallback`
       }
    
    }
    

    THE BIG ONE LINER

    You'll find that hooks in react are just a mechanism to create instance variables (hint: the "instance" is a Fiber) when all you have is a function.