Search code examples
javascriptreactjsreact-hooksuse-effect

functional component is not re-rendering after state change in React


I have a functional component that retrieves data from an http request on page load and updates the state to store that data using useState. I then try to map out a nested array (polls.pollOptions) from the data, I get an error: "TypeError: Cannot read property 'map' of undefined". If i remove the map function and check the React component extension, I can see that the state was updated and it has the correct data. So why doesn't this render to the screen & why receive an error when the data is there to map through? Heres my component:

import React, { useState, useEffect } from "react";
import axios from "axios";
import { useParams } from "react-router";

export const EditPolls2 = () => {
  const { _id } = useParams();
  const [polls, setPolls] = useState([]);

  useEffect(() => {
    axios.get(`/polls/${_id}`).then((p) => {
      setPolls(p.data);
    });
  }, [_id]);

  return (
    <div>
      <p>edit polls 2</p>
      <div>
        <ul>
          {polls.pollOptions.map((p) => (
            <li key={p.pollId}>{p.option}</li>
          ))}
        </ul>
      </div>
    </div>
  );
};

Solution

  • Your current initial state is an empty array, and you are trying to access pollOptions, which is a property that doesn't exist on an empty array.

    If you don't need anything, but pollOptions, leave the initial state as an empty array, use polls and not polls.pollOptions when mapping the data, and make sure to set pollOptions as the state:

    export const EditPolls2 = () => {
      const { _id } = useParams();
      const [polls, setPolls] = useState([]);
    
      useEffect(() => {
        axios.get(`/polls/${_id}`).then((p) => {
          setPolls(p.data.pollOptions); // set the pollOptions array as the state
        });
      }, [_id]);
    
      return (
        <div>
          <p>edit polls 2</p>
          <div>
            <ul>
              {polls.map((p) => ( // use polls and not pollOptions
                <li key={p.pollId}>{p.option}</li>
              ))}
            </ul>
          </div>
        </div>
      );
    };
    

    If you do need the entire object (data) in the state, don't use initial state, and use optional chaining (?.) when mapping the array. If the state is undefined, the mapping would be skipped:

    export const EditPolls2 = () => {
      const { _id } = useParams();
      const [polls, setPolls] = useState(); // no initial state
    
      useEffect(() => {
        axios.get(`/polls/${_id}`).then((p) => {
          setPolls(p.data);
        });
      }, [_id]);
    
      return (
        <div>
          <p>edit polls 2</p>
          <div>
            <ul>
              {polls.pollOptions?.map((p) => ( // optional chaining
                <li key={p.pollId}>{p.option}</li>
              ))}
            </ul>
          </div>
        </div>
      );
    };