graysonmcm
graysonmcm

Reputation: 87

Child component unable to render because Context API comes out as undefined for first render of Child component

I am trying to get a child component to render only when the Context API state that I set up is fully mounted. The Parent component is grabbing from the server and setting the state based on the information received. I keep getting Undefined in the first render, then it re-renders and it comes out as an Array. I want only to render this component so the Child may render it's component without any errors. I've attempted to use the UseEffect but I don't understand it that well..

Here is the Child Component...

import React, { useState, useContext, useEffect } from "react";
import styles from "./bathoneform.module.css";
import Ratings from "../Ratings";
import {
  BathOneFormContext,
  FormQuestionsContext,
} from "../../Store";

function BathOneForm() {
  const [formQuestions, setFormQuestions] = useContext(FormQuestionsContext);
  const [bathOneFormAnswers, setBathOneFormAnswers] = useContext(
    BathOneFormContext
  );
  //setting the array
  const array = formQuestions.bathone;
  const handleChange = (e) => {
    const { name, value } = e.target;
    setBathOneFormAnswers((state) => ({ ...state, [name]: value }));
  };
  console.log(formQuestions);
  return (
    <div>
      <form
        className={styles["BathroomOneFormWrapper"]}
        id="bathroom-one-form"
        onChange={handleChange}
      >
        {array.map((object, index) => {
          return (
            <div className={styles["CheckboxWrapper"]} key={index}>
              <h5>{object}</h5>
              <Ratings
                Question={object}
              />
            </div>
          );
        })}
      </form>
    </div>
  );
}

export default BathOneForm;

Here is the code from Parent Component where I grab from the server...

export default function MoveInForm({ Apartment }) {
  const [formQuestions, setFormQuestions] = useContext(FormQuestionsContext);
  const [state, setState] = useContext(FormSectionsContext);
  const getData = async () => {
    const apartmentName = Apartment.toLowerCase();
    let headers = new Headers();
    headers.append("Content-Type", "application/json");
    headers.append("Accept", "application/json");
    headers.append("Origin", "http://localhost:3000");
    const response = await axios.get(
      `http://localhost:5000/api/${apartmentName}`,
      headers
    );

    await setFormQuestions(response.data[0].bathone);
  };
  useEffect(() => {
    getData();
  }, []);

App.js....

import React, { useState } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router- 
dom";
import styles from "./App.module.css";
import DocumentTitle from "react-document-title";
import MoveInForm from "./pages/MoveInForm";
import NoMatch from "./components/NoMatch";
import Store from "./Store";
import axios from "axios";

function App() {
  const [state, setState] = useState(false);

  //Title Case
  String.prototype.toProperCase = function () {
    return this.replace(/\w\S*/g, function (txt) {
      return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
    });
  };

  const renderMoveIn = (routerProps) => {
    const Name = routerProps.match.params.apartment.toLowerCase();
    let headers = new Headers();
    headers.append("Content-Type", "application/json");
    headers.append("Accept", "application/json");
    headers.append("Origin", "http://localhost:3000");
    axios
      .get(`http://localhost:5000/api/${Name}`, headers)
      .then((res) => {
        setState(true);
      })
      .catch((error) => {
        console.log(error);
      });
    const Apartment = apartmentName.toProperCase();
    return state ? <MoveInForm Apartment={Apartment} /> : <NoMatch />;
  };
  return (
    <DocumentTitle title="Move-In Inspection">
      <Store>
        <Router>
          <div className={styles["App"]}>
            <Switch>
              <Route
                path="/:apartment"
                render={(routerProps) => renderMoveIn(routerProps)}
              />
              <Route component={NoMatch} />
            </Switch>
          </div>
        </Router>
      </Store>
    </DocumentTitle>
  );
}

export default App;

Upvotes: 0

Views: 262

Answers (1)

codemax
codemax

Reputation: 1352

It would appear that the DOM will render first, before the promise is resolved or fulfilled. As the promise runs on the network thread, it will queue the result callback in the micro task thread, which will run after the DOM has completed its first render.

What you can do is

  1. Set a isLoading state in the component, and set it to true for the initial value. And set it to false after the promise resolves.
  2. Create an if condition in the component to load an empty div if the array is empty.

Upvotes: 1

Related Questions