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

Compatibility with React Native Web + NextJS #183

Closed
redbar0n opened this issue Jun 7, 2021 · 8 comments
Closed

Compatibility with React Native Web + NextJS #183

redbar0n opened this issue Jun 7, 2021 · 8 comments

Comments

@redbar0n
Copy link
Contributor

redbar0n commented Jun 7, 2021

Using artsy/fresnel with RNW in next.js had a few problems:

necolas/react-native-web#1688 (comment)

1: The wrapping DOM element

  1. the lib appends div as a wrapping element in the DOM, which is not a RNW component so you need to apply hacky styles to the wrapper div at various places if you combine them

RNW has deprecated all uses of the className prop (since RNW's style will overwrite it). But Fresnel uses the className prop (Media.tsx). RNW also requires data-* attributes to be explicitly set with the dataSet prop. So what I think is needed is an option to config Fresnel to work with RNW. Enabling that config could make the <Media> components output a wrapping:

<View dataSet={( fresnel-container: "true" breakpoint: "sm" )}>

Instead of:

<div class="fresnel-container fresnel-at-sm">

So that on web RNW would output the final:

<div data-fresnel-container="true" data-breakpoint="sm">

Which Fresnel could target using a CSS attribute selector like this:

[data-fresnel-container="true"][data-breakpoint="sm"] {
  display:none !important;
}

To hide components for non-applicable breakpoints on the first render client-side.

Actually, I see that you can use the render props form, which can output a custom component other than a <div>.

// in RNW then View will output either for native or for web
<Media greaterThan="sm">
        {(className, renderChildren) => {
          return (
            <View dataSet={( fresnel-container: "true" breakpoint: className )}>
              {renderChildren ? "Hello Native or Web!" : null}
            </View>
          )
        }}
</Media>

That's good. The only problem is that on web, Fresnel CSS will target the class attribute. But RNW doesn't allow setting this directly, as mentioned initially. So if Fresnel's CSS could target data-* attributes, like <div data-fresnel-container="true" data-breakpoint="sm">, with a CSS attribute selector, like mentioned above, it would be compatible with RNW.

Is that possible? Maybe as a config option?

2: Inline responsive styles

  1. the lib provides no easy way to apply responsive inline styles to a component (e.g. <View style={[isMobile && { color: 'red' }] />)

I think speaks to the last con with using Fresnel that you mentioned in your Readme:

The current media query is no longer something components can access; it is determined only by the props of the component they find themselves in.

This makes it harder to style components conditionally.

You asked:

How might we represent a component that gets styled differently at different breakpoints?

And gave an example:

<>
  <Media at="sm">
    {this.getComponent('sm')
  </Media>
  <Media greaterThan="sm">
    {this.getComponent()
  </Media>
</>
getComponent(breakpoint?: string) {
  const sm = breakpoint === 'sm'
  return <Sans size={sm ? 2 : 3} />
}

Maybe this is a stupid question, but for cases like that example, couldn't you simply do this?

<>
  <Media at="sm">
    <Sans size={2} />
  </Media>
  <Media greaterThan="sm">
    <Sans size={3} />
  </Media>
</>

Anyway, I think that

<Sans size={sm ? 2 : 3} />

is actually really nice to have as an optional API. Because it allows very fine-grained targeting of styles, without worrying about larger markup differences. Maybe you have only one component tree / markup, but want to simply tweak some styles for various resolutions (classic responsivity).

So what about this?

When rendering server-side, put the current breakpoints in a DOM node outside of the React tree, using a vanilla JS DOM selector. So that it is accessible on the client, and doesn't interfere with React's hydration (which has to match server-side). Then provide a hook clientWindowMatchesBreakpoint() that only on the client will access the breakpoints from that DOM node, and run something like this:

const isAppliedBreakpoint = bp => {
  if (onClient()) {
    return clientWindowMatchesBreakpoint(bp)
  } else {
    return isUserAgentBreakpoint(bp)
}
const onClient = () => typeof window !== ‘undefined’
const clientWindowMatchesBreakpoint = bp => {
  // should work if window is resized later
  window.matchMedia((max-width: {getBreakpointFromDomNode(bp).width} )).matches
}
const isUserAgentBreakpoint = (bp) => { /* compare with the breakpoint @artsy/detect-reponsive-traits selected based on user-agent (which Fresnel also sent to the client in the DOM node) */ }

Then when styling components conditionally developers could do:

<Sans size={isAppliedBreakpoint("sm") ? 2 : 3} />

Maybe?

@damassi
Copy link
Member

damassi commented Jun 9, 2021

Thanks for the detailed issue @redbar0n. Happy to give a PR for this feature a good look and guide it to completion, but unfortunately we don't have the resources to work on any big new Fresnel additions right now.

@redbar0n
Copy link
Contributor Author

I understand. I don't have time to work on this myself now either, but maybe some time in the future.

Btw, with respects to the API, then a size prop might not be such a good idea, if wanting to future-proof RNW support which supposes it's own API. It is better to follow something like react-primitives which mirrors RNW's API but with even more targets than just Native and Web (so React components can be imported to Figma etc.).

@KeitelDOG
Copy link

Meanwhile, is there a way to add CSS to a Media via style attribute? I see it's only possible with className and I have to inject CSS in <style> tag at some place. But the normal RN style is not showing anything on <Media> component.

@KeitelDOG
Copy link

Ok I see How in the documentation, you have to output your own component instead of passing it as child to avoid the media generated div.

      <Media greaterThan="sm">
        {(mediaClassNames, renderChildren) => {
          return (
            <MySpecialComponent className={mediaClassNames}>
              {renderChildren ? "Hello desktop!" : null}
            </MySpecialComponent>
          )
        }}
      </Media>

@damassi
Copy link
Member

damassi commented Feb 15, 2022

Closing. Will reopen if activity resumes here.

@damassi damassi closed this as completed Feb 15, 2022
@boylec
Copy link

boylec commented Aug 1, 2024

seems one can't use render props in RSC.

image

@AareFabrik
Copy link

Is there a possibility to make this work with react-native-web? Any progress on this?

@redbar0n
Copy link
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants