Home > Mobile >  How to render random object from array in React?
How to render random object from array in React?

Time:03-31

the problem is I have a component and an array with objects inside. I would like to choose a random object from the array and render it. Here is the code I already have:

import React, { useEffect, useState } from 'react';

const DATA = [
  {
    key1: 'value1',
    key2: ['string1', 'string2', 'string3']
  },
  {
    key1: 'value2',
    key2: ['string4', 'string5', 'string6']
  },
  {
    key1: 'value3',
    key2: ['string7', 'string8', 'string9']
  }
];

const MyComponent = () => {
  const [randomData, setRandomData] = useState({});

  const getRandomObject = (array) => {
    const randomObject = array[Math.floor(Math.random() * array.length)];
    setRandomData(randomObject);
  };

  useEffect(() => {
    getRandomObject(DATA);
  }, []);

  return (
    <div>
      <h2>{randomData.key1}</h2>
      {randomData.key2.map((item) => (
        <div>
          <input type='checkbox' id={item} value={item} />
          <label htmlFor={item}>{item}</label>
        </div>
      ))}
    </div>
  );
}
 
export default MyComponent;

The browser throws me an error:

TypeError: Cannot read properties of undefined (reading 'map')

Does anyone know what's wrong? Thanks in advance

CodePudding user response:

Your useEffect callback runs after the initial render (not before). So on the first render randomData.key2 will be undefined, so you can't call .map() on it. You have a few options to fix this, but the most appropriate is to remove the useEffect() and set the data to a random value when you first call useState():

const getRandomObject = (array) => {
  const randomObject = array[Math.floor(Math.random() * array.length)];
  return randomObject;
};

const MyComponent = () => {
  const [randomData, setRandomData] = useState(() => getRandomObject(DATA));

  return (
    ...
  );
}

See working example below:

const { useState } = React;

const DATA = [
  {
    key1: 'value1',
    key2: ['string1', 'string2', 'string3']
  },
  {
    key1: 'value2',
    key2: ['string4', 'string5', 'string6']
  },
  {
    key1: 'value3',
    key2: ['string7', 'string8', 'string9']
  }
];

const getRandomObject = (array) => {
  const randomObject = array[Math.floor(Math.random() * array.length)];
  return randomObject;
};

const MyComponent = () => {
  const [randomData, setRandomData] = useState(() => getRandomObject(DATA));

  return (
    <div>
      <h2>{randomData.key1}</h2>
      {randomData.key2.map((item) => (
        <div>
          <input type='checkbox' id={item} value={item} />
          <label htmlFor={item}>{item}</label>
        </div>
      ))}
    </div>
  );
}
 
ReactDOM.render(<MyComponent />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>

If you pass a function to useState(), react will invoke that function the first time useState() is called (and not for subsequent rerenders) and set the state to the returned value, which will allow you to set your state to a random value for the intial render.


Another option to fix this would be to check that randomData.key2 is defined before using .map() on it. You can do this by checking if randomData.key2 has a value (ie: isn't undefined) before you call .map() on your array. You can do this with optional chaining (?.):

randomData.key2?.map(...)

The above checks that randomData isn't undefined or null before .map() is invoked on it. You can think of this short-circuiting behaviour to be the similar to the below code:

randomData.key2 && randomData.key2.map(...)

This would also work, and was commonly used before optional chaining was introduced to the language. Out of all of these options, I would suggest the first for your case, but if you find that you need to use useEffect() (eg: you have some asynchronous code that obtains DATA), then one of the other options would work better for that case.

  • Related