Skip to content

Commit c39c347

Browse files
committed
New: SSR-safe API
1 parent 48bbe00 commit c39c347

13 files changed

Lines changed: 2082 additions & 1644 deletions

File tree

README.md

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ The values that you provide are the "maximum pixel-widths" for that screen class
5555

5656
### 2. Generate your custom Responsive System with `createResponsiveSystem`
5757

58-
Here, you'll configure ResponsiveSystem with your breakpoints.
58+
Here, you'll configure Responsive System with your breakpoints.
5959

6060
```js
6161
/* responsiveSystem.js/ts */
@@ -68,13 +68,12 @@ const breakpoints = {
6868

6969
export const { ScreenClassProvider, useResponsiveValue } = createResponsiveSystem({
7070
breakpoints,
71-
defaultScreenClass: 'lg',
7271
});
7372
```
7473

7574
Let's break this down a little bit.
7675

77-
We're calling `createResponsiveSystem` with our own custom breakpoints, and we get back a `ScreenClassProvider` (keeps track of the current screen class), and a hook called `useResponsiveValue` (creates a value that changes based on the screen class). We also provided a `defaultScreenClass` so that Responsive System knows which screen class to use when it can't find a `window` to measure (e.g. during SSR or headless testing). We immediately export everything so that we can import it across our app.
76+
We're calling `createResponsiveSystem` with our own custom breakpoints, and we get back a `ScreenClassProvider` (keeps track of the current screen class), and a hook called `useResponsiveValue` (creates a value that changes based on the screen class). Then, we export everything so that we can import it across our app.
7877

7978
### 3. Render the ScreenClassProvider near the root of your app
8079

@@ -172,12 +171,57 @@ If you'd like to enable desktop/mobile-first cascading, you can pass the `cascad
172171
```js
173172
export const { ScreenClassProvider, useResponsiveValue } = createResponsiveSystem({
174173
breakpoints,
175-
defaultScreenClass: 'lg',
176174
cascadeMode: 'mobile-first', // or "desktop-first"
177175
});
178176
```
179177

180-
## Goodies
178+
## Server-Side Rendering
179+
180+
Server-Side rendering is a bit tricky because we don't have access to the user's device to measure their screen. Because we can't automatically determine the proper screen class during SSR, you must "manually" provide an `initialScreenClass` for Responsive System to render.
181+
182+
```js
183+
export const { ScreenClassProvider, useResponsiveValue } = createResponsiveSystem({
184+
breakpoints,
185+
initialScreenClass: 'lg', // only add this if you're doing Server-Side Rendering
186+
});
187+
```
188+
189+
Now here's the downside: if you guess the initial screen class incorrectly, your users may see a quick flash of the wrong layout before React kicks in (hydrates the app) and adjusts to the proper screen class. In order to avoid that experience, you have a few options:
190+
191+
### Don't SSR
192+
193+
This is the easiest approach. If you don't SSR, then you don't have to worry about manually figuring out the initial screen class. We can figure it out for you and we can make sure that there's no flash of an incorrect screen class
194+
195+
### Client-Only Components
196+
197+
You can render some placeholder content (like a spinner/skeleton/glimmer) during SSR and then render the correct content once the code gets to the client. Showing a loading experience is better than showing an incorrect layout and then shifting it.
198+
199+
Here's a quick implementation that you could use to render a placeholder on the server:
200+
201+
```jsx
202+
function ClientOnly({ placeholder, children }) {
203+
const isOnClient = useRef(false);
204+
useEffect(() => {
205+
// useEffect only runs on the client
206+
isOnClient.current = true;
207+
}, []);
208+
// will return `placeholder` on the server and `children` on the client
209+
return isOnClient.current ? children : placeholder;
210+
}
211+
// usage
212+
<ClientOnly placeholder={<PlaceholderContent />}>
213+
<ResponsiveComponent />
214+
</ClientOnly>;
215+
```
216+
217+
### Smart Server
218+
219+
This route is tricky, but if you want to try it, here are some ideas:
220+
221+
- At a minimum you can check the User-Agent Request header to get an idea of what type of device is being used (mobile vs laptop/desktop) and use that info to make an educated guess.
222+
- If your user is navigating from page to page in your app and you control the links, you could append width and height query params to each request before it goes out. The first page they hit wouldn't have the query params, but all subsequent pages would. And it's technically possible for someone to resize their screen after they click a link, so it's not bulletproof.
223+
224+
## Extras
181225

