40 |
Contents
41 |
60 |
Stateless function
61 |
Stateless functions are a brilliant way to define highly reusable components. They don’t hold state
; they’re just functions.
62 |
const Greeting = () => <div>Hi there!</div>
63 |
64 |
65 |
They get passed props
and context
.
66 |
const Greeting = (props, context) =>
67 | <div style={{color: context.color}}>Hi {props.name}!</div>
68 |
69 |
70 |
They can define local variables, where a function block is used.
71 |
const Greeting = (props, context) => {
72 | const style = {
73 | fontWeight: "bold",
74 | color: context.color,
75 | }
76 |
77 | return <div style={style}>{props.name}</div>
78 | }
79 |
80 |
81 |
But you could get the same result by using other functions.
82 |
const getStyle = context => ({
83 | fontWeight: "bold",
84 | color: context.color,
85 | })
86 |
87 | const Greeting = (props, context) =>
88 | <div style={getStyle(context)}>{props.name}</div>
89 |
90 |
91 |
They can have defined defaultProps
, propTypes
and contextTypes
.
92 |
Greeting.propTypes = {
93 | name: PropTypes.string.isRequired
94 | }
95 | Greeting.defaultProps = {
96 | name: "Guest"
97 | }
98 | Greeting.contextTypes = {
99 | color: PropTypes.string
100 | }
101 |
102 |
103 |
JSX spread attributes
104 |
Spread Attributes is a JSX feature. It’s syntactic sugar for passing all of an object’s properties as JSX attributes.
105 |
These two examples are equivalent.
106 |
// props written as attributes
107 | <main className="main" role="main">{children}</main>
108 |
109 | // props "spread" from object
110 | <main {...{className: "main", role: "main", children}} />
111 |
112 |
113 |
Use this to forward props
to underlying components.
114 |
const FancyDiv = props =>
115 | <div className="fancy" {...props} />
116 |
117 |
118 |
Now, I can expect FancyDiv
to add the attributes it’s concerned with as well as those it’s not.
119 |
<FancyDiv data-id="my-fancy-div">So Fancy</FancyDiv>
120 |
121 | // output: <div class="fancy" data-id="my-fancy-div">So Fancy</div>
122 |
123 |
124 |
Keep in mind that order matters. If props.className
is defined, it’ll clobber the className
defined by FancyDiv
125 |
<FancyDiv className="my-fancy-div" />
126 |
127 | // output: <div className="my-fancy-div"></div>
128 |
129 |
130 |
We can make FancyDiv
s className always “win” by placing it after the spread props ({...props})
.
131 |
// my `className` clobbers your `className`
132 | const FancyDiv = props =>
133 | <div {...props} className="fancy" />
134 |
135 |
136 |
You should handle these types of props gracefully. In this case, I’ll merge the author’s props.className
with the className
needed to style my component.
137 |
const FancyDiv = ({ className, ...props }) =>
138 | <div
139 | className={["fancy", className].join(' ')}
140 | {...props}
141 | />
142 |
143 |
144 |
destructuring arguments
145 |
Destructuring assignment is an ES2015 feature. It pairs nicely with props
in Stateless Functions.
146 |
These examples are equivalent.
147 |
const Greeting = props => <div>Hi {props.name}!</div>
148 |
149 | const Greeting = ({ name }) => <div>Hi {name}!</div>
150 |
151 |
152 |
The rest parameter syntax (...
) allows you to collect all the remaining properties in a new object.
153 |
const Greeting = ({ name, ...props }) =>
154 | <div>Hi {name}!</div>
155 |
156 |
157 |
In turn, this object can use JSX Spread Attributes to forward props
to the composed component.
158 |
const Greeting = ({ name, ...props }) =>
159 | <div {...props}>Hi {name}!</div>
160 |
161 |
162 |
Avoid forwarding non-DOM props
to composed components. Destructuring makes this very easy because you can create a new props
object without component-specific props
.
163 |
conditional rendering
164 |
You can’t use regular if/else conditions inside a component definition. The conditional (ternary) operator is your friend.
165 |
if
166 |
{condition && <span>Rendered when `truthy`</span> }
167 |
168 |
169 |
unless
170 |
{condition || <span>Rendered when `falsey`</span> }
171 |
172 |
173 |
if-else
(tidy one-liners)
174 |
{condition
175 | ? <span>Rendered when `truthy`</span>
176 | : <span>Rendered when `falsey`</span>
177 | }
178 |
179 |
180 |
if-else
(big blocks)
181 |
{condition ? (
182 | <span>
183 | Rendered when `truthy`
184 | </span>
185 | ) : (
186 | <span>
187 | Rendered when `falsey`
188 | </span>
189 | )}
190 |
191 |
192 |
Children types
193 |
React can render children
of many types. In most cases it’s either an array
or a string
.
194 |
string
195 |
<div>
196 | Hello World!
197 | </div>
198 |
199 |
200 |
array
201 |
<div>
202 | {["Hello ", <span>World</span>, "!"]}
203 | </div>
204 |
205 |
206 |
Functions may be used as children. However, it requires coordination with the parent component to be useful.
207 |
function
208 |
<div>
209 | {(() => { return "hello world!"})()}
210 | </div>
211 |
212 |
213 |
Array as children
214 |
Providing an array as children
is a very common. It’s how lists are drawn in React.
215 |
We use map()
to create an array of React Elements for every value in the array.
216 |
<ul>
217 | {["first", "second"].map((item) => (
218 | <li>{item}</li>
219 | ))}
220 | </ul>
221 |
222 |
223 |
That’s equivalent to providing a literal array
.
224 |
<ul>
225 | {[
226 | <li>first</li>,
227 | <li>second</li>,
228 | ]}
229 | </ul>
230 |
231 |
232 |
This pattern can be combined with destructuring, JSX Spread Attributes, and other components, for some serious terseness.
233 |
<div>
234 | {arrayOfMessageObjects.map(({ id, ...message }) =>
235 | <Message key={id} {...message} />
236 | )}
237 | </div>
238 |
239 |
240 |
Function as children
241 |
Using a function as children
isn’t inherently useful.
242 |
<div>{() => { return "hello world!"}()}</div>
243 |
244 |
245 |
However, it can be used in component authoring for some serious power. This technique is commonly referred to as render callbacks
.
246 |
This is a powerful technique used by libraries like ReactMotion. When applied, rendering logic can be kept in the owner component, instead of being delegated.
247 |
See Render callbacks, for more details.
248 |
Render callback
249 |
Here’s a component that uses a Render callback. It’s not useful, but it’s an easy illustration to start with.
250 |
const Width = ({ children }) => children(500)
251 |
252 |
253 |
The component calls children
as a function, with some number of arguments. Here, it’s the number 500
.
254 |
To use this component, we give it a function as children
.
255 |
<Width>
256 | {width => <div>window is {width}</div>}
257 | </Width>
258 |
259 |
260 |
We get this output.
261 |
<div>window is 500</div>
262 |
263 |
264 |
With this setup, we can use this width
to make rendering decisions.
265 |
<Width>
266 | {width =>
267 | width > 600
268 | ? <div>min-width requirement met!</div>
269 | : null
270 | }
271 | </Width>
272 |
273 |
274 |
If we plan to use this condition a lot, we can define another components to encapsulate the reused logic.
275 |
const MinWidth = ({ width: minWidth, children }) =>
276 | <Width>
277 | {width =>
278 | width > minWidth
279 | ? children
280 | : null
281 | }
282 | </Width>
283 |
284 |
285 |
Obviously a static Width
component isn’t useful but one that watches the browser window is. Here’s a sample implementation.
286 |
class WindowWidth extends React.Component {
287 | constructor() {
288 | super()
289 | this.state = { width: 0 }
290 | }
291 |
292 | componentDidMount() {
293 | this.setState(
294 | {width: window.innerWidth},
295 | window.addEventListener(
296 | "resize",
297 | ({ target }) =>
298 | this.setState({width: target.innerWidth})
299 | )
300 | )
301 | }
302 |
303 | render() {
304 | return this.props.children(this.state.width)
305 | }
306 | }
307 |
308 |
309 |
Many developers favor Higher Order Components for this type of functionality. It’s a matter of preference.
310 |
Children pass-through
311 |
You might create a component designed to apply context
and render its children
.
312 |
class SomeContextProvider extends React.Component {
313 | getChildContext() {
314 | return {some: "context"}
315 | }
316 |
317 | render() {
318 | // how best do we return `children`?
319 | }
320 | }
321 |
322 |
323 |
You’re faced with a decision. Wrap children
in an extraneous <div />
or return children
directly. The first options gives you extra markup (which can break some stylesheets). The second will result in unhelpful errors.
324 |
// option 1: extra div
325 | return <div>{children}</div>
326 |
327 | // option 2: unhelpful errors
328 | return children
329 |
330 |
331 |
It’s best to treat children
as an opaque data type. React provides React.Children
for dealing with children
appropriately.
332 |
return React.Children.only(this.props.children)
333 |
334 |
335 |
Proxy component
336 |
(I’m not sure if this name makes sense)
337 |
Buttons are everywhere in web apps. And every one of them must have the type
attribute set to “button”.
338 |
<button type="button">
339 |
340 |
341 |
Writing this attribute hundreds of times is error prone. We can write a higher level component to proxy props
to a lower-level button
component.
342 |
const Button = props =>
343 | <button type="button" {...props}>
344 |
345 |
346 |
We can use Button
in place of button
and ensure that the type
attribute is consistently applied everywhere.
347 |
<Button />
348 | // <button type="button"><button>
349 |
350 | <Button className="CTA">Send Money</Button>
351 | // <button type="button" class="CTA">Send Money</button>
352 |
353 |
354 |
Style component
355 |
This is a Proxy component applied to the practices of style.
356 |
Say we have a button. It uses classes to be styled as a “primary” button.
357 |
<button type="button" className="btn btn-primary">
358 |
359 |
360 |
We can generate this output using a couple single-purpose components.
361 |
import classnames from 'classnames'
362 |
363 | const PrimaryBtn = props =>
364 | <Btn {...props} primary />
365 |
366 | const Btn = ({ className, primary, ...props }) =>
367 | <button
368 | type="button"
369 | className={classnames(
370 | "btn",
371 | primary && "btn-primary",
372 | className
373 | )}
374 | {...props}
375 | />
376 |
377 |
378 |
It can help to visualize this.
379 |
PrimaryBtn()
380 | ↳ Btn({primary: true})
381 | ↳ Button({className: "btn btn-primary"}, type: "button"})
382 | ↳ '<button type="button" class="btn btn-primary"></button>'
383 |
384 |
385 |
Using these components, all of these result in the same output.
386 |
<PrimaryBtn />
387 | <Btn primary />
388 | <button type="button" className="btn btn-primary" />
389 |
390 |
391 |
This can be a huge boon to style maintenance. It isolates all concerns of style to a single component.
392 |
Event switch
393 |
When writing event handlers it’s common to adopt the handle{eventName}
naming convention.
394 |
handleClick(e) { /* do something */ }
395 |
396 |
397 |
For components that handle several event types, these function names can be repetitive. The names themselves might not provide much value, as they simply proxy to other actions/functions.
398 |
handleClick() { require("./actions/doStuff")(/* action stuff */) }
399 | handleMouseEnter() { this.setState({ hovered: true }) }
400 | handleMouseLeave() { this.setState({ hovered: false }) }
401 |
402 |
403 |
Consider writing a single event handler for your component and switching on event.type
.
404 |
handleEvent({type}) {
405 | switch(type) {
406 | case "click":
407 | return require("./actions/doStuff")(/* action dates */)
408 | case "mouseenter":
409 | return this.setState({ hovered: true })
410 | case "mouseleave":
411 | return this.setState({ hovered: false })
412 | default:
413 | return console.warn(`No case for event type "${type}"`)
414 | }
415 | }
416 |
417 |
418 |
Alternatively, for simple components, you can call imported actions/functions directly from components, using arrow functions.
419 |
<div onClick={() => someImportedAction({ action: "DO_STUFF" })}
420 |
421 |
422 |
Don’t fret about performance optimizations until you have problems. Seriously don’t.
423 |
Layout component
424 |
Layout components result in some form of static DOM element. It might not need to update frequently, if ever.
425 |
Consider a component that renders two children
side-by-side.
426 |
<HorizontalSplit
427 | leftSide={<SomeSmartComponent />}
428 | rightSide={<AnotherSmartComponent />}
429 | />
430 |
431 |
432 |
We can aggressively optimize this component.
433 |
While HorizontalSplit
will be parent
to both components, it will never be their owner
. We can tell it to update never, without interrupting the lifecycle of the components inside.
434 |
class HorizontalSplit extends React.Component {
435 | shouldComponentUpdate() {
436 | return false
437 | }
438 |
439 | render() {
440 | <FlexContainer>
441 | <div>{this.props.leftSide}</div>
442 | <div>{this.props.rightSide}</div>
443 | </FlexContainer>
444 | }
445 | }
446 |
447 |
448 |
Container component
449 |
“A container does data fetching and then renders its corresponding sub-component. That’s it.”—Jason Bonta
450 |
Given this reusable CommentList
component.
451 |
const CommentList = ({ comments }) =>
452 | <ul>
453 | {comments.map(comment =>
454 | <li>{comment.body}-{comment.author}</li>
455 | )}
456 | </ul>
457 |
458 |
459 |
We can create a new component responsible for fetching data and rendering the stateless CommentList
component.
460 |
class CommentListContainer extends React.Component {
461 | constructor() {
462 | super()
463 | this.state = { comments: [] }
464 | }
465 |
466 | componentDidMount() {
467 | $.ajax({
468 | url: "/my-comments.json",
469 | dataType: 'json',
470 | success: comments =>
471 | this.setState({comments: comments});
472 | })
473 | }
474 |
475 | render() {
476 | return <CommentList comments={this.state.comments} />
477 | }
478 | }
479 |
480 |
481 |
We can write different containers for different application contexts.
482 |
Higher-order component
483 |
A higher-order function is a function that takes and/or returns a function. It’s not more complicated than that. So, what’s a higher-order component?
484 |
If you’re already using container components, these are just generic containers, wrapped up in a function.
485 |
Let’s start with our stateless Greeting
component.
486 |
const Greeting = ({ name }) => {
487 | if (!name) { return <div>Connecting...</div> }
488 |
489 | return <div>Hi {name}!</div>
490 | }
491 |
492 |
493 |
If it gets props.name
, it’s gonna render that data. Otherwise it’ll say that it’s “Connecting…”. Now for the the higher-order bit.
494 |
const Connect = ComposedComponent =>
495 | class extends React.Component {
496 | constructor() {
497 | super()
498 | this.state = { name: "" }
499 | }
500 |
501 | componentDidMount() {
502 | // this would fetch or connect to a store
503 | this.setState({ name: "Michael" })
504 | }
505 |
506 | render() {
507 | return (
508 | <ComposedComponent
509 | {...this.props}
510 | name={this.state.name}
511 | />
512 | )
513 | }
514 | }
515 |
516 |
517 |
This is just a function that returns component that renders the component we passed as an argument.
518 |
Last step, we need to wrap our our Greeting
component in Connect
.
519 |
const ConnectedMyComponent = Connect(Greeting)
520 |
521 |
522 |
This is a powerful pattern for providing fetching and providing data to any number of stateless function components.
523 |
State hoisting
524 |
Stateless functions don’t hold state (as the name implies).
525 |
Events are changes in state.
526 | Their data needs to be passed to stateful container components parents.
527 |
This is called “state hoisting”.
528 | It’s accomplished by passing a callback from a container component to a child component.
529 |
class NameContainer extends React.Component {
530 | render() {
531 | return <Name onChange={newName => alert(newName)} />
532 | }
533 | }
534 |
535 | const Name = ({ onChange }) =>
536 | <input onChange={e => onChange(e.target.value)} />
537 |
538 |
539 |
Name
receives an onChange
callback from NameContainer
and calls on events.
540 |
The alert
above makes for a terse demo but it’s not changing state.
541 | Let’s change the internal state of NameContainer
.
542 |
class NameContainer extends React.Component {
543 | constructor() {
544 | super()
545 | this.state = {name: ""}
546 | }
547 |
548 | render() {
549 | return <Name onChange={newName => this.setState({name: newName})} />
550 | }
551 | }
552 |
553 |
554 |
The state is hoisted to the container, by the provided callback, where it’s used to update local state.
555 | This sets a nice clear boundary and maximizes the re-usability of stateless function.
556 |
This pattern isn’t limited to stateless functions.
557 | Because stateless function don’t have lifecycle events,
558 | you’ll use this pattern with component classes as well.
559 |
Controlled input is an important pattern to know for use with state hoisting
560 |
(It’s best to process the event object on the stateful component)
561 |
562 |
It’s hard to talk about controlled inputs in the abstract.
563 | Let’s start with an uncontrolled (normal) input and go from there.
564 |
<input type="text" />
565 |
566 |
567 |
When you fiddle with this input in the browser, you see your changes.
568 | This is normal.
569 |
A controlled input disallows the DOM mutations that make this possible.
570 | You set the value
of the input in component-land and it doesn’t change in DOM-land.
571 |
<input type="text" value="This won't change. Try it." />
572 |
573 |
574 |
Obviously static inputs aren’t very useful to your users.
575 | So, we derive a value
from state.
576 |
class ControlledNameInput extends React.Component {
577 | constructor() {
578 | super()
579 | this.state = {name: ""}
580 | }
581 |
582 | render() {
583 | return <input type="text" value={this.state.name} />
584 | }
585 | }
586 |
587 |
588 |
Then, changing the input is a matter of changing component state.
589 |
return (
590 | <input
591 | value={this.state.name}
592 | onChange={e => this.setState({ name: e.target.value })}
593 | />
594 | )
595 |
596 |
597 |
This is a controlled input.
598 | It only updates the DOM when state has changed in our component.
599 | This is invaluable when creating consistent UIs.
600 |
If you’re using stateless functions for form elements,
601 | read about using state hoisting to move new state up the component tree.
602 |
603 |
608 |
609 |
619 |
620 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "build": "node ./scripts/readme-to-index.js"
4 | },
5 | "dependencies": {
6 | "consolidate": "^0.15.1",
7 | "lodash": "^4.17.2",
8 | "marked": "^0.8.0",
9 | "pygmentize-bundled": "^2.3.0"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/scripts/_index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |