Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Styles not Loading in Remix #5937

Open
justinhandley opened this issue Jul 22, 2024 · 5 comments
Open

Styles not Loading in Remix #5937

justinhandley opened this issue Jul 22, 2024 · 5 comments
Labels
issue/bug-unconfirmed Issues that describe a bug that hasn't been confirmed by a maintainer yet

Comments

@justinhandley
Copy link

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}
        />
      )}
    />
  )
}
@justinhandley justinhandley added the issue/bug-unconfirmed Issues that describe a bug that hasn't been confirmed by a maintainer yet label Jul 22, 2024
@TheAlexPorter
Copy link

TheAlexPorter commented Jul 25, 2024

I'm in the same boat. There are a few similar issues open. I'm going to attempt the solution here in #3309 (comment) and see if by making each component with my own styles manually applied I can get past the issue.

Others:

#5710

#3680

@TheAlexPorter
Copy link

TheAlexPorter commented Jul 25, 2024

Update: I tried the method here where you add an emotion provider and that did the trick! Wrapping my app with a CacheProvider is all I had to do.

I don't like that I had to add an extra provider for styling I'm not using directly, but this beats the pain of having to migrate away from react-select.

Here is the snippet from my remix app:

// root.tsx

import createCache from '@emotion/cache'
import { CacheProvider } from '@emotion/react'

function AppWithProviders() {
	const data = useLoaderData<typeof loader>()
	const cache = createCache({
		key: 'test',
		prepend: false,
	})

	return (
		<HoneypotProvider {...data.honeyProps}>
			<CacheProvider value={cache}>
				<UserDataProvider
					data={{
						user: data.user,
						theme: data.requestInfo.userPrefs.theme,
					}}
				>
					<App />
				</UserDataProvider>
			</CacheProvider>
		</HoneypotProvider>
	)
}

From this Isssue: #3680 (comment)

I hope this helps!

@justinhandley
Copy link
Author

Hey @TheAlexPorter - thanks so much for that - I did try wrapping the whole root of my app in an emotion provider and it didn't seem to have any effect. Is there anything else that you had to do to make this work? Do I have to consume the provider somewhere?

@TheAlexPorter
Copy link

I don't do anything other than wrap my app with the cache provider 🤔 The only other thing I did before attempting these solutions was upgrade react to 18.3.1 and remix-run to 2.10.3

@grohart
Copy link

grohart commented Aug 6, 2024

Same problem here with Astro JS (strangely, it's working well in my Remix projects), when using Astro with ViewTransitions component in the page layout headers, styles of react-select are broken when navigating to another page and going back to the page where react-select is called

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
issue/bug-unconfirmed Issues that describe a bug that hasn't been confirmed by a maintainer yet
Projects
None yet
Development

No branches or pull requests

3 participants