diff --git a/.gitignore b/.gitignore index badbc02..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -_site -.sass-cache +node_modules diff --git a/CNAME b/CNAME deleted file mode 100644 index 491b9c8..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -reactpatterns.com diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..7b83f4a --- /dev/null +++ b/README.markdown @@ -0,0 +1,730 @@ +## Contents + +* [Stateless function](#stateless-function) +* [JSX spread attributes](#jsx-spread-attributes) +* [Destructuring arguments](#destructuring-arguments) +* [Conditional rendering](#conditional-rendering) +* [Children types](#children-types) +* [Array as children](#array-as-children) +* [Function as children](#function-as-children) +* [Render callback](#render-callback) +* [Children pass-through](#children-pass-through) +* [Proxy component](#proxy-component) +* [Style component](#style-component) +* [Event switch](#event-switch) +* [Layout component](#layout-component) +* [Container component](#container-component) +* [Higher-order component](#higher-order-component) +* [State hoisting](#state-hoisting) +* [Controlled input](#controlled-input) + +## Stateless function + +[Stateless functions](https://facebook.github.io/react/docs/components-and-props.html) are a brilliant way to define highly reusable components. They don't hold `state`; they're just functions. + +```js +const Greeting = () =>
Hi there!
+``` + +They get passed `props` and `context`. + +```js +const Greeting = (props, context) => +
Hi {props.name}!
+``` + +They can define local variables, where a function block is used. + +```js +const Greeting = (props, context) => { + const style = { + fontWeight: "bold", + color: context.color, + } + + return
{props.name}
+} +``` + +But you could get the same result by using other functions. + +```js +const getStyle = context => ({ + fontWeight: "bold", + color: context.color, +}) + +const Greeting = (props, context) => +
{props.name}
+``` + +They can have defined `defaultProps`, `propTypes` and `contextTypes`. + +```js +Greeting.propTypes = { + name: PropTypes.string.isRequired +} +Greeting.defaultProps = { + name: "Guest" +} +Greeting.contextTypes = { + color: PropTypes.string +} +``` + + +## JSX spread attributes + +Spread Attributes is a JSX feature. It's syntactic sugar for passing all of an object's properties as JSX attributes. + +These two examples are equivalent. +```js +// props written as attributes +
{children}
+ +// props "spread" from object +
+``` + +Use this to forward `props` to underlying components. + +```js +const FancyDiv = props => +
+``` + +Now, I can expect `FancyDiv` to add the attributes it's concerned with as well as those it's not. + +```js +So Fancy + +// output:
So Fancy
+``` + +Keep in mind that order matters. If `props.className` is defined, it'll clobber the `className` defined by `FancyDiv` + +```js + + +// output:
+``` + +We can make `FancyDiv`s className always "win" by placing it after the spread props `({...props})`. + +```js +// my `className` clobbers your `className` +const FancyDiv = props => +
+``` + +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. + +```js +const FancyDiv = ({ className, ...props }) => +
+``` + + +## destructuring arguments + +[Destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) is an ES2015 feature. It pairs nicely with `props` in Stateless Functions. + +These examples are equivalent. +```js +const Greeting = props =>
Hi {props.name}!
+ +const Greeting = ({ name }) =>
Hi {name}!
+``` + +The [rest parameter syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters) (`...`) allows you to collect all the remaining properties in a new object. + +```js +const Greeting = ({ name, ...props }) => +
Hi {name}!
+``` + +In turn, this object can use [JSX Spread Attributes](#jsx-spread-attributes) to forward `props` to the composed component. + +```js +const Greeting = ({ name, ...props }) => +
Hi {name}!
+``` + +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`. + + +## conditional rendering + +You can't use regular if/else conditions inside a component definition. [The conditional (ternary) operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) is your friend. + +`if` + +```js +{condition && Rendered when `truthy` } +``` + +`unless` + +```js +{condition || Rendered when `falsey` } +``` + +`if-else` (tidy one-liners) + +```js +{condition + ? Rendered when `truthy` + : Rendered when `falsey` +} +``` + +`if-else` (big blocks) + +```js +{condition ? ( + + Rendered when `truthy` + +) : ( + + Rendered when `falsey` + +)} +``` + + +## Children types + +React can render `children` of many types. In most cases it's either an `array` or a `string`. + +`string` + +```js +
+ Hello World! +
+``` + +`array` + +```js +
+ {["Hello ", World, "!"]} +
+``` + +Functions may be used as children. However, it requires [coordination with the parent component](#render-callback) to be useful. + +`function` + +```js +
+ {(() => { return "hello world!"})()} +
+``` + + +## Array as children + +Providing an array as `children` is a very common. It's how lists are drawn in React. + +We use `map()` to create an array of React Elements for every value in the array. + +```js +
    + {["first", "second"].map((item) => ( +
  • {item}
  • + ))} +
+``` + +That's equivalent to providing a literal `array`. + +```js +
    + {[ +
  • first
  • , +
  • second
  • , + ]} +
+``` + +This pattern can be combined with destructuring, JSX Spread Attributes, and other components, for some serious terseness. + +```js +
+ {arrayOfMessageObjects.map(({ id, ...message }) => + + )} +
+``` + + +## Function as children + +Using a function as `children` isn't inherently useful. + +```js +
{() => { return "hello world!"}()}
+``` + +However, it can be used in component authoring for some serious power. This technique is commonly referred to as `render callbacks`. + +This is a powerful technique used by libraries like [ReactMotion](https://github.com/chenglou/react-motion). When applied, rendering logic can be kept in the owner component, instead of being delegated. + +See [Render callbacks](#render-callback), for more details. + +## Render callback + +Here's a component that uses a Render callback. It's not useful, but it's an easy illustration to start with. + +```js +const Width = ({ children }) => children(500) +``` + +The component calls `children` as a function, with some number of arguments. Here, it's the number `500`. + +To use this component, we give it a [function as `children`](#function-as-children). + +```js + + {width =>
window is {width}
} +
+``` + +We get this output. + +```js +
window is 500
+``` + +With this setup, we can use this `width` to make rendering decisions. + +```js + + {width => + width > 600 + ?
min-width requirement met!
+ : null + } +
+``` + +If we plan to use this condition a lot, we can define another components to encapsulate the reused logic. + +```js +const MinWidth = ({ width: minWidth, children }) => + + {width => + width > minWidth + ? children + : null + } + +``` + + +Obviously a static `Width` component isn't useful but one that watches the browser window is. Here's a sample implementation. + +```js +class WindowWidth extends React.Component { + constructor() { + super() + this.state = { width: 0 } + } + + componentDidMount() { + this.setState( + {width: window.innerWidth}, + window.addEventListener( + "resize", + ({ target }) => + this.setState({width: target.innerWidth}) + ) + ) + } + + render() { + return this.props.children(this.state.width) + } +} +``` + +Many developers favor [Higher Order Components](#higher-order-component) for this type of functionality. It's a matter of preference. + + +## Children pass-through + +There are times you'll need to wrap a stateless function with lifecycle events. +While we want to wrap component functionality around other components, we don't want to introduce extraneous DOM nodes. +In some apps, this might brake styling. + +We use the function `React.Children.only`. +`only` allows us to return `this.props.children` __if__ there is only one child. +Otherwise, it throws an error. + +```js +class SomeLifeCycleWrapper extends React.Component { + componentDidMount() { + console.log("I mounted but have no DOM.") + } + + render() { + return React.Children.only(this.props.children) + } +} +``` + +In cases where you're working with `state` or `context`, prefer [higher-order components](#higher-order-component) or [render callbacks](#render-callback). + + +## Proxy component + +*(I'm not sure if this name makes sense)* + +Buttons are everywhere in web apps. And every one of them must have the `type` attribute set to "button". + +```js + +// +``` + +## Style component + +This is a [Proxy component](#proxy-component) applied to the practices of style. + +Say we have a button. It uses classes to be styled as a "primary" button. + +```js +' +``` + +Using these components, all of these result in the same output. +```js + + + -// -{% endhighlight %} \ No newline at end of file diff --git a/_posts/2016-08-18-style-component.markdown b/_posts/2016-08-18-style-component.markdown deleted file mode 100644 index f47c261..0000000 --- a/_posts/2016-08-18-style-component.markdown +++ /dev/null @@ -1,49 +0,0 @@ ---- -layout: post -title: "Style component" -date: 2016-08-18 10:03:00 ---- - -This is a [Proxy component](#Proxy component) applied to the practices of style. - -Say we have a button. It uses classes to be styled as a "primary" button. - -{% highlight ts %} -' -{% endhighlight %} - -Using these components, all of these result in the same output. -{% highlight ts %} - - -