@artsy/fresnel

An SSR compatible approach to CSS media query based responsive layouts for ...

README

@artsy/fresnel


The Fresnel equations describe the reflection of light when incident on an

interface between different optical media.


– https://en.wikipedia.org/wiki/Fresnel_equations

Installation


  1. ```sh
  2.   # React 18+
  3.   yarn add @artsy/fresnel

  4.   # React 17
  5.   yarn add @artsy/fresnel@6
  6. ```

Overview


When writing responsive components it's common to use media queries to adjust
the display when certain conditions are met. Historically this has taken place
directly in CSS/HTML:

  1. ```css
  2. @media screen and (max-width:
  3.   .my-container {
  4.     width: 100%;
  5.   }
  6. }
  7. @media screen and (min-width:
  8.   .my-container {
  9.     width: 50%;
  10.   }
  11. }
  12. ```

  1. ```html
  2. <div class="my-container" />
  3. ```

By hooking into a breakpoint definition, @artsy/fresnel takes this declarative
approach and brings it into the React world.

Basic Example


  1. ```tsx
  2. import React from "react"
  3. import ReactDOM from "react-dom"
  4. import { createMedia } from "@artsy/fresnel"

  5. const { MediaContextProvider, Media } = createMedia({
  6.   // breakpoints values can be either strings or integers
  7.   breakpoints: {
  8.     sm: 0,
  9.     md: 768,
  10.     lg: 1024,
  11.     xl: 1192,
  12.   },
  13. })

  14. const App = () => (
  15.   <MediaContextProvider>
  16.     <Media at="sm">
  17.       <MobileApp />
  18.     </Media>
  19.     <Media at="md">
  20.       <TabletApp />
  21.     </Media>
  22.     <Media greaterThanOrEqual="lg">
  23.       <DesktopApp />
  24.     </Media>
  25.   </MediaContextProvider>
  26. )

  27. ReactDOM.render(<App />, document.getElementById("react"))
  28. ```

Server-side Rendering (SSR) Usage


The first important thing to note is that when server-rendering with
@artsy/fresnel, all breakpoints get rendered by the server. Each Media
component is wrapped by plain CSS that will only show that breakpoint if it
matches the user's current browser size. This means that the client can
accurately start rendering the HTML/CSS while it receives the markup, which is
long before the React application has booted. This improves perceived
performance for end-users.

Why not just render the one that the current device needs? We can't accurately
identify which breakpoint your device needs on the server. We could use a
library to sniff the browser user-agent, but those aren't always accurate, and
they wouldn't give us all the information we need to know when we are
server-rendering. Once client-side JS boots and React attaches, it simply washes
over the DOM and removes markup that is unneeded, via a matchMedia call.

SSR Example


First, configure @artsy/fresnel in a Media file that can be shared across
the app:

  1. ```tsx
  2. // Media.tsx

  3. import { createMedia } from "@artsy/fresnel"

  4. const ExampleAppMedia = createMedia({
  5.   breakpoints: {
  6.     sm: 0,
  7.     md: 768,
  8.     lg: 1024,
  9.     xl: 1192,
  10.   },
  11. })

  12. // Generate CSS to be injected into the head
  13. export const mediaStyle = ExampleAppMedia.createMediaStyle()
  14. export const { Media, MediaContextProvider } = ExampleAppMedia
  15. ```

Create a new App file which will be the launching point for our application:

  1. ```tsx
  2. // App.tsx

  3. import React from "react"
  4. import { Media, MediaContextProvider } from "./Media"

  5. export const App = () => {
  6.   return (
  7.     <MediaContextProvider>
  8.       <Media at="sm">Hello mobile!</Media>
  9.       <Media greaterThan="sm">Hello desktop!</Media>
  10.     </MediaContextProvider>
  11.   )
  12. }
  13. ```

Mount `` on the client:

  1. ```tsx
  2. // client.tsx

  3. import React from "react"
  4. import ReactDOM from "react-dom"
  5. import { App } from "./App"

  6. ReactDOM.render(<App />, document.getElementById("react"))
  7. ```

Then on the server, setup SSR rendering and pass `mediaStyle` into a `