I have a function I run on my NextJS application's homepage that takes in the props received from getStaticProps. The only problem is that on iOS and Mac, using the Safari/DuckDuckGo browser, the page will occasionally (about every 5/6 cacheless reloads in incognito) load -all- content....but none of it is visible.

You can still copy and paste the text, you can push down on the images and THEN see them, but the only things visible are the header background color and HTML/Body background color. HOWEVER, it works 100% of the time in Chrome, on all operating systems.

If you think that the problem lies w/i the length and/or methodology of this function, can you please provide me with guidance on how to condense it, or use better practices?

And if you think the problem lies elsewhere, I'd love to know where to start looking. The console doesn't show any problems whatsoever.

Here's the component that is being rendered:

import React, { useState, useEffect } from "react";
import organizeMenu from "../models/orgMenu";
import {
  Box,
  Heading,
  SimpleGrid,
  Divider,
  Center,
  Container,
} from "@chakra-ui/react";
import ItemCard from "./molecules/ItemCard";
import { useMenuStore } from "../state/store";
import SearchBar from './search/SearchBar';

const HomeContainer = ({props}) => {
 

  const { setStateModifierLists } = useMenuStore();
  const modifierLists = props.data.objects.filter(
    (object) => object.type === "MODIFIER_LIST"
  );

  useEffect(() => {
    setStateModifierLists(modifierLists);
  }, []);

  const itemList = props.data.objects.filter(
    (object) => object.type === "ITEM"
  );

  const categories = props.data.objects.filter(
    (object) => object.type === "CATEGORY"
  );

  const loadThis = organizeMenu(props);

  const bfast = loadThis.bfast;
  const entrees = loadThis.entrees;
  const drinks = loadThis.drinks;
  console.log(`bfast`, bfast);

  const [loaded, setLoaded] = useState(false);

  const handleLoad = (e) => {
    console.log("loaded");
    setLoaded(true);
  };
    return (
        
             <Box w="100%">
      <Container>
        <SearchBar categories={categories} itemList={itemList} />
      </Container>

      
      <Heading ml={3}>Breakfast</Heading>
      <Divider />
      <Center>
        <SimpleGrid
          m="0 auto"
          alignItems="center"
          spacing={6}
          p="2"
          columns={[1, null, 2, null, 3]}
        >
          {bfast.map((b) => (
            <ItemCard modifierLists={modifierLists} key={b.id} item={b} />
          ))}
        </SimpleGrid>
      </Center>
      <Heading ml={3}>Entrees</Heading>
      <Divider />
      <Center>
        <SimpleGrid
          m="0 auto"
          alignItems="baseline"
          onLoad={handleLoad}
          spacing={6}
          p="2"
          columns={[1, null, 2, null, 3]}
        >
          {entrees.map((e) => (
            <>
              <ItemCard modifierLists={modifierLists} key={e.id} item={e} />
            </>
          ))}
        </SimpleGrid>
      </Center>
      <Heading ml={3}>Drinks</Heading>
      <Divider />
      <Center>
        <SimpleGrid
          m="0 auto"
          alignItems="stretch"
          onLoad={handleLoad}
          spacing={6}
          p="2"
          columns={[1, null, 2, null, 3]}
        >
          {drinks.map((d) => (
            <ItemCard modifierLists={modifierLists} key={d.id} item={d} />
          ))}
        </SimpleGrid>
      </Center>
    </Box>
        
    )
}

export default HomeContainer

This is the function I wrote to organize the data for rendering:

export default function organizeMenu(props) {
  
    // Segment menu items
    let menuItems = [];
  
    menuItems = props.data.objects.filter((object) => object.type === "ITEM");

    //Segment menu item images
    let itemImages = [];
    itemImages = props.data.objects.filter((object) => object.type === "IMAGE");

    //Segment Categories
    let categories = [];
    categories = props.data.objects.filter(
      (object) => object.type === "CATEGORY"
    );

    //Segment Modifier Lists
    let modifierLists = [];
    modifierLists = props.data.objects.filter(
      (object) => object.type === "MODIFIER_LIST"
    );
    
  
    // Merge data to provide better mapping and ordering process
  
    //Looping through menuItems and itemImages to combine fields into menuItems for ease of mapping data to components
  
    for (let x = 0; x < menuItems.length; x++) {
      menuItems[x].modifiers = [];
      menuItems[x].imageData = {
        url: "https://via.placeholder.com/250",
      };
      for (let y = 0; y < itemImages.length; y++) {
        if (menuItems[x].imageId === itemImages[y].id) {
          // console.log(`Match: ${menuItems[x].imageId}`);
          menuItems[x].imageData = itemImages[y].imageData;
        } else {
          // console.log("No match");
        }
      }
    }
  

  
    // Next, we're going to tie the actual modifiers to the menuItem objects, rather than having to map them separately.
  
    for (let mm = 0; mm < menuItems.length; mm++) {
      menuItems[mm].availableModifiers = [];
      if (menuItems[mm].itemData.modifierListInfo) {
        for (
          let xx = 0;
          xx < menuItems[mm].itemData.modifierListInfo.length;
          xx++
        ) {
          if (menuItems[mm].itemData.modifierListInfo[xx].enabled === true) {
            // console.log("enabled");
            for (let zz = 0; zz < modifierLists.length; zz++) {
              if (
                menuItems[mm].itemData.modifierListInfo[xx].modifierListId ===
                modifierLists[zz].id
              ) {

                for (
                  let xo = 0;
                  xo < modifierLists[zz].modifierListData.modifiers.length;
                  xo++
                ) {
  
                  menuItems[mm].availableModifiers.push(
                    modifierLists[zz].modifierListData.modifiers[xo]
                  );
                }
  

              }
            }
          } else {
            // console.log("no mods");
          }
        }
      }
    }
  
//If modifier has a price, map the price according to the needs of square's api.
  
    for (let qu = 0; qu < menuItems.length; qu++) {
      for (let xz = 0; xz < menuItems[qu].availableModifiers.length; xz++) {
        if (menuItems[qu].availableModifiers[xz].modifierData.priceMoney) {
          menuItems[qu].availableModifiers[xz].basePriceMoney = {
            ...menuItems[qu].availableModifiers[xz].modifierData.priceMoney,
          };
        } else {
          menuItems[qu].availableModifiers[xz].modifierData.basePriceMoney = {
            amount: "0",
            currency: "USD",
          };
          menuItems[qu].availableModifiers[xz].basePriceMoney = {
            amount: "0",
            currency: "USD",
          };
        }
      }
    }
  
  
// Set primary variation (default)
  
    for (let h = 0; h < menuItems.length; h++) {
      if (
        menuItems[h].itemData.variations[0] &&
        menuItems[h].itemData.variations[0].isDeleted === false
      ) {
        menuItems[h].primaryVariation = {
          ...menuItems[h].itemData.variations[0],
          isChosen: false,
        };
      }
    }
  

  
    // Merging "CATEGORIES" with menuItems
  
    for (let q = 0; q < menuItems.length; q++) {
      menuItems[q].categoryName;
      for (let w = 0; w < categories.length; w++) {
        if (menuItems[q].itemData.categoryId === categories[w].id) {
          menuItems[q].categoryName = categories[w].categoryData.name;
        }
      }
    }
  
    // Separating items into arrays based on category...
  
    let breakfastItems = [];
    breakfastItems = menuItems.filter(
      (object) => object.categoryName === "Breakfast"
    );

  
    let entreeItems = [];
    entreeItems = menuItems.filter((object) => object.categoryName === "Entree");
  
    let drinkItems = [];
    drinkItems = menuItems.filter((object) => object.categoryName === "Drinks");

// The object to be returned... Items are rendered from these objects.
    const catalog = {
      bfast: breakfastItems,
      entrees: entreeItems,
      drinks: drinkItems,
    };
  
 
    return catalog;
  }

When it works: Image 1

When it doesn't: Image 1 Image 2

1

There are 1 best solutions below

2
On

Your issue seems due an understanding of the rendering cycle and what causes components to render. Any of these changes will cause the component to render again:

  1. Update to props
  2. Update to state
  3. Context value changes

Your const loadThis = organizeMenu(props); is being run synchronously every time when one of the above changes (could be triggered by parent component too) and is wasteful and causing UI components to render unnecessarily.

  const loadThis = organizeMenu(props);

  const bfast = loadThis.bfast;
  const entrees = loadThis.entrees;
  const drinks = loadThis.drinks;
  console.log(`bfast`, bfast);

Move these into state, useEffect, or ideally run the organizeMenu functionality at build time using getStaticProps and pass the data to the component as props.

See the simplified example with comments below. Every time you click the button, organizeMenu(props) is run and will output to the console and result in the list rendering again.

export default function IndexPage({ categories, menu }) {
  const [selected, setSelected] = useState(0);
  const [menuList, setMenuList] = useState();

  const organizeMenu = (data) => {
    console.info('running organizeMenu');

    return data.map((category) => {
      return {
        id: category,
        name: category.toString()
      };
    });
  };

  // bad, this will be run every time there is a state change i.e. when the button is clicked and setSelected
  const foo = organizeMenu(categories);

  const handleOnClick = () => {
    setSelected(selected + 1);
  };

  useEffect(() => {
    console.info('running useEffect');
    setMenuList(
      categories.map((category) => {
        return {
          id: category,
          name: category.toString()
        };
      })
    );
  }, [categories]);

  return (
    <div>
      Hello World. {selected}
      <br />
      {menu && menu.map((menuItem) => <li key={menuItem.id}>{menuItem.name}</li>)}
      <br />
      {menuList && menuList.map((menuItem) => <li key={menuItem.id}>{menuItem.name}</li>)}
      <hr />
      <button onClick={handleOnClick}>Make it so</button>
    </div>
  );
}

export async function getStaticProps(context) {
  const categories = [1, 2, 3];

  // prep menu here so run at build time
  const menu = categories.map((category) => {
    return {
      id: category,
      name: category.toString()
    };
  });

  return {
    props: {
      categories,
      menu
    }
  };
}

CodesandBox Demo