1

I have a set of select menus and I am trying to change a value when I select an option using onChange={updateValue} event. When I first select an option, the value is not being updated in the select menu. It only changes the second time I try to choose an option. Not sure what I am doing wrong.

Edit: I did some more research (OnChange event using React JS for drop down) and I believe I need the value of the select to be updated as well, using setState. I cant figure out how to do it without having a variable for each value and set the state again.

let selectMenus = [
  {
    id: 'id1',
    name: 'name1',
    label: 'label1',
    value: '0',
    options: [
      {
        text: 'All ages',
        value: '0',
      },
      {
        text: '35 - 37 yrs',
        value: '1',
      },
    ],
    buttonLabel: 'Refresh',
  },
  {
    id: 'id2',
    name: 'name2',
    label: 'label2',
    value: '1',
    options: [
      {
        text: 'All ages',
        value: '0',
      },
      {
        text: '45 - 50 yrs',
        value: '1',
      },
    ],
    buttonLabel: 'Refresh',
  },
];

const [url, setUrl] = useState('http://localhost:5000/selectDropdowns1');

const updateValue = () => {
  setUrl('http://localhost:5000/selectDropdowns2');
};

<form>
  {selectMenus.map((select) => (
    <div key={select.id} className='select-container'>
      <label htmlFor={select.id}>{select.label}</label>
      <select id={select.id} name={select.name} value={select.value} onChange={updateValue}>
        {select.options.map((option) => (
          <option value={option.value} key={uuid()}>
            {option.text}
          </option>
        ))}
      </select>
      <button>{select.buttonLabel}</button>
    </div>
  ))}
</form>;
Acdn
  • 568
  • 2
  • 13
  • 26

1 Answers1

2

The problem is that when you provide onChange prop to select component it become a controlled component.

For more information: React Docs - Forms #controlled components

When you dealing with controlled components you must provide a value to it and when onChange triggerd it should update that value to work properly. Since you did not provide the full code, I imagine you have an array of select menus and options attached to it.

So in this case every select component should have own onChange method and own value to work properly. To achive this we should create another component for only Select Options. Like this;

function SelectComponent({ optionList, onSelected }) {
  const [value, setValue] = useState();

  const updateValue = ({ target }) => {
    setValue(target.value);
    if (onSelected) onSelected(target.value);
  };

  return (
    <>
      <label htmlFor={optionList.id}>{optionList.label}</label>
      <select
        id={optionList.id}
        name={optionList.name}
        value={value}
        onChange={updateValue}
      >
        {optionList.options.map((option) => (
          <option value={option.value} key={uuid()}>
            {option.text}
          </option>
        ))}
      </select>
      <button>{optionList.buttonLabel}</button>
    </>
  );
}

This component accepts to props; optionList and onSelected

optionList is the list of options to render

onSelected is a method that we call when user select and option

On main component, we should change the select section with our select component with props optionList and onSelected

  return (
    <div>
      {selectMenus.map((select) => (
        <div key={select.id} className="select-container">
          <SelectComponent optionList={select} onSelected={updateValue} />
        </div>
      ))}
    </div>
  );

So overall code is like this:

import { useState } from "react";
import { v4 as uuid } from "uuid";

export default function App() {
  const [url, setUrl] = useState();

  const updateValue = (value) => {
    setUrl(value);
  };

  const selectMenus = [
    {
      id: 1,
      label: "Menu 1",
      name: "menu1",
      buttonLabel: "Menu 1",
      options: [
        {
          text: "option 1",
          value: "option1"
        },
        {
          text: "option 2",
          value: "option2"
        },
        {
          text: "option 3",
          value: "option3"
        }
      ]
    },
    {
      id: 2,
      label: "Menu 2",
      name: "menu2",
      buttonLabel: "Menu 2",
      options: [
        {
          text: "option 1",
          value: "option1"
        },
        {
          text: "option 2",
          value: "option2"
        },
        {
          text: "option 3",
          value: "option3"
        }
      ]
    },
    {
      id: 3,
      label: "Menu 3",
      name: "menu3",
      buttonLabel: "Menu 3",
      options: [
        {
          text: "option 1",
          value: "option1"
        },
        {
          text: "option 2",
          value: "option2"
        },
        {
          text: "option 3",
          value: "option3"
        }
      ]
    }
  ];

  return (
    <div className="App">
      <h1>URL Value: {url}</h1>
      {selectMenus.map((select) => (
        <div key={select.id} className="select-container">
          <SelectComponent optionList={select} onSelected={updateValue} />
        </div>
      ))}
    </div>
  );
}

function SelectComponent({ optionList, onSelected }) {
  const [value, setValue] = useState();

  const updateValue = ({ target }) => {
    setValue(target.value);
    if (onSelected) onSelected(target.value);
  };

  return (
    <>
      <label htmlFor={optionList.id}>{optionList.label}</label>
      <select
        id={optionList.id}
        name={optionList.name}
        value={value}
        onChange={updateValue}
      >
        {optionList.options.map((option) => (
          <option value={option.value} key={uuid()}>
            {option.text}
          </option>
        ))}
      </select>
      <button>{optionList.buttonLabel}</button>
    </>
  );
}

Working example is overhere codesandbox

Closery
  • 828
  • 9
  • 14
  • Thanks so much for that. Yes, you were right. Meanwhile I have update my code with selectMenus array. I also added the value to the select. I was having the value inside the object, so I was setting it {select.value}. I was hoping i can just loop through this array of select menus and get it from there, then update it with setState but was blocked – Acdn Jan 11 '22 at 14:29
  • 1
    Yeah you could also do it by adding value to the array(most of the time people solve these problems like this including me after a long ago) but it will kinda be hard to update the value in that long array and also it could cause some performance problems since you update the whole array, it would re-render all components so doing with another component and own states will be better :) Also if this answer was your solution, can you please mark that as "solution" so it can benefit other people. – Closery Jan 11 '22 at 14:42
  • Hey, I wanted to show you another thing I tried. I also need to print out the values of all select menus each time I do a change and i have a default value too: https://stackblitz.com/edit/react-5yoau2. If you know another way to do it, I would appreciate if you tell me. I would have liked to create the object for values iterating through the select menus so if there are more then i don't need to change anything – Acdn Mar 12 '22 at 00:18