Skip to content

Commit 907d84a

Browse files
authored
Support SSR with SizesProvider (renatorib#23)
* remove dist es add to gitignore * add support to ssr with SizesProvider
1 parent f67d326 commit 907d84a

19 files changed

+230
-1049
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
es
2+
dist
13
node_modules
24
lib
35
.DS_Store

.storybook/.babelrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["es2015", "react", "stage-1"]
3+
}

README.md

+137-51
Original file line numberDiff line numberDiff line change
@@ -7,124 +7,134 @@
77
[![Twitter](https://img.shields.io/twitter/url/https/github.com/renatorib/react-sizes.svg?style=social&style=flat-square)](https://twitter.com/intent/tweet?url=https://github.com/renatorib/react-sizes)
88

99
## Install
10+
1011
```
1112
yarn add react-sizes
1213
```
14+
1315
```
1416
npm install react-sizes
1517
```
1618

1719
## What and why
20+
1821
React Sizes is a high-order component with a high performance that transform window sizes (width and height) into props.
1922
You can check inside your component, for example, if user's window is less than 480 pixels of width, and add a custom
2023
content.
2124

2225
It can be very powerful for when you need to display different content for mobile and desktop.
23-
But it's not limited to this case. Just use that at your needs.
26+
But it's not limited to this case. Just use that at your needs.
2427

2528
## Usage
2629

2730
#### With class component.
31+
2832
```jsx
29-
import React, { Component } from 'react';
30-
import withSizes from 'react-sizes';
33+
import React, { Component } from 'react'
34+
import withSizes from 'react-sizes'
3135

3236
class MyComponent extends Component {
3337
render() {
34-
return (
35-
<div>{this.props.isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
36-
);
38+
return <div>{this.props.isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
3739
}
3840
}
3941

4042
const mapSizesToProps = ({ width }) => ({
4143
isMobile: width < 480,
42-
});
44+
})
4345

44-
export default withSizes(mapSizesToProps)(MyComponent);
46+
export default withSizes(mapSizesToProps)(MyComponent)
4547
```
48+
4649
You can play with this example [here](https://codesandbox.io/s/Rg0DDOWnE).
4750

4851
#### As decorator.
52+
4953
```jsx
50-
import React from 'react';
51-
import withSizes from 'react-sizes';
54+
import React from 'react'
55+
import withSizes from 'react-sizes'
5256

5357
@withSizes(({ width }) => ({ isMobile: width < 480 }))
5458
class MyComponent extends Component {
5559
render() {
56-
return (
57-
<div>{this.props.isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
58-
);
60+
return <div>{this.props.isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
5961
}
6062
}
6163

62-
export default MyComponent;
64+
export default MyComponent
6365
```
6466

6567
#### Interoperate with other libraries.
68+
6669
```jsx
67-
import React from 'react';
68-
import withSizes from 'react-sizes';
69-
import { withState, compose } from 'recompose';
70+
import React from 'react'
71+
import withSizes from 'react-sizes'
72+
import { withState, compose } from 'recompose'
7073

7174
const enhancer = compose(
7275
withState('counter', 'setCounter', 0),
73-
withSizes(({ width }) => ({ isMobile: width < 480 })),
74-
);
76+
withSizes(({ width }) => ({ isMobile: width < 480 }))
77+
)
7578

7679
const MyComponent = enhancer(({ isMobile, counter, setCounter }) => (
7780
<div>
7881
<div>
79-
Count: {counter} <button onClick={() => setCounter(n => n + 1)}>Increment</button>
82+
Count: {counter}{' '}
83+
<button onClick={() => setCounter(n => n + 1)}>Increment</button>
8084
</div>
8185
<div>{isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
8286
</div>
83-
));
87+
))
8488

85-
export default MyComponent;
89+
export default MyComponent
8690
```
8791

8892
#### With functional component.
93+
8994
```jsx
90-
import React from 'react';
91-
import withSizes from 'react-sizes';
95+
import React from 'react'
96+
import withSizes from 'react-sizes'
9297

9398
const MyComponent = ({ isMobile }) => (
9499
<div>{isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
95-
);
100+
)
96101

97102
const mapSizesToProps = ({ width }) => ({
98103
isMobile: width < 480,
99-
});
104+
})
100105

101-
export default withSizes(mapSizesToProps)(MyComponent);
106+
export default withSizes(mapSizesToProps)(MyComponent)
102107
```
103108

104109
#### Mess with props.
110+
105111
(Added in 0.1.0)
112+
106113
```jsx
107-
import React from 'react';
108-
import withSizes from 'react-sizes';
114+
import React from 'react'
115+
import withSizes from 'react-sizes'
109116

110117
const MyComponent = ({ isMobile }) => (
111118
<div>{isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
112-
);
119+
)
113120

114121
const mapSizesToProps = ({ width }, { mobileBreakpoint }) => ({
115122
isMobile: width < mobileBreakpoint,
116-
});
123+
})
117124

118-
export default withSizes(mapSizesToProps)(MyComponent);
125+
export default withSizes(mapSizesToProps)(MyComponent)
119126
```
127+
120128
then:
129+
121130
```jsx
122131
<MyComponent mobileBreakpoint={480} />
123132
<MyComponent mobileBreakpoint={400} />
124133
<MyComponent mobileBreakpoint={600} />
125134
```
126135

127136
#### With presets selectors.
137+
128138
```diff
129139
- const mapSizesToProps = ({ width }) => ({
130140
- isMobile: width < 480,
@@ -138,69 +148,145 @@ then:
138148
## Presets Selectors
139149

140150
You can check all **our** presets selectors at our main code `src/withSizes.js`.
151+
141152
```js
142-
withSizes.isMobile = ({ width }) => width < 480;
143-
withSizes.isTablet = ({ width }) => width >= 480 && width < 1024;
144-
withSizes.isDesktop = ({ width }) => width >= 1024;
153+
withSizes.isMobile = ({ width }) => width < 480
154+
withSizes.isTablet = ({ width }) => width >= 480 && width < 1024
155+
withSizes.isDesktop = ({ width }) => width >= 1024
145156

146-
withSizes.isGtMobile = (sizes) => !withSizes.isMobile(sizes);
147-
withSizes.isGtTablet = (sizes) => withSizes.isDesktop(sizes);
157+
withSizes.isGtMobile = sizes => !withSizes.isMobile(sizes)
158+
withSizes.isGtTablet = sizes => withSizes.isDesktop(sizes)
148159

149-
withSizes.isStTablet = (sizes) => withSizes.isMobile(sizes);
150-
withSizes.isStDesktop = (sizes) => !withSizes.isStDesktop(sizes);
160+
withSizes.isStTablet = sizes => withSizes.isMobile(sizes)
161+
withSizes.isStDesktop = sizes => !withSizes.isStDesktop(sizes)
151162

152-
withSizes.isTabletAndGreater = (sizes) => !withSizes.isMobile(sizes);
153-
withSizes.isTabletAndSmaller = (sizes) => !withSizes.isStDesktop(sizes);
163+
withSizes.isTabletAndGreater = sizes => !withSizes.isMobile(sizes)
164+
withSizes.isTabletAndSmaller = sizes => !withSizes.isStDesktop(sizes)
154165
```
155166

156167
If it don't fit to your needs, you can create your own selectors.
168+
157169
```jsx
158170
// utils/sizes/selectors.js
159-
export const isntDesktop = ({ width }) => width < 1024;
160-
export const backgroundColor = ({ width }) => width < 480 ? 'red' : 'green';
171+
export const isntDesktop = ({ width }) => width < 1024
172+
export const backgroundColor = ({ width }) => (width < 480 ? 'red' : 'green')
161173

162174
// your component
163-
import { isntDesktop, backgroundColor } from 'utils/sizes/selectors';
175+
import { isntDesktop, backgroundColor } from 'utils/sizes/selectors'
164176

165177
const mapSizesToProps = sizes => ({
166178
canDisplayMobileFeature: isntDesktop(sizes),
167179
backgroundColor: backgroundColor(sizes),
168-
});
180+
})
169181
```
182+
170183
> `sizes` argument is an object with `width` and `height` properties and represents DOM window width and height.
171184
172185
## Guide
173186

174187
#### mapSizesToProps(sizes)
188+
175189
`sizes` argument is an object with `width` and `height` of DOM window.
176190

177191
```js
178192
const mapSizesToProps = sizes => {
179193
console.log(sizes) // { width: 1200, height: 720 } (example)
180-
};
194+
}
181195
```
182196

183-
In pratice, it is a callback that return props that will injected into your Component.
197+
In pratice, it is a callback that return props that will injected into your Component.
198+
184199
```js
185200
const mapSizesToProps = function(sizes) {
186201
const props = {
187202
backgroundColor: sizes.width < 700 ? 'red' : 'green',
188-
};
203+
}
189204

