8

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.

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
albert_anthony6
  • 594
  • 2
  • 9
  • 30
  • 1
    i think your best bet would be shouldComponentUpdate: https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/using_should_component_update.html – Matt Berg Feb 21 '20 at 18:34
  • 1
    I'll look into it and see if I can make it work. If I get it working, I'll let you know. Thank you – albert_anthony6 Feb 21 '20 at 18:35
  • Just FYI, the functional components and hooks API are much newer and generally more recommended than the class component API, because they are better at achieving separation of concerns. With a class component, you only have one state object, and one of each lifetime cycle method in which to implement all the logic for a particular component. It's fine if you're just trying to learn but there's a reason your course is using functional components and hooks instead of class components. – Patrick Roberts Feb 21 '20 at 18:40

2 Answers2

18

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.

Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100
  • Thanks for the info, I'll see if this works soon. I'm still trying to understand it completely. Could you tell me if I'm right to think that the instructor used a useCallback hook here because he needed the movieId prop to use within the fetchData method but the movieId will not always be the same. Because movieId changes, useCallback will not create a new method if movieId doesn't change but as soon as it changes, it will need to create a new fetchData method. correct? – albert_anthony6 Feb 21 '20 at 20:19
  • @albert_anthony6 100% nailed it. That's how virtually all hooks work, they only "do things" if something in their dependency array is different than the last time the hook was called - memoization at it's finest. – Adam Jenkins Feb 21 '20 at 20:21
  • Okay great, I understand why he uses it now; however, in your examples, the method is being passed as a prop to some other component but what the instructor did with the method was something different. I'll edit the question with the rest of the code so you can see how he used the method. I don't think he passed it as a prop anywhere, I could be wrong but could you look and see yourself and tell me what you think should be done? – albert_anthony6 Feb 21 '20 at 20:29
  • Ha, it’s irrelevant to useCallback the way your instructor used it. It’s used purely for referential integrity for creating callback props. There is NO point to use it the way your instructor used it and it actually only made the code slower, because now it has to check the memorization every time. – Adam Jenkins Feb 21 '20 at 20:34
  • So you at least see what he was trying to do haha. How could what he attempted to do be done better in your opinion? – albert_anthony6 Feb 21 '20 at 20:35
  • 1
    Just put the entire contents of fetchData inside the effect. Alternatively you could extract fetchData completely and have it take movieId and the state setting functions as parameters. – Adam Jenkins Feb 21 '20 at 20:39
  • That's what I needed to hear. Thank you for all of your answers. When I can get around to coding, I'll see if I can get it to work that way. If not, I'll have more questions lol. Thanks again – albert_anthony6 Feb 21 '20 at 20:42
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/208289/discussion-between-albert-anthony6-and-adam). – albert_anthony6 Feb 21 '20 at 22:31
  • I had this same doubt. I am literally googling and asking my friends about it for almost entire day. Your comment came in like an angel to me XD XD Thanks man. Very good explanation. Upvoted :) – EaGle Network Jun 09 '21 at 12:29
1

You can replicate the behavior ofuseCallback by using a memorized function for the given input(eg: movieId)

You can use lodash method

for more in-depth understanding check here

Abhishek
  • 1,302
  • 10
  • 18