JedWatson / react-select

The Select Component for React.js
https://react-select.com/
MIT License
27.59k stars 4.12k forks source link

Some css styles are not created in production but work in development #3309

Open jjlauer opened 5 years ago

jjlauer commented 5 years ago

Hello,

We are hitting an odd css/emotion issue in production, while the component works great in development. We've tried this on v2.0.0-v.2.2.0 and its the same issue with all of them.

The dynamic css rules for the outer divs never get created in the emotion stylesheet. They are, however, set on the divs in the DOM. The rest of the dynamic css rules (e.g. on the options when you click on the control) do get created in the emotion stylesheet. This only happens on a few pages in our app, while other parts work as expected. Here is a screenshot of the the DOM when the page loads.

screenshot from 2019-01-01 19-22-05

You'll notice that the select outermost div was assigned a classname of css-10nd86i, but that css rule does not actually exist in the DOM. Using the javascript console to dump out the emotion stylesheet, you'll notice in this screenshot that the rules are totally empty in the stylesheet.

screenshot from 2019-01-01 19-23-24

Now if you click on the select control to open it, the css rules for the options list all get created:

screenshot from 2019-01-01 19-43-12

So the options get styled correctly, but the primary select component does not. I've tried various versions of emotion, react-select, react, etc., but the issue still persists. We've had to swap out this component in production for the time being, but would like to figure this out. Any help would be greatly appreciated.

gwyneplaine commented 5 years ago

@jjlauer are there any key similarities between the pages where this happens? Any information you could provide us to narrow down a reproducible case for this would be helpful; and if possible a codesandbox reproduction.

This usually happens when you're trying to instantiate emotion across contexts, like in an iframe or through server side rendering for example.

jjlauer commented 5 years ago

Made some headway. On the page in our app that works, it's a very complicated page and the user would only see the Select component if they were already on the page for a few seconds (e.g. javascript all loaded, before Select ever rendered).

On the page this issue occurs the Select component is rendered immediately when the browser loads the page. If I delay the Select from being rendered for even just 100ms (via setTimeout) then it renders correctly. Any ideas on what could be causing something like that and why even just 100ms delay in React rendering the Select component would fix it?

ryanrombough commented 5 years ago

@jjlauer Did you ever figure this out? I'm having the same issue. The selector is properly styled in a development environment, but in a production build none of the emotion stylesheets get injected into the head.

jjlauer commented 5 years ago

@ryanrombough Unfortunately no. Only workaround is what i mentioned above -- you have to delay rendering of the widget so it doesn't occur on the first react render().

ryanrombough commented 5 years ago

@jjlauer I just tried implementing a 500ms delay on rendering but it did not solve the issue for me. Guess I'll keep digging. Thanks!

ryanrombough commented 5 years ago

I have narrowed the cause of my issue down to the minification step in my Webpack v4 build. Here's the stack overflow question I created to try to find out why this is happening.

jjlauer commented 5 years ago

Hmm.. did turning off minimize in webpack fix your production issue?

I also had issues w/ the css not being injected into the header, but adding a delay (of any use of react-select) on the initial react render fixed it. It feels like a race condition between Emotion fully initializing before react-select uses it.

On Wed, Apr 3, 2019 at 6:10 PM Ryan Rombough notifications@github.com wrote:

I have narrowed the cause of my issue down to the minification step in my Webpack v4 build. Here's the stack overflow question https://stackoverflow.com/questions/55505056/why-would-webpack-4-minification-prevent-styles-for-react-select-component .

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/JedWatson/react-select/issues/3309#issuecomment-479677022, or mute the thread https://github.com/notifications/unsubscribe-auth/AAjwAtXg0d-2BhpFcJqVxX0oGVuaiV4Dks5vdSbOgaJpZM4ZmF0W .

ryanrombough commented 5 years ago

Yea, but that's not really a solution for us because our bundle is too large when not minified. I tried implementing the delay you suggested but could not get it to work.

ChayanJana1233 commented 4 years ago

Hi @jjlauer I am facing the same CSS not being applied in Production. If you can kindly help me with the code snippet for setting the delay in rendering the react-select it would be extremely helpful!! Thank you.

hemedani commented 4 years ago

I have the same issues

NearHuscarl commented 4 years ago

I managed to fix the issue by adding classNamePrefix which seems to force react-select to add the classNames to the sub-components in production

<Select
  classNamePrefix='select'
  {...}
/>

Before

<div class=" css-1hwfws3">
  ...
</div>

After

<div class="select__value-container select__value-container--is-multi select__value-container--has-value css-1hwfws3">
  ...
</div>
bladey commented 4 years ago

Thanks for letting us know @NearHuscarl!

kunambi commented 3 years ago

@NearHuscarl's solution didn't solve my problem. Have used classNamePrefix for a while, and the issue remains the same.

@jjlauer how did you implement the delay?

@bladey is there a way to "force" the CSS to be loaded programatically?

Getting desperate here 😅

jjlauer commented 3 years ago

Here is how I implemented a very simple delay to rendering. Here's the main react component (e.g. top-level entrypoint). I originally was rendering the ReportsPanel component, but developed a simple wrapper component i called DelayedRender that is now responsible for rendering it instead.

class ReportsPage extends Component {

  constructor() {
    super();
  }

  render() {
    const { dispatch, query, user, accounts } = this.props;

    return (
      <DelayedRender>
        <ReportsPanel
          dispatch={dispatch}
          query={query}
          user={user}
          accounts={accounts}
          active={true}
        />
      </DelayedRender>
    );
  }
}

Here is DelayedRender:

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';

export default class DelayedRender extends PureComponent {

  constructor() {
    super();

    this.state = {
      render: false
    };
  }

  componentDidMount() {
    const { time } = this.props;

    setTimeout(() => this.setState({ render: true }), (time || 400));
  }

  render() {
    const { children } = this.props;
    const { render } = this.state;

    if (!render) {
      return null;
    }

    return children;
  }
}
wegry commented 3 years ago

This seems like it's still present in react-select@4.2.1.

rushilsrivastava commented 3 years ago

This problem has reappeared for us in 4.2.1

wegry commented 3 years ago

@rushilsrivastava are you folks using react-windowed-select as well as react-select? My team has a hunch it may be the former causing issues.

Confusingly, downgrading just react-windowed-select to use react-select 3 seems to resolve the issue.

rushilsrivastava commented 3 years ago

@wegry We aren't using react-windowed-select, still having a hard time pinpointing what package/code change caused this since downgrading to 4.1.0 doesn't seem to work for us either. We will give react-select v3 a shot though and report back here.

For the time being, we are using @jjlauer's solution.

ebonow commented 3 years ago

Greetings @wegry , @rushilsrivastava , @jjlauer , or anyone else,

Would any of you be willing to share your package.json to help us recreate the issue?

Methuselah96 commented 3 years ago

@wegry Can you look at your yarn.lock or package-lock.json and see if anything depends on Emotion besides react-select?

Methuselah96 commented 3 years ago

@wegry I looked into your package.json and it looks like you have react-windowed-select@^2.0.2 installed which references react-select@3. Can you try upgrading that to react-windowed-select@3.0.0 and see if it makes a difference? react-windowed-select@2 depends on react-select@3, but react-windowed-select@3 depends on react-select@4, so that mismatch could be a potential cause of the issue.

wegry commented 3 years ago

@Methuselah96 let me post my whole package.json and yarn.lock as a gist. The version above is current after the downgrade. I'll send the version reproing this instead. My mistake.

wegry commented 3 years ago

Just so it's clear that I added a gist (not sure if edits notify), https://gist.github.com/wegry/5b2d97db8257b459b95c08c1e3153dd5. @Methuselah96

Methuselah96 commented 3 years ago

@wegry Yeah, I'm not seeing anything suspicious. At this point the only thing to do is to try to recreate the issue in a public repo so that we can dig into it more. Are you using Webpack? Are there CSS loaders that you're using that could be affecting this?

wegry commented 3 years ago

@Methuselah96 I've unfortunately been unable to repro this at a smaller scale than the app it's occuring in.

Are you using Webpack?

Yeah, webpack@5.26.3.

I'm thinking in our case, it's because we have two+ chunks that pull in react-select. The way we repro the styles visibly being broken (and not present in the dom) is

  1. Prod build
  2. cache free refreshing chunk A where we import react-select's default export.
  3. Navigating to a page (in a different chunk) where a wrapped component containing three different versions of react-select (one of which is 'react-select/creatable') and react-windowed-select is used.

There might be multiple emotion caches being used in our case?

Are there CSS loaders that you're using that could be affecting this?

I would think CSS loaders wouldn't affect this, especially since react-select doesn't package css, right?

erkez commented 3 years ago

I had the issue with the styles not working with production build after some of my components were inserted into a shadow root. What worked for me was to add CacheProvider from emotion somewhere in the application root.

Please see https://github.com/JedWatson/react-select/issues/3680#issuecomment-809518245 for more details.

Hope this helps.

piotrrussw commented 3 years ago

Any update or solution on this issue? Apart from delay rendering

arda- commented 3 years ago

I had this issue with react-select 4.3.0, and @NearHuscarl 's solution https://github.com/JedWatson/react-select/issues/3309#issuecomment-678728542 worked for me!

stack:

issue:

solution:

myorkgitis commented 3 years ago

I was able to come up with a simple workaround, but can only guess as to why it works.

TLDR I hid a <Select> in the _app.tsx outside of the <Component>.

// _app.tsx
function MyApp({Component, pageProps}: AppProps) {
    return (
        <>
        <div>
            // This is the User Context Provider from auth0
            <UserProvider>
                <div className="app-container">
                    <Component {...pageProps} />
                </div>
            </UserProvider>

            // Hide this select from view
            <div style={{display: "none"}}>

                // This select is put here to get the emotion styles rendered in production */}
                <Select
                    instanceId={"rendered-select"}
                    value={{label: "none", value: "none"}}
                    options={[{label: "none", value: "none"}]}
                    onChange={() => null}
                />
            </div>
        </div>
        </>
    )
}

export default MyApp

My guess as to why this works I had this issue running react-select 4.3.1 on next.js and tried the previously mentioned solutions but they did not work (delayed rendering https://github.com/JedWatson/react-select/issues/3309#issuecomment-714758497 worked occasionally, but not every time).

Since the delayed rendering worked occasionally, I thought it could be a caching issue (or some other race condition related to the inner workings of react/next.

I am using auth0 and wrapping every page in the UserProvider from auth0 in my _app.tsx. UserProvider then provides context via a useUser.

// Every page needing app user context uses this wrapper
const PageWrapper = ({children, serializedDsUser} : PageWrapperProps) => {
    // authUser is the auth0 user
    const { user: authUser, isLoading } = useUser()

    // dsUser is the app's user object
    const dsUser = serializedDsUser ? JSON.parse(serializedDsUser) : null

    const context: UserSessionContextValues = {
        authUser: authUser ? authUser : null,
        dsUser,
        isLoggedIn: authUser ? true : false
    }

    return (
        <div>
            {isLoading ?
                <AppLoader isLoading />
                :
                <UserSessionContext.Provider value={context}>
                {children}
                </UserSessionContext.Provider>
            }
        </div>
    )
}

export default PageWrapper

Each page does not render until user data returned by useUser completes loading. Without spending many more hours tracing this bug down, I'm guessing something having to do with conditionally rendering the pages and subsequently any nested <Select> components based on user state and SSR is causing some sort of race condition with the caching (as mentioned by @jjlauer https://github.com/JedWatson/react-select/issues/3309#issuecomment-479678164).

Hope this saves someone some time.

mattvb91 commented 3 years ago

None of the above with hiding in _app.tsx or setting a classname prefix works for me on nextjs currently on client route push. Has anyone been able to get this working recently?

delayed rendering doesnt work either i cant get the css to show up if im including the select conditionaly based on state

kunambi commented 3 years ago

None of the above with hiding in _app.tsx or setting a classname prefix works for me on nextjs currently on client route push. Has anyone been able to get this working recently?

delayed rendering doesnt work either i cant get the css to show up if im including the select conditionaly based on state

Had the same issue - nothing helped.

I only managed to make it work by, on dev server I inspected each and every element copied the CSS to my own CSS-files. Make sure that you lock the exact version so a future update doesn't break the layout.

mattvb91 commented 3 years ago

@kunambi thanks for the tip. Im assuming this just went into your global css? Will give this a try later

mattvb91 commented 3 years ago

@kunambi how did you turn off the dynamic classnames? just by giving it a classPrefix?

kunambi commented 3 years ago

@kunambi how did you turn off the dynamic classnames? just by giving it a classPrefix?

Yes, I pretty much hardcoded everything to make sure it works all the time. Also locked the version number to 3.1.0

Fley commented 3 years ago

For us the issue occurred when the chrome tab using react-select was left idle for some time (could be hours). When going back to this tab the issue was there.

I implemented the same kind of solution as @kunambi. On my side I chose to bypass the emotion styles by passing my "own internal components" via the components property. The components I use are actually the original lib components but where I force my own css classes (see example below) 🤷

It was a bit painful to do, but since we've release it we don't have the issue anymore.

import { components } from 'react-select';

export const MultiValueLabel = (props) => {
  return (
    <components.MultiValueLabel
      {...props}
      innerProps={{
        ...props.innerProps,
        className: "multiValueLabel",
      }}
    />
  );
};
mattvb91 commented 3 years ago

Thanks for the input guys, im afraid I don't have enough deep knowledge / lacking time to implement a fix so ive just had to go down the route of not using next/link and making the app do a hard refresh on the pages im using the component. Maybe its fixed in the next major version.

EandrewJones commented 2 years ago

I am currently encountering this in v5.2.2. None of the mentioned solutions work.

mattvb91 commented 2 years ago

Update for everyone it ended up being this library that was causing the issue for me: https://github.com/google-map-react/google-map-react

There must be some onload event in that library that triggered some css removal. I cant exactly remember what the fix was but I think I needed to use a different import for the component I was using.

The way I figured it out was start a clean project with only react-select and then start adding libraries from my other project until I started getting the same issue again.

marcomartinscastanho commented 2 years ago

I'm having the same issue appearing in v3.2.0. In my project, we were already using the classNamePrefix as suggested by @NearHuscarl.

[local | prod] image

I can see that the class is added to the component in both cases, it just seems like it isn't loaded in production.

jvannistelrooy commented 1 year ago

I had a similar problem with the react-select 5.7.0, Next.js 13.3, and server-side rendering. I used the storybook example mentioned in the react-select documentation about classNames here: https://react-select.com/styles#the-classnames-prop

useMemo in this part of the example doesn't work well with SSR:

const EmotionCacheProvider = ({ children }: { children: React.ReactNode }) => {
  const cache = React.useMemo(
    () =>
      createCache({
        key: 'with-tailwind',
        insertionPoint: document.querySelector('title')!,
      }),
    []
  );

  return <CacheProvider value={cache}>{children}</CacheProvider>;
};

Removing the useMemo() and returning createCache() directly fixed the problem for me.

mstajic-bosch commented 6 months ago

Hi folks, its been a while since the last comment, any updates on this, I have tried delayed rendering and it does not work. Using in a SPA mode, using 5.8.0 version