190-
return props;
191-
};
205+
return props
206+
}
192207
```
193208

194209
But you can simplify this to stay practical and elegant.
210+
195211
```js
196212
const mapSizesToProps = ({ width }) => ({
197213
backgroundColor: width < 700 ? 'red' : 'green',
198-
});
214+
})
215+
```
216+
217+
## Server Side Rendering
218+
219+
Since React Sizes rely on window to computate sizes, we can't computate the values in server enviroment. To try to get around this we can **guess** user viewport based on your user-agent, and pass values by a Context Provider.
220+
But be careful, **user-agent based detection is not a reliable solution**. It's a workaround.
221+
222+
```js
223+
// Config can be created based on user-agent. See below
224+
const config = { fallbackWidth: 360, fallbackHeight: 640 }
225+
226+
return (
227+
<SizesProvider config={config}>
228+
<App />
229+
</SizesProvider>
230+
)
231+
```
232+
233+
Example:
234+
235+
```js
236+
import MobileDetect from 'mobile-detect'
237+
import Express from 'express'
238+
import { SizesProvider } from 'react-sizes'
239+
// All other imports
240+
241+
const getSizesFallback = userAgent => {
242+
const md = new MobileDetect(userAgent)
243+
244+
if (!!md.mobile()) {
245+
return {
246+
fallbackWidth: 360,
247+
fallbackHeight: 640,
248+
}
249+
} else if (!!md.tablet()) {
250+
return {
251+
fallbackWidth: 768,
252+
fallbackHeight: 1024,
253+
}
254+
}
255+
256+
return {
257+
fallbackWidth: 1280,
258+
fallbackHeiht: 700,
259+
}
260+
}
261+
262+
// Note: you don't need to use express, this is just an example
263+
const app = new Express()
264+
app.use((req, res) => {
265+
// ...
266+
const sizesConfig = getSizesFallback(req.headers['user-agent'])
267+
268+
const App = (
269+
<AnotherProvider>
270+
<Router location={req.url}>
271+
<SizesProvider config={sizesConfig}>
272+
<Root />
273+
</SizesProvider>
274+
</Router>
275+
</AnotherProvider>
276+
)
277+
278+
res.status(200)
279+
res.send(`<!doctype html>\n${ReactDOM.renderToString(<App />)}`)
280+
res.end()
281+
})
282+
283+
app.listen(/* ... */)
199284
```
200285

201286
## Performance Notes
202287

203288
#### Shallow Compare
289+
204290
React Sizes do a shallow compare in props generated from `mapSizesToProps` (called `propsToPass`), so it will only rerender when they really change. If you create a deep data sctructure, this can generate false positives. In these cases, we recommend using immutable for a more reliable shallow compare result. Or just don't use deep data structures, if possible.
205291

206292
## Contribute

0 commit comments

Comments
 (0)