JedWatson / JedWatson/react-select

Styles not Loading in Remix

Open
#5,937 12 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

issue/bug-unconfirmed
Dominant language
TypeScript
Stars
28k
Forks
4.1k
PR merge metrics
No merged PRs in 30d

Description

Hi There - I'm sorry, I'm not sure how to replicate this in your code sandbox. I have a suspicion that this somehow relates to Remix and SSR, but can't get to the root of it.

Basically, the form, doesn't display properly. If you change something in code, and dev mode reloads the page, then everything shows correctly. If you do an actual browser load or refresh, the form styles don't load correctly.

Screenshot 2024-07-22 at 12 42 13 PM

Based on my research, I have tried waiting for hydration, and I've tried using a useEffect to make sure it only loads in the client to separate from SSR, but neither works.

This is the controlled form field that I'm rendering:

import { useQuery } from '@apollo/client'
import { Control, Controller, FieldValues } from 'react-hook-form'
import AsyncSelect from 'react-select/async'
import { CSSObjectWithLabel, GroupBase, OptionsOrGroups } from 'react-select'

interface OptionType {
  label: string
  value: string
}

interface OptionItem {
  id: string
  name?: string
  firstName?: string
  lastName?: string
}

interface RelationSelectProps {
  field: {
    key: string
    options: {
      document?: any // Specify the GraphQL document type if available
      dataType?: string
      filter?: (items: OptionItem[]) => OptionItem[]
      selectOptionsFunction?: (items: OptionItem[]) => OptionType[]
      multi?: boolean
    }
  }
  control: Control<FieldValues, unknown>
}

function label(item: { id: string; name?: string; firstName?: string; lastName?: string }): string {
  if (item?.name) {
    return item.name
  }
  if (item?.firstName && item?.lastName) {
    return `${item.firstName} ${item.lastName}`
  }
  if (item?.firstName && !item?.lastName) {
    return item.firstName
  }
  return item.id
}

function defaultOptionsMap(items: OptionItem[]): OptionType[] {
  return items?.map?.((option) => ({ value: `${option.id}`, label: label(option) }))
}

export function RelationSelect({ field, control }: Readonly<RelationSelectProps>) {
  const { data, loading, refetch } = useQuery(field.options.document)

  let dataList: OptionItem[] =
    field.options.dataType && !loading ? data?.[field.options.dataType] : [{ value: '', label: 'Loading...' }]

  if (field.options.filter && !loading) {
    dataList = field.options.filter(dataList)
  }

  function getDefaultOptions(
    dataList: any[],
    options: { selectOptionsFunction?: (data: any[]) => OptionType[] },
  ): OptionType[] {
    if (dataList && dataList.length > 0) {
      if (options.selectOptionsFunction) {
        return options.selectOptionsFunction(dataList)
      } else {
        return defaultOptionsMap(dataList)
      }
    } else {
      return [{ value: '', label: 'No Matching Data Found' }]
    }
  }

  async function getStorageOptions(inputText: string): Promise<OptionsOrGroups<OptionType, GroupBase<OptionType>>> {
    const res = await refetch({ input: { search: inputText } })
    const data = field.options.dataType ? res.data[field.options.dataType] : null
    return getOptions(data)
  }

  function getOptions(data: any): OptionType[] | OptionsOrGroups<OptionType, GroupBase<OptionType>> {
    if (data) {
      if (field.options.selectOptionsFunction) {
        return field.options.selectOptionsFunction(data)
      } else {
        return defaultOptionsMap(data)
      }
    } else {
      return [{ value: '', label: 'No Matching Data Found' }]
    }
  }

  const customStyles = {
    control: (provided: CSSObjectWithLabel) => ({
      ...provided,
      backgroundColor: 'white',
      fontSize: '14px',
    }),
    singleValue: (provided: CSSObjectWithLabel) => ({
      ...provided,
      fontSize: '14px',
      color: '#64748b',
    }),
    option: (provided: CSSObjectWithLabel) => ({
      ...provided,
      fontSize: '14px',
      color: '#64748b',
    }),
  }

  return (
    <Controller
      control={control}
      name={field.key}
      render={({ field: { onChange, value } }) => (
        <AsyncSelect
          name={field.key}
          instanceId={field.key}
          value={value}
          key={field.key}
          defaultOptions={getDefaultOptions(dataList, field.options)}
          loadOptions={getStorageOptions}
          onChange={onChange}
          isLoading={loading}
          isMulti={field.options.multi}
          classNamePrefix="rs"
          styles={customStyles}
        />
      )}
    />
  )
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the provided RelationSelect component and reproduce the difference between a full browser refresh and a development reload in a Remix SSR setup. Inspect how AsyncSelect, classNamePrefix, and styles behave during the initial load; done means the form styles render correctly after a full refresh as well as after hot reload.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.