Understanding Context & Array Types

Good Afternoon,

I am in the process of setting up my context for my application but running into an issue with the information display after the initial load. The initial application load then navigating to the products page everything displays correctly, but if I was to reload the page on Products screen the information is missing. If I was to reload the page on the Products screen, then click the navigation click to that same page the information will load correctly.

index.js

//Context
import UserContextProvider from "./components/context/UserContext";
import ProductsContextProvider from "./components/context/ProductsContext";
import CartContextProvider from "./components/context/CartContext";

ReactDOM.render(
  <BrowserRouter>
    <UserContextProvider>
      <ProductsContextProvider>
        <CartContextProvider>
          <App />
        </CartContextProvider>
      </ProductsContextProvider>
    </UserContextProvider>
  </BrowserRouter>,
  document.getElementById("root")
);

app.js

const App = () => {
  return (
    <div className="App">
      <Switch>
        <Route exact path="/" component={HomePageScreen} />
        <Route exact path="/shop/bands" component={BandsPageScreen} />
        <Route exact path="/shop/photo" component={InsertPageScreen} />
        <Route exact path="/shop/products" component={ProductScreen} />
     </Switch>
    </div>
  );
};

productContext.js

import React, { createContext, useState } from "react";
import axios from "axios";

export const ProductsContext = createContext();

const initialState = [];

const ProductsContextProvider = ({ children }) => {
  const GetProducts = async () => {
    await axios.get("/api/products/find/all").then((response) => {
      if (response.data.results) {
        for (var i = 0; i < response.data.results.length; i++) {
          initialState.push(response.data.results[i]);
        }
      }
    });
  };
  GetProducts();
  const [products] = useState(initialState);
  console.log("Setting Product Context");

  return (
    <ProductsContext.Provider value={{ products }}>
      {children}
    </ProductsContext.Provider>
  );
};

export default ProductsContextProvider;

productScreen.js

import { React, useContext, useState, useEffect } from "react";
import axios from "axios";
import HeaderBar from "./modules/header";
import { Button, Image, Icon, Card, Container } from "semantic-ui-react";
import Temp from "../../img/tempphoto.png";

//Context Files
import { UserContext } from "../context/UserContext";
import { ProductsContext } from "../context/ProductsContext";

const ProductScreen = () => {
  const activeScreen = "productScreen";
  const { products } = useContext(ProductsContext);

  console.log(products);

  const allProducts = products.map((product) => (
    <Card raised key={product.sku}>
      <Image src={Temp} wrapped ui={false}></Image>
      <Card.Content>
        <Card.Header>{product.title}</Card.Header>
        <Card.Meta>
          Sku: {product.sku} | Color: {product.color}
        </Card.Meta>
        <Card.Description> {product.description}</Card.Description>
      </Card.Content>
      <Card.Content extra>
        {/* <Button color="green" onClick={(e) => addItemCart(product._id, e)}>
          <Icon name="add to cart"></Icon>Add to Card
        </Button> */}
      </Card.Content>
    </Card>
  ));

  return (
    <>
      <HeaderBar screen={activeScreen} />
      <Container>
        <Card.Group>{allProducts}</Card.Group>
      </Container>
    </>
  );
};

export default ProductScreen;

  1. Reload Product Screen -> First Array after reload
  2. Click the Navigation Link -> Second Array after click (Everything loads correctly here).

enter image description here

Not sure what I am doing wrong but the information is being stored within the context from what I can see.


Solution 1:

You're populating your initialState asynchronously, and setting the products array to use that empty array as its initial state, and then proceed to mutate the state by pushing in items from the response.

You should define the state setter function in addition to the state, and then call the state setter when all the responses complete.

You also need a useEffect so as to only call the API once, on mount, rather than on every re-render.

const ProductsContextProvider = ({ children }) => {
    const [products, setProducts] = useState([]);
    useEffect(() => {
        axios.get("/api/products/find/all")
            .then((response) => {
                const { results } = response.data;
                if (results) {
                    setProducts(results);
                }
            })
        // .catch(handleErrors); // don't forget this part
    }, []);
    return (
        <ProductsContext.Provider value={{ products }}>
            {children}
        </ProductsContext.Provider>
    );
};