Bogdan Gromov
Bogdan Gromov

Reputation: 3

Dynamic Option does not trigger the Select Component in SolidJS. (Playground includes)

I am using SolidJS and SUID ( Solid material UI ) library.

I am trying to use the same example as in the SUID documentation.

https://suid.io/components/select

import { For, createResource, createSignal } from 'solid-js';
import { Select, MenuItem } from '@suid/material'; // or "solid-ui", based on the library's exports

function Post() {
  const [postsResource, { mutate, refetch }] = createResource(async () => {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  });

  // State for the selected option
  const [selectedOption, setSelectedOption] = createSignal('any');

  return (
    <div>
      <h1>Posts</h1>
      {postsResource.loading && <p>Loading...</p>}
      {postsResource.error && <p>Error: {postsResource.error.message}</p>}
      {
        <>
          <Select
            // defaultValue={'any'}
            // value={selectedOption()}
            onChange={(e) => {
              console.log('onChange', e.currentTarget);
              setSelectedOption(e.currentTarget.value);
            }}
          >
            <MenuItem value="any">Choose any...</MenuItem>
            <MenuItem value="option1">option1</MenuItem>
            <MenuItem value="option2">option2</MenuItem>
            <For each={postsResource()}>
              {(post) => <MenuItem value={post.id}>{post.title}</MenuItem>}
            </For>
          </Select>
        </>
      }
    </div>
  );
}

export default Post;

In this case, the Options that have values like any, option1 and option2 (static options) are selectable, but other are not.

Also I should mention, when I use static options (code below) a Select works fine.

  const options = [
    {id: 1, label: 'Option 1'},
    {id: 2, label: 'Option 2'},
    {id: 3, label: 'Option 3'},
  ];

But when I use dynamic options (fetch, createResource, promise, etc), something goes wrong. There are main problems:

  1. Chosen Option does not change Select value
  2. onChange handler always receives similar value
  3. More than two Select changes cause console errors:
MUI: A component is changing the uncontrolled value state of Select to be controlled.
Elements should not switch from uncontrolled to controlled (or vice versa).
Decide between using a controlled or uncontrolled Select element for the lifetime of the component.
The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.

I tried to:

  1. Set a defaultValue to Select;
  2. Set a defaultValue to createSignal;
  3. Set a tabIndex, because I noticed that in For each MenuItem has tabIndex=-1

tabIndex


Video/gif problem on imgur

SUID issue on github


Playground

Provide you with playground for your experiments.


What my expectations are:

Upvotes: 0

Views: 557

Answers (1)

snnsnn
snnsnn

Reputation: 13698

The value from createResource will be undefined until the request resolves to a value. So, the component For receives undefined initially. You should restructure your component accordingly. It is best to use Switch/Match with resource.state value for conditional rendering.

Edit: Problem is with how SUID handles event. Select implements controlled data flow that somehow does not work well reactive values.

Try using a static value like the index number, it works as expected. As soon as you use a reactive value, it emit errors.

import { Match, Switch, createResource, createSignal } from 'solid-js';
import { Select, MenuItem } from '@suid/material';

function Post() {
  const [data, { mutate, refetch }] = createResource(async () => {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  });

  const [value, setValue] = createSignal('any');

  const handleChange = (event) => {
    console.log(event.target.value);
    setValue(event.target.value);
    console.log(event.currentTarget.value);
  };

  return (
    <div>
      <h1>Posts {value()}</h1>
      <Switch>
        <Match when={data.state === 'pending'}>Loading</Match>
        <Match when={data.state === 'errored'}>Failed</Match>
        <Match when={data.state === 'ready'}>
          <Select
            labelId="demo-simple-select-label"
            id="demo-simple-select"
            value={value()}
            label="Value"
            onChange={handleChange}
          >
            {data().map((post, index) => (
              <MenuItem value={index}>{post.title}</MenuItem>
            ))}
          </Select>
        </Match>
      </Switch>
    </div>
  );
}

export default Post;

Upvotes: 0

Related Questions