182226
### `useScreenClass` Hook
183227

@@ -190,7 +234,7 @@ Want direct access to the screen class itself? Sure! We're already providing it
190234

191235
export const {
192236
ScreenClassProvider,
193-
responsive,
237+
useResponsiveValue,
194238
useScreenClass, // export the hook
195239
} = createResponsiveSystem(...);
196240

@@ -205,10 +249,6 @@ const Button = props => {
205249
};
206250
```
207251

208-
## Server-Side Rendering
209-
210-
If `react-responsive-system` cannot access the `window`, it will use the `defaultScreenClass` that you set in the configuration object. When the code is re-hydrated on the client, it will adjust to the actual screen class.
211-
212252
## Under the Hood
213253

214254
For folks who are curious about the implementation:
@@ -233,9 +273,17 @@ So the middle number actually represents a major breaking change for v0 versions
233273

234274
In this section, we'll cover those major changes so that folks can decide whether or not they want to update.
235275

236-
### v0.9
276+
### v0.10 - Server-Side Rendering support
277+
278+
In this update we focused on supporting server-side rendering (SSR). Fundamentally, we don't have access to the user's screen during server rendering, so we can't automatically determine the proper screen class for the server-rendered content.
279+
280+
In order to continue to provide an optimal client-side rendering experience while also supporting a bug-free server-side rendering experience we made `defaultScreenClass` optional and we also renamed it to `initialScreenClass` to make its purpose more clear. Making it optional allows us to dynamically decide whether to use our client-side optimized implementation or to switch to the server-side optimized implementation.
237281

238-
There are significant breaking changes between the 0.8.X and 0.9.X lines. With that said, the 0.8.X line went for 2+ years with no major changes and is quite stable, so there's really not much reason to upgrade if it's working for your project!
282+
IMPORTANT: if your app is client-side only, don't include `initialScreenClass` in your setup! We'll automatically determine the initial screen class and ensure that the content renders properly (without shifting). If you provide an `initialScreenClass` and it's incorrect, your users might see your components shift from one screen class to another when they load the page.
283+
284+
### v0.9 - useResponsiveValue replaces responsive(Component)
285+
286+
> Note: the 0.8.X line went for 2+ years with no major changes and is quite stable, so there's really not much reason to upgrade if it's working for your project!
239287
240288
The reason that we haven't moved to 1.0 yet is that the API just hasn't felt quite right. It was a bit clunky and there were a few hacks behind the scenes to get it to work the way that we wanted it to work. 0.9 eliminates those hacks, improves the performance, and generally gets much closer to what we feel could be worthy of 1.0; the downside is that we needed to make some fundamental (breaking) changes.
241289

@@ -254,5 +302,3 @@ const responsiveSlidesToShow = useResponsiveValue(4, { onSmallScreens: 2 });
254302

255303
<Carousel slidesToShow={responsiveSlidesToShow} />;
256304
```
257-
258-
It's quite difficult to gather community feedback for a change like this, but we're very interested to hear how y'all feel about the change. Did you love the old API? Should we continue to support it? Do you have a use case where the new API falls short? Feel free to reach out on Twitter (@ejhammond) or to file an issue to start a discussion!

examples/typescript/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"react-responsive-system": "../../"
1818
},
1919
"devDependencies": {
20+
"@testing-library/jest-dom": "^5.16.3",
2021
"@types/react": "^17.0.43",
2122
"@types/react-dom": "^17.0.14",
2223
"parcel": "^2.4.1",

0 commit comments

Comments
 (0)