14

I want to show a dropdown in React using the following enumeration. The idea is to prevent using random strings as keys in my <select>. Keys should be constrained to Stooges.larry, Stooges.curly and Stooges.moe:

const Stooges = {
  larry: "Larry Fine",
  curly: "Curly Howard",
  moe: "Moe Howard"
};

Here's the code for my select, which works just fine:

<select value={stooge} onChange={handleChange}>
  {Object.keys(Stooges).map(key => (
    <option key={key} value={key}>
      {Stooges[key]}
    </option>
  ))}
</select>

The problem comes in when I am trying to set the initial value of the dropdown - I have to use a hard coded key:

const [stooge, setStooge] = React.useState('larry');

I can soften this a bit by picking the first key:

const keys = Object.keys(Stooges);
const [stooge, setStooge] = React.useState(keys[0]);

However, this still feels wrong. I can't tell from key[0] who am I picking.

Is there a better way to deal with enumerations in this use case? I have tried string enums in TypeScript, but they have the same issue:

enum Stooges {
  larry = "Larry Fine",
  curly = "Curly Howard",
  moe = "Moe Howard"
}

Please see my CodeSandbox here.

1
  • I found out that keyof Stooges would only work for type Stooges = {...}, so I deleted my answer :( Commented Apr 11, 2019 at 21:58

3 Answers 3

10

For an enum Animals:

enum Animal{
  Cat= "Cat",
  Dog= "Dog",
  Hamster= "Hamster"
}

We can define a generic function getEnumKeys ,that gets the array of keys of an enum:

// Inspired from https://github.com/microsoft/TypeScript/issues/30611#issuecomment-570773496
function getEnumKeys<
    T extends string,
    TEnumValue extends string | number,
>(enumVariable: { [key in T]: TEnumValue }) {
    return Object.keys(enumVariable) as Array<T>;
}

We can then map through the list of keys of Animal returned by the getEnumKeys function in the select.

import React from "react";

enum Animal {
  Cat = "Cat",
  Dog = "Dog",
  Hamster = "Hamster"
}

export default function App() {
  const [currentAnimal, setCurrentAnimal] = React.useState<Animal>(Animal.Cat);
  return (
    <select
      value={currentAnimal}
      onChange={(e) => {
        setCurrentAnimal(Animal[e.target.value as keyof typeof Animal]);
      }}
    >
      {getEnumKeys(Animal).map((key, index) => (
        <option key={index} value={Animal[key]}>
          {key}
        </option>
      ))}
    </select>
  );
}

function getEnumKeys<
   T extends string,
   TEnumValue extends string | number,
>(enumVariable: { [key in T]: TEnumValue }) {
    return Object.keys(enumVariable) as Array<T>;
}

Code Sandbox: https://codesandbox.io/s/unruffled-sinoussi-pr7dpf?file=/src/App.tsx:0-600

Sign up to request clarification or add additional context in comments.

4 Comments

Got a typescript error unless I changed <T> to <T extends Object>
i guess, better not using the index as the key?
In this case we cannot use the index as a key for the enum as the only keys we can use are the ones defined in the enum itself e.g. "Cat", "Dog" and "Hamster"
If you use <T extends Object> you lose the return type safety, I made an edit with a suggestion that seems to work better.
3

For lack of a better solution, I have taken the following approach. I am at least able to pick the right enum by name: Stooges.larry.id:

const Stooges = {
  larry: {
    id: "larry",
    name: "Larry Fine"
  },
  curly: {
    id: "curly",
    name: "Curly Howard"
  },
  moe: {
    id: "moe",
    name: "Moe Howard"
  }
};

function App() {
  const [stooge, setStooge] = React.useState(Stooges.larry.id);

  const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    setStooge(event.target.value);
  };

  return (
    <div className="App">
      <select value={stooge} onChange={handleChange}>
        {Object.keys(Stooges).map(key => (
          <option key={key} value={key}>
            {Stooges[key].name}
          </option>
        ))}
      </select>
    </div>
  );
}

Here's the new CodeSandbox: https://codesandbox.io/embed/wqj2q94o2k

1 Comment

using Object.values(Stooges).map((value, index) => (...) instead wouldn't have fixed your issue?
-1

this works for me

enum Theme {
  LIGHT = 'light',
  DARK = 'dark',
}
const App: React.FC = () => {
  const [theme, setTheme] = useState<Theme>(Theme.LIGHT);

  const onChange = (ev: React.ChangeEvent<HTMLSelectElement>) => {
    const { value } = ev.target;
    setTheme(value as Theme);
  };
  return (
    <div className={`app ${theme}`}>
      <select name="theme" onChange={onChange} id="theme">
        <option value={Theme.LIGHT}>light</option>
        <option value={Theme.DARK}>dark</option>
      </select>
    </div>
  );
};

1 Comment

While this answer could work, it can be inferred that the OP is looking to have the enum dynamically generated

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.