Search code examples
reactjsreact-hooksreact-contextuse-context

Uncaught TypeError: Object is not iterable (cannot read property Symbol(Symbol.iterator)) in my React project


I am new to context API and trying to get my code to work. I am getting error:

Uncaught TypeError: Object is not iterable (cannot read property Symbol(Symbol.iterator))

Would some of you good people help with this?

The initialUserState logs an object the looks like this: {name: nanny}

My context file is:

import { createContext, useState } from "react";

const initialUserState = JSON.parse(localStorage.getItem("user"));
console.log(initialUserState);
const initialFormValues = {
  video: "Video.mp4",
  hourly: "",
  ageRange: [],
  car: "",
  languages: [],
  homework: "",
  license: "",
  preference: "",
  vaccination: "",
  radius: "",
  booked: "",
  fileUpload: "file.jpg",
};

export const Global = createContext();

export default function GlobalContext({ children }) {
  const [state, setState] = useState(initialUserState);
  const [values, setValues] = useState(initialFormValues); //setting form state
  const [radioSelected, setRadioSelected] = useState();

  const changeState = (newState = {}) => {
    const tempState = { ...state, ...newState };
    setState(tempState);
    localStorage.setItem("user", JSON.stringify(tempState));
  };

  return (
    <Global.Provider
      value={[
        { ...state },
        changeState,
        values,
        setValues,
        radioSelected,
        setRadioSelected,
      ]}
    >
      {children}
    </Global.Provider>
  );
}

then where I am getting state from, the initialFormValues does not log in the console:

const HourlyRate = () => {

  //context API

  const [values, setValues] = useContext(GlobalContext); **//this is where the error is being flagged from**

  //handle form value changes
  const handleChange = (e) => {
    e.preventDefault();

    const { name, value } = e.target;
    setValues({ ...values, [name]: value });
  };

  return (
    <div className="sub-settings-div">
      <Link className="back" to="/video-upload">
        <span>
          <ArrowBackIcon />
        </span>
        Back
      </Link>
      <h3 className="sub-settings-header">{t("common.title")}</h3>
      <h2 className="sub-settings-sub-header">{t("hourly.subTitle")}</h2>
      <p className="range">(10.45 &euro; - 20 &euro;) </p>
      <form className="input">
        <TextField
          className="input-field"
          required
          id="outlined-required"
          label="Hourly Rate"
          name="hourly"
          value={values.hourly}
          onChange={handleChange}
        />
      </form>
      <div className="nav-btns">
        <Link to="/video-upload" className="prev-link">
          <button className="prev">{t("buttons.back")}</button>
        </Link>
        <Link to="/age-range" className="next-link">
          <button className="next">
            {t("buttons.next")}
            <span>
              <ArrowForwardIcon />
            </span>
          </button>
        </Link>
      </div>
    </div>
  );
};

export default HourlyRate;

Solution

  • Couple issues I see:

    1. You are passing the context provider component GlobalContext to the useContext hook, const [values, setValues] = useContext(GlobalContext);
    2. The context value is an array, so when using array destructuring the order of the array elements matters.

    Use the correct variable for the context. You may want to give the context the GlobalContext name, and rename the provider something like GlobalProvider. I suggest also using an object for the context value to eliminate the array element order destructuring issue.

    export const GlobalContext = createContext();
    
    export default function GlobalProvider({ children }) {
      const [state, setState] = useState(initialUserState);
      const [values, setValues] = useState(initialFormValues); //setting form state
      const [radioSelected, setRadioSelected] = useState();
    
      const changeState = (newState = {}) => {
        const tempState = { ...state, ...newState };
        setState(tempState);
        localStorage.setItem("user", JSON.stringify(tempState));
      };
    
      return (
        <Global.Provider
          value={{
            state,
            changeState,
            values,
            setValues,
            radioSelected,
            setRadioSelected,
          }}
        >
          {children}
        </Global.Provider>
      );
    }
    

    Then assuming your main App component is correctly wrapped in the GlobalProvider, use object destructuring from the hook.

    const { values, setValues } = useContext(GlobalContext);