• 18-19 College Green, Dublin 2
  • 01 685 9088
  • info@cunninghamwebsolutions.com
  • cunninghamwebsolutions
    Cunningham Web Solutions
    • HomeHome
    • About UsAbout Us
    • Our ServicesOur Services
      • Web Design
      • Digital Marketing
      • SEO Services
      • E-commerce Websites
      • Website Redevelopment
      • Social Media Services
    • Digital MarketingDigital Marketing
      • Adwords
      • Social Media Services
      • Email Marketing
      • Display Advertising
      • Remarketing
    • PortfolioPortfolio
    • FAQ’sFAQ’s
    • BlogBlog
    • Contact UsContact Us
    MENU CLOSE back  
    • Home
    • About Us
    • Our Services
      • back
      • Web Design
      • Digital Marketing
      • SEO Services
      • E-commerce Websites
      • Website Redevelopment
      • Social Media Services
    • Digital Marketing
      • back
      • Adwords
      • Social Media Services
      • Email Marketing
      • Display Advertising
      • Remarketing
    • Portfolio
    • FAQ’s
    • Blog
    • Contact Us

    How To Create A Custom React Hook To Fetch And Cache Data

    You are here:
    1. Home
    2. Web Design
    3. How To Create A Custom React Hook To Fetch And Cache Data
    Thumbnail for 25275
    Smashing Editorial

    How To Create A Custom React Hook To Fetch And Cache Data

    How To Create A Custom React Hook To Fetch And Cache Data

    Ademola Adegbuyi

    2020-07-13T09:00:00+00:00
    2020-07-13T12:35:19+00:00

    If you are a newbie to React Hooks, you can start by checking the official documentation to get a grasp of it. After that, I’d recommend reading Shedrack Akintayo’s “Getting Started With React Hooks API”. To ensure you’re following along, there is also an article written by Adeneye David Abiodun that covers best practices with React Hooks which I’m sure will prove to be useful to you.

    Throughout this article, we’ll be making use of Hacker News Search API to build a custom hook which we can use to fetch data. While this tutorial will cover the Hacker News Search API, we’ll have the hook work in a way that it will return response from any valid API link we pass to it.

    Best Practices With React

    React is a fantastic JavaScript library for building rich user interfaces. It provides a great component abstraction for organizing your interfaces into well-functioning code, and there’s just about anything you can use it for. articles on React →

    Fetching Data In A React Component

    Before React hooks, it was conventional to fetch initial data in the componentDidMount() lifecycle method, and data based on prop or state changes in componentDidUpdate() lifecycle method.

    Here’s how it works:

    componentDidMount() {
      const fetchData = async () => {
        const response = await fetch(
          `https://hn.algolia.com/api/v1/search?query=JavaScript`
        );
        const data = await response.json();
        this.setState({ data });
      };
      
      fetchData();
    }
    
    
    componentDidUpdate(previousProps, previousState) {
        if (previousState.query !== this.state.query) {
          const fetchData = async () => {
            const response = await fetch(
              `https://hn.algolia.com/api/v1/search?query=${this.state.query}`
            );
            const data = await response.json();
            this.setState({ data });
          };
    
          fetchData();
        }
      }
    

    The componentDidMount lifecycle method gets invoked as soon as the component gets mounted, and when that is done, what we did was to make a request to search for “JavaScript” via the Hacker News API and update the state based on the response.

    The componentDidUpdate lifecycle method, on the other hand, gets invoked when there’s a change in the component. We compared the previous query in the state with the current query to prevent the method from getting invoked every time we set “data” in state. One thing we get from using hooks is to combine both lifecycle methods in a cleaner way — meaning that we won’t need to have two lifecycle methods for when the component mounts and when it updates.

    Fetching Data With useEffect Hook

    The useEffect hook gets invoked as soon as the component is mounted. If we need the hook to rerun based on some prop or state changes, we’ll need to pass them to the dependency array (which is the second argument of the useEffect hook).

    Let’s explore how to fetch data with hooks:

    import { useState, useEffect } from 'react';
    
    const [status, setStatus] = useState('idle');
    const [query, setQuery] = useState('');
    const [data, setData] = useState([]);
    
    useEffect(() => {
        if (!query) return;
    
        const fetchData = async () => {
            setStatus('fetching');
            const response = await fetch(
                `https://hn.algolia.com/api/v1/search?query=${query}`
            );
            const data = await response.json();
            setData(data.hits);
            setStatus('fetched');
        };
    
        fetchData();
    }, [query]);
    

    In the example above, we passed query as a dependency to our useEffect hook. By doing that, we’re telling useEffect to track query changes. If the previous query value isn’t the same as the current value, the useEffect get invoked again.

    With that said, we’re also setting several status on the component as needed, as this will better convey some message to the screen based on some finite states status. In the idle state, we could let users know that they could make use of the search box to get started. In the fetching state, we could show a spinner. And, in the fetched state, we’ll render the data.

    It’s important to set the data before you attempt to set status to fetched so that you can prevent a flicker which occurs as a result of the data being empty while you’re setting the fetched status.

    Creating A Custom Hook

    “A custom hook is a JavaScript function whose name starts with ‘use’ and that may call other Hooks.”

    — React Docs

    That’s really what it is, and along with a JavaScript function, it allows you to reuse some piece of code in several parts of your app.

    The definition from the React Docs has given it away but let’s see how it works in practice with a counter custom hook:

    const useCounter = (initialState = 0) => {
          const [count, setCount] = useState(initialState);
          const add = () => setCount(count + 1);
          const subtract = () => setCount(count - 1);
          return { count, add, subtract };
    };
    

    Here, we have a regular function where we take in an optional argument, set the value to our state, as well as add the add and the subtract methods that could be used to update it.

    Everywhere in our app where we need a counter, we can call useCounter like a regular function and pass an initialState so we know where to start counting from. When we don’t have an initial state, we default to 0.

    Here’s how it works in practice:

    import { useCounter } from './customHookPath';
    
    const { count, add, subtract } = useCounter(100);
    
    eventHandler(() => {
      add(); // or subtract();
    });
    

    What we did here was to import our custom hook from the file we declared it in, so we could make use of it in our app. We set its initial state to 100, so whenever we call add(), it increases count by 1, and whenever we call subtract(), it decreases count by 1.

    Creating useFetch Hook

    Now that we’ve learned how to create a simple custom hook, let’s extract our logic to fetch data into a custom hook.

    const useFetch = (query) => {
        const [status, setStatus] = useState('idle');
        const [data, setData] = useState([]);
    
        useEffect(() => {
            if (!query) return;
    
            const fetchData = async () => {
                setStatus('fetching');
                const response = await fetch(
                    `https://hn.algolia.com/api/v1/search?query=${query}`
                );
                const data = await response.json();
                setData(data.hits);
                setStatus('fetched');
            };
    
            fetchData();
        }, [query]);
    
        return { status, data };
    };
    

    It’s pretty much the same thing we did above with the exception of it being a function that takes in query and returns status and data. And, that’s a useFetch hook that we could use in several components in our React application.

    This works, but the problem with this implementation now is, it’s specific to Hacker News so we might just call it useHackerNews. What we intend to do is, to create a useFetch hook that can be used to call any URL. Let’s revamp it to take in a URL instead!

    const useFetch = (url) => {
        const [status, setStatus] = useState('idle');
        const [data, setData] = useState([]);
    
        useEffect(() => {
            if (!url) return;
            const fetchData = async () => {
                setStatus('fetching');
                const response = await fetch(url);
                const data = await response.json();
                setData(data);
                setStatus('fetched');
            };
    
            fetchData();
        }, [url]);
    
        return { status, data };
    };
    

    Now, our useFetch hook is generic and we can use it as we want in our various components.

    Here’s one way of consuming it:

    const [query, setQuery] = useState('');
    
    const url = query && `https://hn.algolia.com/api/v1/search?query=${query}`;
    const { status, data } = useFetch(url);
    

    In this case, if the value of query is truthy, we go ahead to set the URL and if it’s not, we’re fine with passing undefined as it’d get handled in our hook. The effect will attempt to run once, regardless.

    Memoizing Fetched Data

    Memoization is a technique we would use to make sure that we don’t hit the hackernews endpoint if we have made some kind of request to fetch it at some initial phase. Storing the result of expensive fetch calls will save the users some load time, therefore, increasing overall performance.

    Note: For more context, you could check out Wikipedia’s explanation on Memoization.

    Let’s explore how we could do that!

    const cache = {};
    
    const useFetch = (url) => {
        const [status, setStatus] = useState('idle');
        const [data, setData] = useState([]);
    
        useEffect(() => {
            if (!url) return;
    
            const fetchData = async () => {
                setStatus('fetching');
                if (cache[url]) {
                    const data = cache[url];
                    setData(data);
                    setStatus('fetched');
                } else {
                    const response = await fetch(url);
                    const data = await response.json();
                    cache[url] = data; // set response in cache;
                    setData(data);
                    setStatus('fetched');
                }
            };
    
            fetchData();
        }, [url]);
    
        return { status, data };
    };
    

    Here, we’re mapping URLs to their data. So, if we make a request to fetch some existing data, we set the data from our local cache, else, we go ahead to make the request and set the result in the cache. This ensures we do not make an API call when we have the data available to us locally. We’ll also notice that we’re killing off the effect if the URL is falsy, so it makes sure we don’t proceed to fetch data that doesn’t exist. We can’t do it before the useEffect hook as that will go against one of the rules of hooks, which is to always call hooks at the top level.

    Declaring cache in a different scope works but it makes our hook go against the principle of a pure function. Besides, we also want to make sure that React helps in cleaning up our mess when we no longer want to make use of the component. We’ll explore useRef to help us in achieving that.

    Memoizing Data With useRef

    “useRef is like a box that can hold a mutable value in its .current property.”

    — React Docs

    With useRef, we can set and retrieve mutable values at ease and its value persists throughout the component’s lifecycle.

    Let’s replace our cache implementation with some useRef magic!

    const useFetch = (url) => {
        const cache = useRef({});
        const [status, setStatus] = useState('idle');
        const [data, setData] = useState([]);
    
        useEffect(() => {
            if (!url) return;
            const fetchData = async () => {
                setStatus('fetching');
                if (cache.current[url]) {
                    const data = cache.current[url];
                    setData(data);
                    setStatus('fetched');
                } else {
                    const response = await fetch(url);
                    const data = await response.json();
                    cache.current[url] = data; // set response in cache;
                    setData(data);
                    setStatus('fetched');
                }
            };
    
            fetchData();
        }, [url]);
    
        return { status, data };
    };
    

    Here, our cache is now in our useFetch hook with an empty object as an initial value.

    Wrapping Up

    Well, I did state that setting the data before setting the fetched status was a good idea, but there are two potential problems we could have with that, too:

    1. Our unit test could fail as a result of the data array not being empty while we’re in the fetching state. React could actually batch state changes but it can’t do that if it’s triggered asynchronously;
    2. Our app re-renders more than it should.

    Let’s do a final clean-up to our useFetch hook.,We’re going to start by switching our useStates to a useReducer. Let’s see how that works!

    const initialState = {
        status: 'idle',
        error: null,
        data: [],
    };
    
    const [state, dispatch] = useReducer((state, action) => {
        switch (action.type) {
            case 'FETCHING':
                return { ...initialState, status: 'fetching' };
            case 'FETCHED':
                return { ...initialState, status: 'fetched', data: action.payload };
            case 'FETCH_ERROR':
                return { ...initialState, status: 'error', error: action.payload };
            default:
                return state;
        }
    }, initialState);
    

    Here, we added an initial state which is the initial value we passed to each of our individual useStates. In our useReducer, we check what type of action we want to perform, and set the appropriate values to state based on that.

    This resolves the two problems we discussed earlier, as we now get to set the status and data at the same time in order to help prevent impossible states and unnecessary re-renders.

    There’s just one more thing left: cleaning up our side effect. Fetch implements the Promise API, in the sense that it could be resolved or rejected. If our hook tries to make an update while the component has unmounted because of some Promise just got resolved, React would return Can't perform a React state update on an unmounted component.

    Let’s see how we can fix that with useEffect clean-up!

    useEffect(() => {
        let cancelRequest = false;
        if (!url) return;
    
        const fetchData = async () => {
            dispatch({ type: 'FETCHING' });
            if (cache.current[url]) {
                const data = cache.current[url];
                dispatch({ type: 'FETCHED', payload: data });
            } else {
                try {
                    const response = await fetch(url);
                    const data = await response.json();
                    cache.current[url] = data;
                    if (cancelRequest) return;
                    dispatch({ type: 'FETCHED', payload: data });
                } catch (error) {
                    if (cancelRequest) return;
                    dispatch({ type: 'FETCH_ERROR', payload: error.message });
                }
            }
        };
    
        fetchData();
    
        return function cleanup() {
            cancelRequest = true;
        };
    }, [url]);
    

    Here, we set cancelRequest to true after having defined it inside the effect. So, before we attempt to make state changes, we first confirm if the component has been unmounted. If it has been unmounted, we skip updating the state and if it hasn’t been unmounted, we update the state. This will resolve the React state update error, and also prevent race conditions in our components.

    Conclusion

    We’ve explored several hooks concepts to help fetch and cache data in our components. We also went through cleaning up our useEffect hook which helps prevent a good number of problems in our app.

    If you have any questions, please feel free to drop them in the comments section below!

    • See the repo for this article →

    References

    • “Introducing Hooks,” React Docs
    • “Getting Started With The React Hooks API,” Shedrack Akintayo
    • “Best Practices With React Hooks,” Adeneye David Abiodun
    • “Functional Programming: Pure Functions,” Arne Brasseur

    (ks, ra, yk, il)

    From our sponsors: How To Create A Custom React Hook To Fetch And Cache Data

    Posted on 13th July 2020Web Design
    FacebookshareTwittertweetGoogle+share

    Related posts

    Archived
    22nd March 2023
    Archived
    18th March 2023
    Archived
    20th January 2023
    Thumbnail for 25788
    Handling Continuous Integration And Delivery With GitHub Actions
    19th October 2020
    Thumbnail for 25778
    A Monthly Update With New Guides And Community Resources
    19th October 2020
    Thumbnail for 25781
    Supercharge Testing React Applications With Wallaby.js
    19th October 2020
    Latest News
    • Archived
      22nd March 2023
    • Archived
      18th March 2023
    • Archived
      20th January 2023
    • 20201019 ML Brief
      19th October 2020
    • Thumbnail for 25788
      Handling Continuous Integration And Delivery With GitHub Actions
      19th October 2020
    • Thumbnail for 25786
      The Future of CX with Larry Ellison
      19th October 2020
    News Categories
    • Digital Marketing
    • Web Design

    Our services

    Website Design
    Digital Marketing
    SEO
    E-commerce
    Social Media Services
    Website Design
    Website Design

    A website is an important part of any business. Professional website development is an essential element of a successful online business.

    We provide website design services for every type of website imaginable. We supply brochure websites, E-commerce websites, bespoke website design, custom website development and a range of website applications. We love developing websites, come and talk to us about your project and we will tailor make a solution to match your requirements.

    You can contact us by phone, email or send us a request through our online form and we can give you a call back.

    More Information

    Digital Marketing
    Digital Marketing

    Our digital marketeers have years of experience in developing and excuting digital marketing strategies. We can help you promote your business online with the most effective methods to achieve the greatest return for your marketing budget. We offer a full service with includes the following:

    1. Social Media Marketing

    2. Email & Newsletter Advertising

    3. PPC - Pay Per Click

    4. A range of other methods are available

    More Information

    SEO
    SEO Services

    SEO is an essential part of owning an online property. The higher up the search engines that your website appears, the more visitors you will have and therefore the greater the potential for more business and increased profits.

    We offer a range of SEO services and packages. Our packages are very popular due to the expanse of on-page and off-page SEO services that they cover. Contact us to discuss your website and the SEO services that would best suit to increase your websites ranking.

    More Information

    E-commerce
    E-commerce Websites

    E-commerce is a rapidly growing area with sales online increasing year on year. A professional E-commerce store online is essential to increase sales and is a reflection of your business to potential customers. We provide professional E-commerce websites custom built to meet our clients requirements.

    Starting to sell online can be a daunting task and we are here to make that journey as smooth as possible. When you work with Cunningham Web Solutions on your E-commerce website, you will benefit from the experience of our team and every detail from the website design to stock management is carefully planned and designed with you in mind.

    More Information

    Social Media Services
    Social Media Services

    Social Media is becoming an increasingly effective method of marketing online. The opportunities that social media marketing can offer are endless and when managed correctly can bring great benefits to every business.

    Social Media Marketing is a low cost form of advertising that continues to bring a very good ROI for our clients. In conjuction with excellent website development and SEO, social media marketing should be an essential part of every digital marketing strategy.

    We offer Social Media Management packages and we also offer Social Media Training to individuals and to companies. Contact us to find out more.

    More Information

    Cunningham Web Solutions
    © Copyright 2025 | Cunningham Web Solutions
    • Home
    • Our Services
    • FAQ's
    • Account Services
    • Privacy Policy
    • Contact Us