React Context Provider and Consumer logs / renders twice from Axios API

Viewed 21

The code works but console log shows it renders twice. I want to reduce unnecessary rerenders/API fetching. Solutions tried include async-await, try-catch, useMemo, React.Memo, StrictMode in index.js (just quadruples the console log entries).

I prepared a codesandbox.

This is the console log: Console logs twice every time

In the sandbox I use a fake API compared to my actual project code which uses Axios. The outcome is the same.

SearchContext (Context Provider) fetches from API once, then BlogList (Context Consumer) fetches again and console logs the second time.

React Version: 18.2.0

SearchContext.js

import React, { createContext, useState, useEffect, useMemo, useCallback } from 'react';
import axios from 'axios';

const SearchContext = createContext();

export const SearchContextProvider = ({ children }) => {

    const [blog, setBlog] = useState([]);
    const value = useMemo(() => ([ blog, setBlog ]), [blog]);
    // const value = React.memo(() => ([ blog, setBlog ]), [blog]);

    // I try 'useMemo, React.memo' (above):
    

    // I try 'try-catch':
    // const retrieveBlog = () => {
    //     try {
    //         const response = axios.get(`${process.env.REACT_APP_API_URL}/api/v2/pages/?type=blog.Blog&fields=image,description`)
    //         .then(response => {
    //                 setBlog(response.data.items);
    //                 console.log('thisiscontextprovider(blog):', blog)
    //             })
    //     }
    //     catch (err) {
    //         console.log(err);
    //     }
    // }

    // useEffect(() => {
    //     retrieveBlog();
    // }, []);

    
    // I try 'useCallback, if, async-await, try-catch' :
    const retrieveBlog = useCallback(async () => {
        if (blog.length === 0){
            try {
                const response = await axios.get(`${process.env.REACT_APP_API_URL}/api/v2/pages/?type=blog.Blog&fields=image,description`)
                .then(response => {
                        setBlog(response.data.items);
                        console.log('thisiscontextprovider(blog):', blog)
                    })
            }
            catch (err) {
                console.log(err);
            }
        }
    })

    useEffect(() => {
        retrieveBlog()
    }, []);

    // I try 'async-await':
    // useEffect(() => {
    //     async function retrieveBlog() {
    //         try {
    //             const response = await axios.get(`${process.env.REACT_APP_API_URL}/api/v2/pages/?type=blog.Blog&fields=image,description`)
    //             .then(response => {
    //                     setBlog(response.data.items);
    //                     console.log('thisiscontextprovider(blog):', blog)
    //                 })
    //         }
    //         catch (err) {
    //             console.log(err);
    //         }
    //     }
    //     retrieveBlog()
    // }, []);

    return (
        <div>
            <SearchContext.Provider value={value}>
            {/* <SearchContext.Provider value={[blog, setBlog]}> */}
                {children}
                {console.log('from context provider return:', blog)}
            </SearchContext.Provider>
        </div>
        
    );
};

export default SearchContext;

BlogList.js

import React, { useContext, useCallback, useEffect } from 'react';
import styled from 'styled-components';

import SearchContext from "../contexts/SearchContext";
import { SearchContextProvider } from "../contexts/SearchContext";

const BlogCard = styled.div`
`;

const BlogList = () => {

  const [blog, setBlog] = useContext(SearchContext);

  return (
    <div>
      <SearchContextProvider>
        BlogList
        <BlogCard>
            {blog.map((blog) => (
              <li key={blog.id}>
                {blog.title}
              </li>
            ))}
        </BlogCard>
      </SearchContextProvider>
    </div>
    
  );
};

export default BlogList;

App.js

import React from "react";
import { Route, Routes } from "react-router-dom";
import styled from "styled-components";

import BlogList from "./components/BlogList";

import { SearchContextProvider } from "./contexts/SearchContext";

const Wrapper = styled.h1``;

function App() {
  return (
    <Wrapper>
      <p>
        This CodeSandbox is for my question on StackOverflow. Console logs
        twice.
      </p>
      <SearchContextProvider>
        <Routes>
          <Route exact path="" element={<BlogList />} />
          {/* <Route exact path="/blog" element={<BlogList />} /> */}
        </Routes>
      </SearchContextProvider>
    </Wrapper>
  );
}

export default App;

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

reportWebVitals();

I am keen to understand why this happens, rather than just solving it.

0 Answers
Related