How to convert functional component using hooks to class component
Asked Answered
M

2

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.

Merridie answered 21/2, 2020 at 18:22 Comment(3)
i think your best bet would be shouldComponentUpdate: developmentarc.gitbooks.io/react-indepth/content/life_cycle/…Lo
I'll look into it and see if I can make it work. If I get it working, I'll let you know. Thank youMerridie
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.Seel
S
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.

Seaway answered 21/2, 2020 at 19:9 Comment(9)
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?Merridie
@Merridie 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.Seaway
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?Merridie
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.Seaway
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?Merridie
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.Seaway
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 againMerridie
Let us continue this discussion in chat.Merridie
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 :)Nerland
N
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

Napier answered 21/2, 2020 at 18:59 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.