SaltyCrane: javascripthttps://www.saltycrane.com/blog/2022-12-09T17:46:12-08:00Aphrodite to CSS Modules codemod
2022-12-09T17:46:12-08:00https://www.saltycrane.com/blog/2022/12/aphrodite-css-modules-codemod/<p>
I wanted to convert our React project from
<a href="https://github.com/Khan/aphrodite">Aphrodite</a> to
<a href="https://github.com/css-modules/css-modules">CSS Modules</a>. The
biggest impetus was that Aphrodite isn't supported by the new
<a href="https://nextjs.org/blog/next-13#new-app-directory-beta"
>Next.js v13 <code>app</code> directory feature</a
>, which I'm excited to try. I like styled-components, but my co-worker
likes CSS Modules and it's hard to go wrong with CSS Modules. CSS Modules
also has built-in support in Next.js and it looks pretty good in
<a
href="https://2022.stateofcss.com/en-US/css-in-js/#css_in_js_experience_marimekko"
>this graphic from the State of CSS survey</a
>.
</p>
<p>
To ease the conversion, I wrote a
<a href="https://github.com/facebook/jscodeshift">jscodeshift</a> codemod to
automate most of the process.
<strong
>The codemod is on github here:
<a href="https://github.com/saltycrane/aphrodite-to-css-modules-codemod"
><code>aphrodite-to-css-modules-codemod</code></a
>.</strong
>
An example is below.
</p>
<p>
The codemod worked well for my 200 Aphrodite files. I did spend time manually
converting JS constants into CSS variables. I also manually handled CSS
precedence issues since Aphrodite handles precedence more nicely than CSS. But
overall I was pretty happy with the results. (It was certainly more successful
than my
<a
href="https://github.com/saltycrane/reactstrap-v8-to-react-bootstrap-v2-codemod"
>attempt</a
>
at a reactstrap-to-react-bootstrap codemod which I never used.)
</p>
<h4 id="before">Before</h4>
<p><code>./example/src/MyComponent.tsx</code>:</p>
<pre><code>import { css, StyleSheet } from "aphrodite";
import classNames from "classnames";
import React from "react";
import { colors } from "./constants";
import { hexToRgbA } from "./utils";
export default function MyComponent() {
const isSomething = true;
const isSomethingElse = false;
return (
<div
className={css(
isSomethingElse ? myStyles.containerGrid : myStyles.containerFlex,
)}
style={{}}
>
<div className={css(myStyles.header, myStyles.content)}>header</div>
<div className={classNames(css(myStyles.content), "another-class")}>
<div>Lorem ipsum</div>
</div>
<span className={css(isSomething && myStyles.warning)}></span>
</div>
);
}
// comment I
export const myStyles = StyleSheet.create({
containerGrid: {
backgroundColor: "white",
// comment 1
/* comment 2 */ display: "grid" /* comment 4 */, // comment 5
gridTemplate: `
"sourceselect . reviewbutton" auto
"pagination filters filters " auto
"rowcount filters filters " 20px
/ 2fr 1fr 2fr
`,
width: 200,
},
containerFlex: {
display: "flex",
},
content: {
lineHeight: 1.5,
},
header: {
backgroundColor: "#ccc",
color: hexToRgbA(colors.danger, 0.8),
display: "inline-block",
":hover": {
color: colors.primary,
borderColor: `${colors.info} !important`,
},
},
// comment a
warning: {
fontWeight: 700,
color: colors.warning,
opacity: 0,
} /* comment b */, // comment c
});
</code></pre>
<h4 id="after">After</h4>
<p><code>./example/src/MyComponent.tsx</code>:</p>
<pre><code>import myStyles from "./MyComponent.module.css";
import classNames from "classnames";
import React from "react";
import { colors } from "./constants";
import { hexToRgbA } from "./utils";
export default function MyComponent() {
const isSomething = true;
const isSomethingElse = false;
return (
<div
className={
isSomethingElse ? myStyles.containerGrid : myStyles.containerFlex
}
style={{}}
>
<div
className={
// TODO: check CSS precedence
classNames(myStyles.header, myStyles.content)
}
>
header
</div>
<div className={classNames(myStyles.content, "another-class")}>
<div>Lorem ipsum</div>
</div>
<span className={classNames(isSomething && myStyles.warning)}></span>
</div>
);
}
export { myStyles };
</code></pre>
<p><code>./example/src/MyComponent.module.css</code>:</p>
<pre><code>/* comment I */
.containerGrid {
background-color: white;
/* comment 1 */
/* comment 2 */
display: grid; /* comment 4 */ /* comment 5 */
grid-template:
"sourceselect . reviewbutton" auto
"pagination filters filters " auto
"rowcount filters filters " 20px
/ 2fr 1fr 2fr
;
width: 200px;
}
.containerFlex {
display: flex;
}
.content {
line-height: 1.5;
}
.header {
background-color: #ccc;
color: var(--bs-danger-alpha80);
display: inline-block;
}
.header:hover {
color: var(--bs-primary);
border-color: var(--bs-info) !important;
}
/* comment a */
.warning {
font-weight: 700;
color: var(--bs-warning);
opacity: 0;
} /* comment b */ /* comment c */
</code></pre>
<h4 id="js-context-file">JS context file</h4>
<p>
The expressions in the styles object (e.g. <code>colors.danger</code>,
<code>hexToRgbA(colors.danger, 0.8)</code>, etc.) were evaluated using the
following "context" file.
</p>
<p><code>./context.example.js</code>:</p>
<pre><code>const colors = {
danger: "var(--bs-danger)",
info: "var(--bs-info)",
primary: "var(--bs-primary)",
warning: "var(--bs-warning)",
};
function hexToRgbA(hex, alpha) {
return hex.replace(/\)$/, `-alpha${alpha * 100})`);
}
</code></pre>
Simple codemod example with jscodeshift
2021-05-03T17:47:28-07:00https://www.saltycrane.com/blog/2021/05/simple-codemod-example-jscodeshift/<p>
<a href="https://github.com/facebook/jscodeshift"><code>jscodeshift</code></a>
codemods allow refactoring JavaScript or TypeScript code by manipulating the
abstract syntax tree.
</p>
<p>
This is an example showing how to rename variables named <code>foo</code> to
<code>bar</code>.
</p>
<h4 id="install-jscodeshift">Install jscodeshift</h4>
<pre><code class="language-sh">npm install -g jscodeshift
</code></pre>
<h4 id="create-an-example-file-to-modify">Create an example file to modify</h4>
<ul>
<li>
<p>create a folder</p>
<pre><code class="language-sh">mkdir my-project
cd my-project
</code></pre>
</li>
<li>
<p>create an example file, <code>my-file-to-modify.js</code></p>
<pre><code class="language-js">const foo = 1;
console.log(foo);
</code></pre>
</li>
</ul>
<h4 id="create-a-transform">Create a transform</h4>
<p>create a file <code>my-transform.js</code></p>
<pre><code class="language-js">module.exports = function transformer(fileInfo, api) {
return api
.jscodeshift(fileInfo.source)
.find(api.jscodeshift.Identifier)
.forEach(function (path) {
if (path.value.name === "foo") {
api.jscodeshift(path).replaceWith(api.jscodeshift.identifier("bar"));
}
})
.toSource();
};
</code></pre>
<h4 id="run-it">Run it</h4>
<pre><code>jscodeshift -t my-transform.js ./my-file-to-modify.js
</code></pre>
<p>The file <code>my-file-to-modify.js</code> now contains:</p>
<pre><code>const bar = 1;
console.log(bar);
</code></pre>
<h4 id="another-example">Another example</h4>
<p>
This example removes the React JSX element <code><MyHeader /></code> and
removes the <code>MyHeader</code> import. I'm not sure why, but it added
some extra parentheses. Prettier cleaned this up for me, but if you have an
improvement, let me know.
</p>
<pre><code>// removeMyHeader.js
module.exports = function transformer(file, api) {
const jscodeshift = api.jscodeshift;
const withoutElement = jscodeshift(file.source)
.find(jscodeshift.JSXElement)
.forEach(function (path) {
if (path.value.openingElement.name.name === "MyHeader") {
path.prune();
}
})
.toSource();
const withoutImport = jscodeshift(withoutElement)
.find(jscodeshift.ImportDefaultSpecifier)
.forEach(function (path) {
if (path.value.local.name === "MyHeader") {
path.parentPath.parentPath.prune();
}
})
.toSource();
return withoutImport;
};
</code></pre>
<p>Here is a command to run it for a React TypeScript codebase:</p>
<pre><code>jscodeshift --parser=tsx --extensions=tsx -t ./removeMyHeader.js ./src
</code></pre>
<h4 id="ast-explorer">AST Explorer</h4>
<p>
<a href="https://astexplorer.net/">AST Explorer</a> is a very helpful tool to
experiment and learn the API with code completion. Go to
<a href="https://astexplorer.net/">https://astexplorer.net/</a> and select
"jscodeshift" under the "Transform" menu.
</p>
<h4 id="lodash-error">lodash error</h4>
<pre><code>Error: Cannot find module 'lodash'
</code></pre>
<p>
When running <code>jscodeshift</code>, I got the above error so I ran
<code>npm install -g lodash</code> and this got rid of the error for me.
</p>
Buildtime vs runtime environment variables with Next.js and Docker
2021-04-13T13:06:02-07:00https://www.saltycrane.com/blog/2021/04/buildtime-vs-runtime-environment-variables-nextjs-docker/<p>
For a Next.js app, <strong>buildtime</strong> environment variables are
variables that are used when the <code>next build</code> command runs.
<strong>Runtime</strong> variables are variables used when the
<code>next start</code> command runs.
</p>
<p>
Below are ways to set buildtime and rutime environment variables with Docker
and ways to use buildtime and runtime environment variables with Next.js. Note
the <code>Dockerfile</code> is written for simplicity to illustrate the
examples. For a more optimized Next.js Docker build see my
<a href="/blog/2021/04/nextjs-gitlab-cicd-docker-multi-stage-example/"
>Docker multi-stage CI example</a
>.
</p>
<h4 id="methods-for-setting-environment-variables-with-docker">
Methods for setting environment variables with Docker
</h4>
<table>
<thead>
<tr>
<th align="left">Method</th>
<th align="center">Available at buildtime</th>
<th align="center">Available at runtime</th>
<th align="center">Value passed to <code>docker build</code></th>
<th align="center">Value passed to <code>docker run</code></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>ARG</code></td>
<td align="center">✔</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="left">
<a
href="#setting-static-environment-variables-for-buildtime-and-runtime"
><code>ENV</code></a
>
</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="left">
<a href="#setting-dynamic-buildtime-environment-variables"
><code>ARG</code> + <code>docker build --build-arg</code></a
>
</td>
<td align="center">✔</td>
<td align="center"></td>
<td align="center">✔</td>
<td align="center"></td>
</tr>
<tr>
<td align="left">
<a
href="#setting-dynamic-buildtime-environment-variables-that-are-available-at-runtime-also"
><code>ARG</code> + <code>ENV</code> +
<code>docker build --build-arg</code></a
>
</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center"></td>
</tr>
<tr>
<td align="left">
<a href="#setting-dynamic-runtime-environment-variables"
><code>docker run --env</code></a
>
</td>
<td align="center"></td>
<td align="center">✔</td>
<td align="center"></td>
<td align="center">✔</td>
</tr>
</tbody>
</table>
<h4 id="methods-for-using-environment-variables-in-nextjs">
Methods for using environment variables in Next.js
</h4>
<table>
<thead>
<tr>
<th align="left">Method</th>
<th align="left">Set at</th>
<th align="center">
Available in Next.js client side rendered code (browser)
</th>
<th align="center">Available in Next.js server side rendered code</th>
<th align="center">Available in Node.js</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">
<a href="https://nextjs.org/docs/basic-features/environment-variables"
><code>.env</code> files</a
>
</td>
<td align="left">?both?</td>
<td align="center"></td>
<td align="center">✔</td>
<td align="center"></td>
<td>
<code>process.env</code> cannot be destructured or accessed with dynamic
properties
</td>
</tr>
<tr>
<td align="left">
<a
href="https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser"
><code>NEXT_PUBLIC_</code> prefixed vars in <code>.env</code> files</a
>
</td>
<td align="left">buildtime</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center"></td>
<td>
<code>process.env</code> cannot be destructured or accessed with dynamic
properties
</td>
</tr>
<tr>
<td align="left">
<a href="#using-buildtime-environment-variables"
><code>env</code> in <code>next.config.js</code></a
>
</td>
<td align="left">buildtime</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center"></td>
<td>
<code>process.env</code> cannot be destructured or accessed with dynamic
properties
</td>
</tr>
<tr>
<td align="left">
<a
href="#using-runtime-environment-variables-client-side-or-server-side"
><code>publicRuntimeConfig</code></a
>
</td>
<td align="left">runtime</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center"></td>
<td>Requires page uses SSR</td>
</tr>
<tr>
<td align="left">
<a href="#using-runtime-environment-variables-server-side-only"
><code>serverRuntimeConfig</code></a
>
</td>
<td align="left">runtime</td>
<td align="center"></td>
<td align="center">✔</td>
<td align="center"></td>
<td></td>
</tr>
<tr>
<td align="left">
<a
href="#using-runtime-environment-variables-server-side-not-processed-by-nextjs"
><code>process.env</code></a
>
</td>
<td align="left">runtime</td>
<td align="center"></td>
<td align="center"></td>
<td align="center">✔</td>
<td></td>
</tr>
</tbody>
</table>
<h4 id="assume-this-packagejson-for-the-examples-below">
Assume this <code>package.json</code> for the examples below
</h4>
<pre><code class="language-json">{
"scripts": {
"build": "next build",
"dev": "next",
"start": "next start"
},
"dependencies": {
"next": "^10.0.9",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
</code></pre>
<h4 id="setting-static-environment-variables-for-buildtime-and-runtime">
Setting static environment variables for buildtime and runtime
</h4>
<p>
Environment variables can be specified with the <code>ENV</code> instruction
in a <code>Dockerfile</code>. Below <code>MY_VAR</code> will be available to
both <code>next build</code> and <code>next start</code>. For more information
see
<a href="https://docs.docker.com/engine/reference/builder/#env"
>https://docs.docker.com/engine/reference/builder/#env</a
>
</p>
<p>
<strong><code>Dockerfile</code></strong>
</p>
<pre><code class="language-Dockerfile">FROM node:14-alpine
ENV MY_VAR=cake
WORKDIR /app
COPY . ./
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
</code></pre>
<p><strong>Docker build</strong></p>
<pre><code class="language-sh">docker build -t mytag .
</code></pre>
<p><strong>Docker run</strong></p>
<pre><code class="language-sh">docker run mytag
</code></pre>
<h4 id="setting-dynamic-buildtime-environment-variables">
Setting dynamic buildtime environment variables
</h4>
<p>
Dynamic environment variables can be passed to the
<code>docker build</code> command using <code>--build-arg</code> and used in
the <code>Dockerfile</code> with the <code>ARG</code> statement. Below
<code>MY_VAR</code> is an environment variable available to
<code>next build</code>.
</p>
<p>
Note that <code>MY_VAR</code> is not available to <code>next start</code>.
<code>ARG</code> statements act like <code>ENV</code> statements in that they
are treated like environment variables during <code>docker build</code>, but
they are not persisted in the image. To make them available during
<code>docker run</code> (and <code>next start</code>) set the value using
<code>ENV</code> (see the next example).
</p>
<p>
For more information see
<a href="https://docs.docker.com/engine/reference/builder/#arg"
>https://docs.docker.com/engine/reference/builder/#arg</a
>
</p>
<p>
<strong><code>Dockerfile</code></strong>
</p>
<pre><code>FROM node:14-alpine
ARG MY_VAR
WORKDIR /app
COPY . ./
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
</code></pre>
<p><strong>Docker build</strong></p>
<pre><code>docker build --build-arg MY_VAR=cake -t mytag .
</code></pre>
<p><strong>Docker run</strong></p>
<pre><code>docker run mytag
</code></pre>
<h4
id="setting-dynamic-buildtime-environment-variables-that-are-available-at-runtime-also"
>
Setting dynamic buildtime environment variables that are available at runtime
also
</h4>
<p>
The variable in the previous example, set using <code>ARG</code>, is not
persisted in the Docker image so it is not available at runtime. To make it
available at runtime, copy the value from <code>ARG</code> to
<code>ENV</code>.
</p>
<p>
<strong><code>Dockerfile</code></strong>
</p>
<pre><code>FROM node:14-alpine
ARG MY_VAR
ENV MY_VAR=$MYVAR
WORKDIR /app
COPY . ./
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
</code></pre>
<p><strong>Docker build</strong></p>
<pre><code>docker build --build-arg MY_VAR=cake -t mytag .
</code></pre>
<p><strong>Docker run</strong></p>
<pre><code>docker run mytag
</code></pre>
<h4 id="setting-dynamic-runtime-environment-variables">
Setting dynamic runtime environment variables
</h4>
<p>
Dynamic environment variables can be passed to <code>docker run</code> using
the <code>--env</code> flag. These will not be available to
<code>next build</code> but they will be available to <code>next start</code>.
For more information see
<a
href="https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file"
>https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file</a
>
</p>
<p>
<strong><code>Dockerfile</code></strong>
</p>
<pre><code>FROM node:14-alpine
WORKDIR /app
COPY . ./
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
</code></pre>
<p><strong>Docker build</strong></p>
<pre><code>docker build -t mytag .
</code></pre>
<p><strong>Docker run</strong></p>
<pre><code>docker run --env MY_VAR=cake mytag
</code></pre>
<h4 id="using-buildtime-environment-variables">
Using buildtime environment variables
</h4>
<p>
To use buildtime environment variables in Next.js code, set them using
<code>env</code> in <code>next.config.js</code>. Then access them via
<code>process.env</code> in your app code. NOTE:
<code>process.env</code> cannot be destructured or used with dynamic property
access. Next.js does a string substituion at build time using the webpack
<a href="https://webpack.js.org/plugins/define-plugin/">DefinePlugin</a>. For
more information see
<a
href="https://nextjs.org/docs/api-reference/next.config.js/environment-variables"
>https://nextjs.org/docs/api-reference/next.config.js/environment-variables</a
>
</p>
<p>
<strong><code>next.config.js</code></strong>
</p>
<pre><code>module.exports = {
env: {
MY_VAR: process.env.MY_VAR
}
}
</code></pre>
<p>
<strong><code>my-app-file.js</code></strong>
</p>
<pre><code>console.log(process.env.MY_VAR)
</code></pre>
<h4 id="using-runtime-environment-variables-client-side-or-server-side">
Using runtime environment variables (client-side or server-side)
</h4>
<p>
To use runtime environment variables (client-side or server-side), set them
using <code>publicRuntimeConfig</code> in <code>next.config.js</code>. Then
access them using <code>getConfig</code> from <code>next/config</code>. NOTE:
this only works for Next.js pages where server-side rendering (SSR) is used.
i.e. the page must use
<a
href="https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering"
><code>getServerSideProps</code></a
>
or
<a href="https://nextjs.org/docs/api-reference/data-fetching/getInitialProps"
><code>getInitialProps</code></a
>. For more information see
<a
href="https://nextjs.org/docs/api-reference/next.config.js/runtime-configuration"
>https://nextjs.org/docs/api-reference/next.config.js/runtime-configuration</a
>
</p>
<p>
<strong><code>next.config.js</code></strong>
</p>
<pre><code>module.exports = {
publicRuntimeConfig: {
MY_VAR: process.env.MY_VAR
}
}
</code></pre>
<p>
<strong><code>my-app-file.js</code></strong>
</p>
<pre><code>import getConfig from "next/config";
const { publicRuntimeConfig } = getConfig();
console.log(publicRuntimeConfig.MY_VAR)
</code></pre>
<h4 id="using-runtime-environment-variables-server-side-only">
Using runtime environment variables (server-side only)
</h4>
<p>
To use runtime environment variables (server-side only), set them using
<code>serverRuntimeConfig</code> in <code>next.config.js</code>. Then access
them using <code>getConfig</code> from <code>next/config</code>. For more
information see
<a
href="https://nextjs.org/docs/api-reference/next.config.js/runtime-configuration"
>https://nextjs.org/docs/api-reference/next.config.js/runtime-configuration</a
>
</p>
<p>
NOTE: this applies to to files Next.js "builds". Server run files
not processed by Next.js can use <code>process.env</code> to access
environment variables. See below.
</p>
<p>
<strong><code>next.config.js</code></strong>
</p>
<pre><code>module.exports = {
serverRuntimeConfig: {
MY_VAR: process.env.MY_VAR
}
}
</code></pre>
<p>
<strong><code>my-app-file.js</code></strong>
</p>
<pre><code>import getConfig from "next/config";
const { serverRuntimeConfig } = getConfig();
console.log(serverRuntimeConfig.MY_VAR)
</code></pre>
<h4
id="using-runtime-environment-variables-server-side-not-processed-by-nextjs"
>
Using runtime environment variables server-side (not processed by Next.js)
</h4>
<p>
For files not processed by Next.js (<code>next build</code>) (e.g. a
<code>server.js</code> file run by <code>node</code>), runtime environment
variables can be accessed on the server via <code>process.env</code>. NOTE:
"runtime" variables here means variables used when the Node.js
process runs. For more information see
<a
href="https://nodejs.org/docs/latest-v14.x/api/process.html#process_process_env"
>https://nodejs.org/docs/latest-v14.x/api/process.html#process_process_env</a
>
</p>
<p>
<strong><code>server.js</code></strong>
</p>
<pre><code>console.log(process.env.MY_VAR)
</code></pre>
<h4 id="nextjs-assetprefix">Next.js <code>assetPrefix</code></h4>
<p>
If the
<a
href="https://nextjs.org/docs/api-reference/next.config.js/cdn-support-with-asset-prefix"
>Next.js <code>assetPrefix</code></a
>
is set in <code>next.config.js</code> using an environment variable, the
environment variable should be set at buildtime for
<a
href="https://nextjs.org/docs/advanced-features/automatic-static-optimization"
>Next.js static pages</a
>
but set at runtime for
<a href="https://nextjs.org/docs/basic-features/pages#server-side-rendering"
>server rendered pages</a
>.
</p>
<p>
<strong><code>next.config.js</code></strong>
</p>
<pre><code>module.exports = {
assetPrefix: process.env.MY_ASSET_PREFIX
}
</code></pre>
Next.js GitLab CI/CD Docker multi-stage example
2021-04-06T09:59:46-07:00https://www.saltycrane.com/blog/2021/04/nextjs-gitlab-cicd-docker-multi-stage-example/<p>
This describes an example <a href="https://nextjs.org/">Next.js</a> project
with a <a href="https://docs.gitlab.com/ee/ci/">GitLab CI/CD</a> pipeline that
does the following:
</p>
<ul>
<li>installs npm packages and builds static assets</li>
<li>runs ESLint, TypeScript, and Cypress</li>
<li>builds a Docker image for deployment</li>
<li>
pushes the Docker image to the
<a href="https://docs.gitlab.com/ee/user/packages/container_registry/"
>GitLab Container Registry</a
>
</li>
</ul>
<p>
This example prepares a Docker image for deployment but doesn't actually
deploy it. See
<a
href="/blog/2021/03/example-nextjs-gitlab-cicd-amazon-ecr-and-ecs-deploy-pipeline/"
>an example CI/CD pipeline that deploys to Amazon ECS</a
>.
</p>
<p>
To increase speed and reduce image size, it uses
<a href="https://docs.docker.com/develop/develop-images/multistage-build/"
>Docker multi-stage builds</a
>.
</p>
<ul>
<li>
GitLab repo:
<a
href="https://gitlab.com/saltycrane/next-docker-multi-stage-gitlab-ci-cd-example"
>https://gitlab.com/saltycrane/next-docker-multi-stage-gitlab-ci-cd-example</a
>
</li>
<li>
CI/CD Pipelines:
<a
href="https://gitlab.com/saltycrane/next-docker-multi-stage-gitlab-ci-cd-example/-/pipelines"
>https://gitlab.com/saltycrane/next-docker-multi-stage-gitlab-ci-cd-example/-/pipelines</a
>
</li>
</ul>
<h4 id="dockerfile"><code>Dockerfile</code></h4>
<p>The Dockerfile defines 3 stages:</p>
<ul>
<li>
the "builder" stage installs npm packages and builds static
assets. It produces artifacts (<code>/app</code> and
<code>/root/.cache</code>) that are used by the cypress and deploy stages.
It is also used to build an image used to run ESLint and TypeScript.
</li>
<li>
the "cypress" stage uses a different base image from the
"builder" stage and is used to run cypress tests
</li>
<li>
the final deploy stage copies the <code>/app</code> directory from the
"builder" stage and sets <code>NODE_ENV</code> to
"production" and exposes port 3000
</li>
</ul>
<pre><code>ARG BASE_IMAGE=node:14.16-alpine
# ================================================================
# builder stage
# ================================================================
FROM $BASE_IMAGE as builder
ENV NODE_ENV=test
ENV NEXT_TELEMETRY_DISABLED=1
RUN apk add --no-cache bash git
WORKDIR /app
COPY ./package.json ./
COPY ./package-lock.json ./
RUN CI=true npm ci
COPY . ./
RUN NODE_ENV=production npm run build
# ================================================================
# cypress stage
# ================================================================
FROM cypress/base:14.16.0 as cypress
WORKDIR /app
# copy cypress from the builder image
COPY --from=builder /root/.cache /root/.cache/
COPY --from=builder /app ./
ENV NODE_ENV=test
ENV NEXT_TELEMETRY_DISABLED=1
# ================================================================
# final deploy stage
# ================================================================
FROM $BASE_IMAGE
WORKDIR /app
COPY --from=builder /app ./
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
EXPOSE 3000
CMD ["npm", "start"]
</code></pre>
<h4 id="gitlab-ciyml"><code>.gitlab-ci.yml</code></h4>
<ul>
<li>
3 images are built: <code>test</code>, <code>cypress</code>, and
<code>deploy</code>. The <code>test</code> image is used for running ESLint
and TypeScript and is needed for <code>cypress</code> and
<code>deploy</code>. The <code>cypress</code> image is used for running
Cypress.
</li>
<li>
it uses
<a href="https://docs.docker.com/develop/develop-images/build_enhancements/"
>Docker BuildKit</a
>
to make caching easier. (With BuildKit, cached layers will be automatically
pulled when needed. Without BuildKit, images used for caching need to be
explicitly pulled.) For comparison, see
<a
href="https://gitlab.com/saltycrane/next-docker-multi-stage-gitlab-ci-cd-example/-/commit/6b238a34a8bdeb310d17fcbaa83227ba2eb78d92"
>this diff adding BuildKit</a
>. Note <code>DOCKER_BUILDKIT</code> is set to <code>1</code> to enable
BuildKit.
</li>
</ul>
<pre><code>variables:
# enable docker buildkit. Used with `BUILDKIT_INLINE_CACHE=1` below
DOCKER_BUILDKIT: 1
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TEST: $CI_REGISTRY_IMAGE/test:latest
IMAGE_CYPRESS: $CI_REGISTRY_IMAGE/cypress:latest
IMAGE_DEPLOY: $CI_REGISTRY_IMAGE/deploy:latest
stages:
- build
- misc
- deploy
.base:
image: docker:latest
services:
- docker:dind
before_script:
- docker --version
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
build:builder:
extends: .base
stage: build
script:
- docker build --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from "$IMAGE_TEST" --target builder -t "$IMAGE_TEST" .
- docker push "$IMAGE_TEST"
build:deployimage:
extends: .base
stage: misc
needs: ["build:builder"]
script:
- docker build --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from "$IMAGE_DEPLOY" --cache-from "$IMAGE_TEST" --cache-from "$IMAGE_CYPRESS" -t "$IMAGE_DEPLOY" .
- docker push "$IMAGE_DEPLOY"
test:cypress:
extends: .base
stage: misc
needs: ["build:builder"]
script:
- docker build --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from "$IMAGE_CYPRESS" --cache-from "$IMAGE_TEST" --target cypress -t "$IMAGE_CYPRESS" .
- docker push "$IMAGE_CYPRESS"
- docker run "$IMAGE_CYPRESS" npm run cy:citest
test:eslint:
extends: .base
stage: misc
needs: ["build:builder"]
script:
- docker run "$IMAGE_TEST" npm run eslint
test:typescript:
extends: .base
stage: misc
needs: ["build:builder"]
script:
- docker run "$IMAGE_TEST" npm run tsc
deploy:
stage: deploy
needs: ["build:deployimage", "test:cypress", "test:eslint", "test:typescript"]
script:
- echo "deploy here"
</code></pre>
<h4 id="dockerignore"><code>.dockerignore</code></h4>
<p>
Adding the <code>.git</code> directory to <code>.dockerignore</code> prevented
cache invalidation for the <code>COPY . ./</code> command in the
<code>Dockerfile</code>.
</p>
<pre><code>.git
</code></pre>
<h4 id="references">References</h4>
<ul>
<li>
<a href="https://docs.docker.com/develop/develop-images/multistage-build/"
>https://docs.docker.com/develop/develop-images/multistage-build/</a
>
</li>
<li>
<a
href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#making-docker-in-docker-builds-faster-with-docker-layer-caching"
>https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#making-docker-in-docker-builds-faster-with-docker-layer-caching</a
>
</li>
<li>
<a href="https://docs.gitlab.com/ee/ci/directed_acyclic_graph/index.html"
>https://docs.gitlab.com/ee/ci/directed_acyclic_graph/index.html</a
>
</li>
<li>
<a href="https://docs.gitlab.com/ee/ci/yaml/README.html#needs"
>https://docs.gitlab.com/ee/ci/yaml/README.html#needs</a
>
</li>
<li>
<a href="https://testdriven.io/blog/faster-ci-builds-with-docker-cache/"
>https://testdriven.io/blog/faster-ci-builds-with-docker-cache/</a
>
</li>
<li>
<a
href="https://docs.docker.com/engine/reference/commandline/build/#specifying-external-cache-sources"
>https://docs.docker.com/engine/reference/commandline/build/#specifying-external-cache-sources</a
>
</li>
<li>
<a href="https://docs.docker.com/develop/develop-images/build_enhancements/"
>https://docs.docker.com/develop/develop-images/build_enhancements/</a
>
</li>
</ul>
Next.js Cypress GitLab CI example
2021-03-25T16:12:37-07:00https://www.saltycrane.com/blog/2021/03/nextjs-cypress-gitlab-ci-example/<p>
This is an example <a href="https://nextjs.org/">Next.js</a> project that runs
a <a href="https://www.cypress.io/">Cypress</a> test in Docker using a
<a href="https://docs.gitlab.com/ee/ci/">GitLab CI</a> pipeline. It also uses
the
<a href="https://docs.gitlab.com/ee/user/packages/container_registry/"
>GitLab Container Registry</a
>
for caching purposes.
</p>
<ul>
<li>
GitLab repo:
<a href="https://gitlab.com/saltycrane/next-cypress-gitlab-ci-example"
>https://gitlab.com/saltycrane/next-cypress-gitlab-ci-example</a
>
</li>
<li>
CI Pipelines:
<a
href="https://gitlab.com/saltycrane/next-cypress-gitlab-ci-example/-/pipelines"
>https://gitlab.com/saltycrane/next-cypress-gitlab-ci-example/-/pipelines</a
>
</li>
</ul>
<h4 id="gitlab-ciyml"><code>.gitlab-ci.yml</code></h4>
<pre><code>variables:
DOCKER_TLS_CERTDIR: "/certs"
stages:
- test
test-cypress:
stage: test
image: docker:latest
services:
- docker:dind
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:latest
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker pull $IMAGE_TAG || true
- docker build --cache-from $IMAGE_TAG -t $IMAGE_TAG .
- docker push $IMAGE_TAG
- docker run $IMAGE_TAG npm run cy:citest
</code></pre>
<h4 id="dockerfile"><code>Dockerfile</code></h4>
<p>
This uses the official Cypress Docker image (<a
href="https://github.com/cypress-io/cypress-docker-images/blob/master/base/14.16.0/Dockerfile"
><code>Dockerfile</code></a
>).
</p>
<pre><code>FROM cypress/base:14.16.0
WORKDIR /app
# run npm install before adding app code for better Docker caching
# https://semaphoreci.com/docs/docker/docker-layer-caching.html
COPY ./package.json /app
COPY ./package-lock.json /app
# CI=true suppresses Cypress progress log spam
RUN CI=true npm ci
COPY . /app
RUN npm run build
</code></pre>
<h4 id="packagejson"><code>package.json</code></h4>
<pre><code>{
"scripts": {
"build": "next build",
"cy:citest": "start-server-and-test start http://localhost:3000 cy:run",
"cy:run": "cypress run",
"dev": "next",
"start": "next start"
},
"dependencies": {
"next": "^10.0.9",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"cypress": "^6.8.0",
"start-server-and-test": "^1.12.1"
}
}
</code></pre>
<h4 id="cypressintegrationindex_specjs">
<code>cypress/integration/index_spec.js</code>
</h4>
<pre><code>describe("index page", () => {
it("loads successfully", () => {
cy.visit("http://localhost:3000");
cy.contains("Index");
});
});
</code></pre>
<h4 id="references">References</h4>
<ul>
<li>
<a
href="https://docs.cypress.io/guides/continuous-integration/introduction#Setting-up-CI"
>https://docs.cypress.io/guides/continuous-integration/introduction#Setting-up-CI</a
>
</li>
<li>
<a
href="https://docs.gitlab.com/ee/user/packages/container_registry/#build-and-push-by-using-gitlab-cicd"
>https://docs.gitlab.com/ee/user/packages/container_registry/#build-and-push-by-using-gitlab-cicd</a
>
</li>
</ul>
Example Next.js GitLab CI/CD Amazon ECR and ECS deploy pipeline
2021-03-25T10:29:45-07:00https://www.saltycrane.com/blog/2021/03/example-nextjs-gitlab-cicd-amazon-ecr-and-ecs-deploy-pipeline/<p>
I've created an example <a href="https://nextjs.org/">Next.js</a> project
with a <a href="https://docs.gitlab.com/ee/ci/">GitLab CI/CD</a> pipeline that
builds a Docker image, pushes it to
<a href="https://aws.amazon.com/ecr/">Amazon ECR</a>, deploys it to an
<a href="https://aws.amazon.com/ecs/">Amazon ECS</a>
<a href="https://aws.amazon.com/fargate/">Fargate</a> cluster, and uploads
static assets (JS, CSS, etc.) to
<a href="https://aws.amazon.com/s3/">Amazon S3</a>. The example GitLab repo is
here:
<a href="https://gitlab.com/saltycrane/next-aws-ecr-ecs-gitlab-ci-cd-example"
>https://gitlab.com/saltycrane/next-aws-ecr-ecs-gitlab-ci-cd-example</a
>
</p>
<h4 id="interesting-files">Interesting files</h4>
<p>
Here are the interesting parts of some of the files. See the full source code
in the
<a href="https://gitlab.com/saltycrane/next-aws-ecr-ecs-gitlab-ci-cd-example"
>GitLab repo</a
>.
</p>
<ul>
<li>
<p>
<code>.gitlab-ci.yml</code> (<a
href="https://gitlab.com/saltycrane/next-aws-ecr-ecs-gitlab-ci-cd-example/-/blob/main/.gitlab-ci.yml"
>view at gitlab</a
>)
</p>
<ul>
<li>
the variables <code>AWS_ACCESS_KEY_ID</code>,
<code>AWS_SECRET_ACCESS_KEY</code>, and <code>ECR_HOST</code> are set in
the GitLab UI under "Settings" > "CI/CD" >
"Variables"
</li>
<li>
this uses the
<a href="https://hub.docker.com/r/saltycrane/aws-cli-and-docker"
>saltycrane/aws-cli-and-docker</a
>
Docker image which provides the <code>aws</code> v2 command line tools
and <code>docker</code> in a single image. It is based on
<a href="https://hub.docker.com/r/amazon/aws-cli">amazon/aws-cli</a> and
installs bc, curl, docker, jq, and tar. This idea is from
<a href="https://www.youtube.com/watch?v=jg9sUceyGaQ"
>Valentin's tutorial</a
>.
</li>
</ul>
<pre><code>variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
AWS_DEFAULT_REGION: "us-east-1"
CI_APPLICATION_REPOSITORY: "$ECR_HOST/next-aws-ecr-ecs-gitlab-ci-cd-example"
CI_APPLICATION_TAG: "$CI_PIPELINE_IID"
CI_AWS_S3_BUCKET: "next-aws-ecr-ecs-gitlab-ci-cd-example"
CI_AWS_ECS_CLUSTER: "next-aws-ecr-ecs-gitlab-ci-cd-example"
CI_AWS_ECS_SERVICE: "next-aws-ecr-ecs-gitlab-ci-cd-example"
CI_AWS_ECS_TASK_DEFINITION: "next-aws-ecr-ecs-gitlab-ci-cd-example"
NEXT_JS_ASSET_URL: "https://$CI_AWS_S3_BUCKET.s3.amazonaws.com"
stages:
- build
- deploy
build:
stage: build
image: saltycrane/aws-cli-and-docker
services:
- docker:dind
script:
- ./bin/build-and-push-image-to-ecr
- ./bin/upload-assets-to-s3
deploy:
stage: deploy
image: saltycrane/aws-cli-and-docker
services:
- docker:dind
script:
- ./bin/ecs update-task-definition
</code></pre>
</li>
<li>
<p>
<code>Dockerfile</code> (<a
href="https://gitlab.com/saltycrane/next-aws-ecr-ecs-gitlab-ci-cd-example/-/blob/main/Dockerfile"
>view at gitlab</a
>)
</p>
<p>
The value of <code>NEXT_JS_ASSET_URL</code> is passed in using the
<code>--build-arg</code> option of the <code>docker build</code> command
run in <code>bin/build-and-push-image-to-ecr</code>. It is used like an
environment variable in the <code>RUN npm run build</code> command below.
In this project it is assigned to <code>assetPrefix</code> in
<code>next.config.js</code>.
</p>
<pre><code>FROM node:14.16-alpine
ARG NEXT_JS_ASSET_URL
ENV NODE_ENV=production
WORKDIR /app
COPY ./package.json ./
COPY ./package-lock.json ./
RUN npm ci
COPY . ./
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
</code></pre>
</li>
<li>
<p>
<code>bin/build-and-push-image-to-ecr</code> (<a
href="https://gitlab.com/saltycrane/next-aws-ecr-ecs-gitlab-ci-cd-example/-/blob/main/bin/build-and-push-image-to-ecr"
>view at gitlab</a
>)
</p>
<pre><code># log in to the amazon ecr docker registry
aws ecr get-login-password | docker login --username AWS --password-stdin "$ECR_HOST"
# build docker image
docker pull "$CI_APPLICATION_REPOSITORY:latest" || true
docker build --build-arg "NEXT_JS_ASSET_URL=$NEXT_JS_ASSET_URL" --cache-from "$CI_APPLICATION_REPOSITORY:latest" -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" -t "$CI_APPLICATION_REPOSITORY:latest" .
# push image to amazon ecr
docker push "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
docker push "$CI_APPLICATION_REPOSITORY:latest"
</code></pre>
</li>
<li>
<p>
<code>bin/upload-assets-to-s3</code> (<a
href="https://gitlab.com/saltycrane/next-aws-ecr-ecs-gitlab-ci-cd-example/-/blob/main/bin/upload-assets-to-s3"
>view at gitlab</a
>)
</p>
<pre><code>LOCAL_ASSET_PATH=/tmp/upload-assets
mkdir $LOCAL_ASSET_PATH
# copy the generated assets out of the docker image
docker run --rm --entrypoint tar "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" cf - .next | tar xf - -C $LOCAL_ASSET_PATH
# rename .next to _next
mv "$LOCAL_ASSET_PATH/.next" "$LOCAL_ASSET_PATH/_next"
# remove directories that should not be uploaded to S3
rm -rf "$LOCAL_ASSET_PATH/_next/cache"
rm -rf "$LOCAL_ASSET_PATH/_next/server"
# gzip files
find $LOCAL_ASSET_PATH -regex ".*\.\(css\|svg\|js\)$" -exec gzip {} \;
# strip .gz extension off of gzipped files
find $LOCAL_ASSET_PATH -name "*.gz" -exec sh -c 'mv $1 `echo $1 | sed "s/.gz$//"`' - {} \;
# upload gzipped js, css, and svg assets
aws s3 sync --no-progress $LOCAL_ASSET_PATH "s3://$CI_AWS_S3_BUCKET" --cache-control max-age=31536000 --content-encoding gzip --exclude "*" --include "*.js" --include "*.css" --include "*.svg"
# upload non-gzipped assets
aws s3 sync --no-progress $LOCAL_ASSET_PATH "s3://$CI_AWS_S3_BUCKET" --cache-control max-age=31536000 --exclude "*.js" --exclude "*.css" --exclude "*.svg" --exclude "*.map"
</code></pre>
</li>
<li>
<p>
<code>bin/ecs</code> (<a
href="https://gitlab.com/saltycrane/next-aws-ecr-ecs-gitlab-ci-cd-example/-/blob/main/bin/ecs"
>view full file</a
>) (This file was copied from the
<a href=""><code>gitlab-org</code> repo</a>)
</p>
<pre><code>#!/bin/bash -e
update_task_definition() {
local -A register_task_def_args=( \
['task-role-arn']='taskRoleArn' \
['execution-role-arn']='executionRoleArn' \
['network-mode']='networkMode' \
['cpu']='cpu' \
['memory']='memory' \
['pid-mode']='pidMode' \
['ipc-mode']='ipcMode' \
['proxy-configuration']='proxyConfiguration' \
['volumes']='volumes' \
['placement-constraints']='placementConstraints' \
['requires-compatibilities']='requiresCompatibilities' \
['inference-accelerators']='inferenceAccelerators' \
)
image_repository=$CI_APPLICATION_REPOSITORY
image_tag=$CI_APPLICATION_TAG
new_image_name="${image_repository}:${image_tag}"
register_task_definition_from_remote
new_task_definition=$(aws ecs register-task-definition "${args[@]}")
new_task_revision=$(read_task "$new_task_definition" 'revision')
new_task_definition_family=$(read_task "$new_task_definition" 'family')
# Making sure that we at least have one running task (even if desiredCount gets updated again with new task definition below)
service_task_count=$(aws ecs describe-services --cluster "$CI_AWS_ECS_CLUSTER" --services "$CI_AWS_ECS_SERVICE" --query "services[0].desiredCount")
if [[ $service_task_count == 0 ]]; then
aws ecs update-service --cluster "$CI_AWS_ECS_CLUSTER" --service "$CI_AWS_ECS_SERVICE" --desired-count 1
fi
# Update ECS service with newly created task defintion revision.
aws ecs update-service \
--cluster "$CI_AWS_ECS_CLUSTER" \
--service "$CI_AWS_ECS_SERVICE" \
--task-definition "$new_task_definition_family":"$new_task_revision"
return 0
}
read_task() {
val=$(echo "$1" | jq -r ".taskDefinition.$2")
if [ "$val" == "null" ];then
val=$(echo "$1" | jq -r ".$2")
fi
if [ "$val" != "null" ];then
echo -n "${val}"
fi
}
register_task_definition_from_remote() {
task=$(aws ecs describe-task-definition --task-definition "$CI_AWS_ECS_TASK_DEFINITION")
current_container_definitions=$(read_task "$task" 'containerDefinitions')
new_container_definitions=$(echo "$current_container_definitions" | jq --arg val "$new_image_name" '.[0].image = $val')
args+=("--family" "${CI_AWS_ECS_TASK_DEFINITION}")
args+=("--container-definitions" "${new_container_definitions}")
for option in "${!register_task_def_args[@]}"; do
value=$(read_task "$task" "${register_task_def_args[$option]}")
if [ -n "$value" ];then
args+=("--${option}" "${value}")
fi
done
}
update_task_definition
</code></pre>
</li>
</ul>
<h4 id="usage---set-up-aws-resources">Usage - set up AWS resources</h4>
<p>
Below are the minimum steps I needed to create the required AWS services for
my example. I use the AWS region <code>"us-east-1"</code>. For info
about creating some of these services via the command line, see my
<a href="/blog/2021/03/amazon-ecs-notes/">Amazon ECS notes</a>.
</p>
<p><strong>Create an ECR repository</strong></p>
<ul>
<li>
create a private ECR repository here:
<a href="https://console.aws.amazon.com/ecr/repositories?region=us-east-1"
>https://console.aws.amazon.com/ecr/repositories?region=us-east-1</a
>
</li>
<li>name the repository "next-aws-ecr-ecs-gitlab-ci-cd-example"</li>
<li>
leave "Tag immutability" disabled to allow the "latest"
tag to be overwritten
</li>
</ul>
<p><strong>Create an ECS Fargate cluster</strong></p>
<ul>
<li>
click "Create Cluster" here:
<a
href="https://us-east-1.console.aws.amazon.com/ecs/home?region=us-east-1#/clusters"
>https://us-east-1.console.aws.amazon.com/ecs/home?region=us-east-1#/clusters</a
>
</li>
<li>choose "Networking only" (Fargate)</li>
<li>name the cluster "next-aws-ecr-ecs-gitlab-ci-cd-example"</li>
<li>check the "Create VPC" checkbox</li>
<li>click "Create"</li>
</ul>
<p><strong>Create an ECS task definition</strong></p>
<ul>
<li>
click "Create new Task Definition" here:
<a
href="https://console.aws.amazon.com/ecs/home?region=us-east-1#/taskDefinitions"
>https://console.aws.amazon.com/ecs/home?region=us-east-1#/taskDefinitions</a
>
</li>
<li>select "FARGATE" and click "Next step"</li>
<li>
configure task
<ul>
<li>
for "Task Definition Name" enter
"next-aws-ecr-ecs-gitlab-ci-cd-example"
</li>
<li>for "Task Role" select "None"</li>
<li>
for "Task execution role" select "Create new role"
</li>
<li>for "Task memory" select "0.5GB"</li>
<li>for "Task CPU" select "0.25 vCPU"</li>
<li>
click "Add container"
<ul>
<li>
for "Container Name" enter
"next-aws-ecr-ecs-gitlab-ci-cd-example"
</li>
<li>
for "Image" enter "asdf" (this will be updated
by the gitlab ci/cd job)
</li>
<li>leave "Private repository authentication" unchecked</li>
<li>for "Port mappings" enter "3000"</li>
<li>click "Add"</li>
</ul>
</li>
<li>click "Create"</li>
</ul>
</li>
</ul>
<p><strong>Create an ECS service</strong></p>
<ul>
<li>
click "Create" here:
<a
href="https://us-east-1.console.aws.amazon.com/ecs/home?region=us-east-1#/clusters/next-aws-ecr-ecs-gitlab-ci-cd-example/services"
>https://us-east-1.console.aws.amazon.com/ecs/home?region=us-east-1#/clusters/next-aws-ecr-ecs-gitlab-ci-cd-example/services</a
>
</li>
<li>
configure service
<ul>
<li>for "Launch type" select "FARGATE"</li>
<li>
for "Task Definition" enter
"next-aws-ecr-ecs-gitlab-ci-cd-example"
</li>
<li>
for "Cluster" select
"next-aws-ecr-ecs-gitlab-ci-cd-example"
</li>
<li>
for "Service name" enter
"next-aws-ecr-ecs-gitlab-ci-cd-example"
</li>
<li>for "Number of tasks" enter 1</li>
<li>for "Deployment type" select "Rolling update"</li>
<li>click "Next step"</li>
</ul>
</li>
<li>
configure network
<ul>
<li>
select the appropriate "Cluster VPC" and two
"Subnets"
</li>
<li>click "Next step"</li>
</ul>
</li>
<li>
set Auto Scaling
<ul>
<li>click "Next step"</li>
</ul>
</li>
<li>
review
<ul>
<li>click "Create Service"</li>
</ul>
</li>
</ul>
<p><strong>Open port 3000</strong></p>
<ul>
<li>
on the ECS service page
<a
href="https://us-east-1.console.aws.amazon.com/ecs/home?region=us-east-1#/clusters/next-aws-ecr-ecs-gitlab-ci-cd-example/services/next-aws-ecr-ecs-gitlab-ci-cd-example/details"
>https://us-east-1.console.aws.amazon.com/ecs/home?region=us-east-1#/clusters/next-aws-ecr-ecs-gitlab-ci-cd-example/services/next-aws-ecr-ecs-gitlab-ci-cd-example/details</a
>
under "Network Access", next to "Security groups", click
the link to the security group
</li>
<li>click "Actions" then click "Edit inbound rules"</li>
<li>click "Add rule"</li>
<li>for "Port range" enter "3000"</li>
<li>for "Source" select "0.0.0.0/0"</li>
<li>click "Save rules"</li>
</ul>
<p><strong>Create a S3 bucket</strong></p>
<ul>
<li>
click "Create bucket" here:
<a href="https://s3.console.aws.amazon.com/s3/home?region=us-east-1"
>https://s3.console.aws.amazon.com/s3/home?region=us-east-1</a
>
</li>
<li>
for "Bucket name" enter
"next-aws-ecr-ecs-gitlab-ci-cd-example"
</li>
<li>uncheck "Block all public access"</li>
<li>
check the "I acknowledge that the current settings might result in this
bucket and the objects within becoming public" checkbox
</li>
<li>click "Create bucket"</li>
</ul>
<p><strong>Update permissions for S3 bucket</strong></p>
<ul>
<li>
go to Permissions (<a
href="https://s3.console.aws.amazon.com/s3/buckets/next-aws-ecr-ecs-gitlab-ci-cd-example?region=us-east-1&tab=permissions"
>https://s3.console.aws.amazon.com/s3/buckets/next-aws-ecr-ecs-gitlab-ci-cd-example?region=us-east-1&tab=permissions</a
>) and under "Bucket policy", click "Edit"
</li>
<li>
enter:
<pre><code>{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::next-aws-ecr-ecs-gitlab-ci-cd-example/*"
}
]
}
</code></pre>
</li>
<li>click "Save changes"</li>
</ul>
<p><strong>Create an IAM user</strong></p>
<ul>
<li>
<a
href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/get-set-up-for-amazon-ecs.html#create-an-iam-user"
>create an IAM user</a
>. The user must have at least ECR, ECS, and S3 permissions.
</li>
<li>
take note of the <code>AWS_ACCESS_KEY_ID</code> and
<code>AWS_SECRET_ACCESS_KEY</code>
</li>
</ul>
<h4 id="usage---run-the-cicd-pipeline">Usage - run the CI/CD pipeline</h4>
<p>
<strong>Fork the example gitlab repo and configure CI/CD variables</strong>
</p>
<ul>
<li>
fork
<a
href="https://gitlab.com/saltycrane/next-aws-ecr-ecs-gitlab-ci-cd-example"
>https://gitlab.com/saltycrane/next-aws-ecr-ecs-gitlab-ci-cd-example</a
>
</li>
<li>
go to "Settings" > "CI/CD" > "Variables"
and add the following variables. You can choose to "protect" and
"mask" all of them.
<ul>
<li><code>AWS_ACCESS_KEY_ID</code></li>
<li><code>AWS_SECRET_ACCESS_KEY</code></li>
<li>
<code>ECR_HOST</code> (This is the part of the ECR repository URI before
the <code>/</code>. It looks something like
<code>XXXXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com</code>)
</li>
</ul>
</li>
</ul>
<p>
<strong>Edit variables in <code>.gitlab-ci.yml</code></strong>
</p>
<p>
If you used names other than
"next-aws-ecr-ecs-gitlab-ci-cd-example", edit the variables in
<code>.gitlab-ci.yml</code>.
</p>
<p><strong>Test it</strong></p>
<ul>
<li>clone the repo and push a commit</li>
<li>
see the pipeline running under "CI/CD" > "Pipelines"
</li>
<li>
go to the cluster tasks page:
<a
href="https://us-east-1.console.aws.amazon.com/ecs/home?region=us-east-1#/clusters/next-aws-ecr-ecs-gitlab-ci-cd-example/tasks"
>https://us-east-1.console.aws.amazon.com/ecs/home?region=us-east-1#/clusters/next-aws-ecr-ecs-gitlab-ci-cd-example/tasks</a
>
</li>
<li>click on the task and copy the "Public IP"</li>
<li>
enter the public IP followed by <code>:3000</code> in the browser (Note: the
IP address changes for every <code>git push</code>. A
<a href="https://aws.amazon.com/elasticloadbalancing/">load balancer</a>
should probably be used, but I didn't do that.)
</li>
</ul>
<h4 id="references-buildpush">References (build/push)</h4>
<ul>
<li>
<a href="https://www.youtube.com/watch?v=jg9sUceyGaQ"
>https://www.youtube.com/watch?v=jg9sUceyGaQ</a
>
</li>
<li>
<a
href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#docker"
>https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#docker</a
>
</li>
<li>
<a
href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#using-docker-caching"
>https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#using-docker-caching</a
>
</li>
</ul>
<h4 id="references-deploy">References (deploy)</h4>
<ul>
<li>
<a
href="https://docs.gitlab.com/ee/ci/cloud_deployment/#deploy-your-application-to-the-aws-elastic-container-service-ecs"
>https://docs.gitlab.com/ee/ci/cloud_deployment/#deploy-your-application-to-the-aws-elastic-container-service-ecs</a
>
</li>
<li>
<a
href="https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/AWS/Deploy-ECS.gitlab-ci.yml"
>https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/AWS/Deploy-ECS.gitlab-ci.yml</a
>
</li>
<li>
<a
href="https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml"
>https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml</a
>
</li>
<li>
<a
href="https://gitlab.com/gitlab-org/cloud-deploy/-/blob/master/aws/ecs/Dockerfile"
>https://gitlab.com/gitlab-org/cloud-deploy/-/blob/master/aws/ecs/Dockerfile</a
>
</li>
<li>
<a
href="https://gitlab.com/gitlab-org/cloud-deploy/-/blob/master/aws/src/bin/ecs"
>https://gitlab.com/gitlab-org/cloud-deploy/-/blob/master/aws/src/bin/ecs</a
>
</li>
</ul>
How to download pull request metadata using the GitHub GraphQL API
2021-02-22T10:33:43-08:00https://www.saltycrane.com/blog/2021/02/how-download-pull-request-metadata-using-github-graphql-api/<p>
This is a TypeScript Node.js script to download GitHub pull request
information (title, body, comments, etc.) using the GitHub GraphQL API. The
data is saved in a JSON file.
</p>
<p>
The GitHub repo is here:
<a href="https://github.com/saltycrane/download-github-prs">
<code>download-github-prs</code> </a
>.
</p>
<h4>Create a GitHub personal access token</h4>
<p>
Create a GitHub personal access token as described here (no checkboxes need to
be selected for public repos):
<a
href="https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token"
>https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token</a
>
This is used to access the GitHub GraphQL API.
</p>
<h4>Intall libraries</h4>
<ul>
<li>
Create a directory and cd into it
<pre class="console">
$ mkdir -p /tmp/my-prs
$ cd /tmp/my-prs </pre
>
</li>
<li>
Create package.json file:
<pre class="json">
{
"scripts": {
"download": "ts-node index.ts"
},
"dependencies": {
"node-fetch": "^2.6.1"
},
"devDependencies": {
"@types/node-fetch": "^2.5.7",
"ts-node": "^9.0.0",
"typescript": "^4.1.2"
}
}</pre
>
</li>
<li>
Install
<pre class="console">$ npm install </pre>
</li>
<h4>Create script</h4>
<p>
Creat a file, <code>/tmp/my-prs/index.ts</code>, replacing
<code>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</code> with the GitHub
personal access token described above.
</p>
<pre class="js">
import * as fs from "fs";
import fetch from "node-fetch";
const GITHUB_TOKEN = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
const GITHUB_GRAPHQL_API_URL = "https://api.github.com/graphql";
const OUTPUT_DIR = "/tmp/my-prs";
const REPO_OWNER = "facebookexperimental";
const REPO_NAME = "Recoil";
fetchPullRequest(1);
/**
* fetchPullRequest
*/
async function fetchPullRequest(prNumber: number) {
const reactionFragment = `
content
user {
login
}
`;
const userContentEditFragment = `
createdAt
deletedAt
deletedBy {
login
}
diff
editedAt
editor {
login
}
updatedAt
`;
const commentFragment = `
author {
login
}
body
createdAt
reactions(first: 100) {
nodes {
${reactionFragment}
}
}
userContentEdits(first: 100) {
nodes {
${userContentEditFragment}
}
}
`;
const query = `
query {
repository(owner: "${REPO_OWNER}", name: "${REPO_NAME}") {
nameWithOwner
pullRequest(number: ${prNumber}) {
author { login }
baseRefName
baseRefOid
body
closedAt
comments(first: 100) {
nodes {
${commentFragment}
}
}
commits(first: 250) {
nodes {
commit {
oid
}
}
}
createdAt
files(first: 100) {
nodes { path }
}
headRefName
headRefOid
mergeCommit { oid }
merged
mergedAt
mergedBy { login }
number
publishedAt
reactions(first: 10) {
nodes {
${reactionFragment}
}
}
reviews(first: 10) {
nodes {
author { login }
body
comments(first: 10) {
nodes {
${commentFragment}
}
}
commit {
oid
}
createdAt
editor { login }
publishedAt
reactions(first: 10) {
nodes {
${reactionFragment}
}
}
resourcePath
submittedAt
updatedAt
userContentEdits(first: 10) {
nodes {
${userContentEditFragment}
}
}
}
}
state
title
updatedAt
userContentEdits(first: 10) {
nodes {
${userContentEditFragment}
}
}
}
}
}
`;
// make graphql query and strigify the response
const resp = await fetchQuery(query);
const respStr = JSON.stringify(resp, null, 2);
// save json file
const filepath = [
`${OUTPUT_DIR}/`,
`${REPO_NAME}-pr-${String(prNumber).padStart(4, "0")}.json`,
].join("");
console.log(`Saving ${filepath}...`);
fs.writeFileSync(filepath, respStr);
}
/**
* fetchQuery
*/
function fetchQuery(query: string, variables: Record<string, any> = {}) {
return fetch(GITHUB_GRAPHQL_API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `bearer ${GITHUB_TOKEN}`,
},
body: JSON.stringify({
query,
variables,
}),
}).then((response) => {
return response.json();
});
}
</pre
>
<h4>
Run it
<h4>
<pre class="console">$ npm run download </pre>
</h4>
</h4>
<h4>Output</h4>
<p>This produces the following JSON file</p>
<pre class="console">
$ cat /tmp/my-prs/Recoil-pr-0001.json
{
"data": {
"repository": {
"nameWithOwner": "facebookexperimental/Recoil",
"pullRequest": {
"author": {
"login": "facebook-github-bot"
},
"baseRefName": "master",
"baseRefOid": "40e870caadc159a87e81be291ff641410ab32e8f",
"body": "This is pull request was created automatically because we noticed your project was missing a Contributing file.\n\nCONTRIBUTING files explain how a developer can contribute to the project - which you should actively encourage.\n\nThis PR was crafted with love by Facebook's Open Source Team.",
"closedAt": "2020-05-13T04:12:15Z",
"comments": {
"nodes": [
{
"author": {
"login": "davidmccabe"
},
"body": "Already added this manually.",
"createdAt": "2020-05-13T04:12:15Z",
"reactions": {
"nodes": []
},
"userContentEdits": {
"nodes": []
}
}
]
},
"commits": {
"nodes": [
{
"commit": {
"oid": "96f91679540362fa96a6c92611a8ef5621447b42"
}
}
]
},
"createdAt": "2020-05-06T22:31:01Z",
"files": {
"nodes": [
{
"path": "CONTRIBUTING.md"
}
]
},
"headRefName": "automated_fixup_contributing_file_exists",
"headRefOid": "96f91679540362fa96a6c92611a8ef5621447b42",
"mergeCommit": null,
"merged": false,
"mergedAt": null,
"mergedBy": null,
"number": 1,
"publishedAt": "2020-05-06T22:31:01Z",
"reactions": {
"nodes": []
},
"reviews": {
"nodes": []
},
"state": "CLOSED",
"title": "Adding Contributing file",
"updatedAt": "2020-10-07T20:23:05Z",
"userContentEdits": {
"nodes": []
}
}
}
}
}</pre
>
</ul>
How to generate static HTML using React, TypeScript, and Node.js
2020-05-20T21:21:40-07:00https://www.saltycrane.com/blog/2020/05/how-generate-static-html-using-react-typescript-and-nodejs/<p>
<a href="https://reactjs.org/">React</a> is used to build web applications
that run JavaScript in a user's browser (<a
href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web#csr"
>client side rendering</a
>). It can also be used from a Node.js script to generate static HTML (<a
href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web#static-rendering"
>static rendering</a
>). I used this technique to
<a href="https://github.com/saltycrane/css-width-cheat-sheet">generate</a>
some
<a href="https://www.saltycrane.com/blog/2020/04/how-width-set-css/"
>CSS width experiments</a
>
and a
<a href="https://www.saltycrane.com/cheat-sheets/typescript/next.js/latest/"
>TypeScript Next.js cheat sheet</a
>. The example below shows how to use React with TypeScript and Node.js to
generate static HTML. I also made an
<a href="https://github.com/saltycrane/typescript-react-static-render-script"
>example project on github</a
>.
</p>
<h4 id="install-node">Install Node.js</h4>
<ul>
<li>
<pre>$ brew install node</pre>
</li>
</ul>
<h4 id="setup">Set up project</h4>
<ul>
<li>
Create a project directory and cd into it
<pre>
$ mkdir my-project
$ cd my-project</pre
>
</li>
<li>
Create a <code>package.json</code> file:
<pre>
{
"scripts": {
"render": "tsc && node dist/render.js"
},
"dependencies": {
"@types/node": "^14.0.4",
"@types/prettier": "^2.0.0",
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
"prettier": "^2.0.5",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"typescript": "^3.9.3"
}
}</pre
>
</li>
<li>
Install React, TypeScript, and other packages
<pre>$ npm install</pre>
</li>
<li>
Create a
<a href="https://www.typescriptlang.org/docs/handbook/tsconfig-json.html"
><code>tsconfig.json</code></a
>
file to configure TypeScript
<pre>
{
"compilerOptions": {
"baseUrl": ".",
"esModuleInterop": true,
"jsx": "react",
"lib": ["dom", "es2019"],
"outDir": "dist",
"paths": {
"*": ["src/*", "src/@types/*"]
}
},
"include": ["src/*.tsx"]
}</pre
>
</li>
</ul>
<h4 id="script">Create a script to generate a static HTML file</h4>
<ul>
<li>
Create a directory, <code>src</code> and a file <code>src/render.tsx</code>:
<pre>
import * as fs from "fs";
import prettier from "prettier";
import React from "react";
import ReactDOMServer from "react-dom/server";
render();
function render() {
let html = ReactDOMServer.renderToStaticMarkup(<HelloWorldPage />);
let htmlWDoc = "<!DOCTYPE html>" + html;
let prettyHtml = prettier.format(htmlWDoc, { parser: "html" });
let outputFile = "./output.html";
fs.writeFileSync(outputFile, prettyHtml);
console.log(`Wrote ${outputFile}`);
}
function HelloWorldPage() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<title>Hello world</title>
</head>
<body>
<h1>Hello world</h1>
</body>
</html>
);
}</pre
>
</li>
</ul>
<h4 id="run-script">Run the script and view the output</h4>
<ul>
<li>
Run the script
<pre>$ npm run render</pre>
</li>
<li>
Open the <code>output.html</code> file in the browser
<pre>$ open output.html</pre>
</li>
</ul>
How to remount a React component when a prop changes
2019-12-13T15:14:44-08:00https://www.saltycrane.com/blog/2019/12/how-remount-react-component-when-prop-changes/<p>
To remount a component when a prop changes, use the
<a href="https://reactjs.org/docs/reconciliation.html#keys"
>React <code>key</code> attribute</a
>
as described in
<a
href="https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key"
>this post on the React blog</a
>:
</p>
<blockquote>
When a key changes, React will create a new component instance rather than
update the current one.
</blockquote>
<p>
The example below shows how the <code>key</code> attribute can be used. In
<code>Parent</code>, the <code>key</code> attribute of
<code><Child></code> is set to <code>String(primaryExists)</code>. When
<code>primaryExists</code> changes in the parent component, the child
component unmounts and remounts allowing <code>useState</code> to
re-initialize its state with the intial value passed in from props
(<code>!primaryExists</code>).
<a href="https://codesandbox.io/s/remount-on-prop-change-bk58p"
>Play with it on CodeSandbox</a
>.
</p>
<pre class="javascript">
import React, { useState } from "react";
function Parent() {
// Note: in my real code, primaryExists was derived from API data,
// but I useState here to simplify the example
const [primaryExists, setPrimaryExists] = useState(true);
return (
<div>
<label>
<input
checked={primaryExists}
onChange={() => setPrimaryExists(x => !x)}
type="checkbox"
/>
Primary exists
</label>
<Child key={String(primaryExists)} primaryExists={primaryExists} />
</div>
);
}
function Child({ primaryExists }) {
const [isPrimary, setIsPrimary] = useState(!primaryExists);
return (
<div>
<label>
<input
checked={isPrimary}
onChange={() => setIsPrimary(x => !x)}
type="checkbox"
/>
Is primary
</label>
</div>
);
} </pre
>
<h4 id="additional-info">Additional information</h4>
<p>
Sebastian Markbåge, from the React core team,
<a href="https://twitter.com/sebmarkbage/status/1262937670444974081">
reinforced the usage</a
>
of keys on single items:
</p>
<blockquote>
...BUT it has given the misinterpretation that only lists need keys. You need
keys on single items too. In a master/detail, the detail should have a key.
</blockquote>
React hook to fit text in a div
2019-11-15T10:24:04-08:00https://www.saltycrane.com/blog/2019/11/react-hook-fit-text-div/<p>
This is a React hook that iteratively adjusts the font size so that text will
fit in a div.
</p>
<ul>
<li>
checks if text is overflowing by using <code>scrollHeight</code> and
<code>offsetHeight</code> from
<a href="https://stackoverflow.com/a/10017343/101911">
https://stackoverflow.com/a/10017343/101911
</a>
</li>
<li>
uses binary search; makes a maximum of 5 adjustments with a resolution of 5%
font size from 20-100%
</li>
</ul>
<p>
The code is also in a github repo:
<a href="https://github.com/saltycrane/use-fit-text">use-fit-text</a>
</p>
<pre class="javascript">
const useFitText = () => {
const MIN_FONT_SIZE = 20;
const MAX_FONT_SIZE = 100;
const RESOLUTION = 5;
const ref = useRef(null);
const [state, setState] = useState({
fontSize: MAX_FONT_SIZE,
fontSizePrev: MIN_FONT_SIZE,
fontSizeMax: MAX_FONT_SIZE,
fontSizeMin: MIN_FONT_SIZE,
});
const { fontSize, fontSizeMax, fontSizeMin, fontSizePrev } = state;
useEffect(() => {
const isDone = Math.abs(fontSize - fontSizePrev) <= RESOLUTION;
const isOverflow =
!!ref.current &&
(ref.current.scrollHeight > ref.current.offsetHeight ||
ref.current.scrollWidth > ref.current.offsetWidth);
const isAsc = fontSize > fontSizePrev;
// return if the font size has been adjusted "enough" (change within RESOLUTION)
// reduce font size by one increment if it's overflowing
if (isDone) {
if (isOverflow) {
const fontSizeNew =
fontSizePrev < fontSize
? fontSizePrev
: fontSize - (fontSizePrev - fontSize);
setState({
fontSize: fontSizeNew,
fontSizeMax,
fontSizeMin,
fontSizePrev,
});
}
return;
}
// binary search to adjust font size
let delta;
let newMax = fontSizeMax;
let newMin = fontSizeMin;
if (isOverflow) {
delta = isAsc ? fontSizePrev - fontSize : fontSizeMin - fontSize;
newMax = Math.min(fontSizeMax, fontSize);
} else {
delta = isAsc ? fontSizeMax - fontSize : fontSizePrev - fontSize;
newMin = Math.max(fontSizeMin, fontSize);
}
setState({
fontSize: fontSize + delta / 2,
fontSizeMax: newMax,
fontSizeMin: newMin,
fontSizePrev: fontSize,
});
}, [fontSize, fontSizeMax, fontSizeMin, fontSizePrev, ref]);
return { fontSize: `${fontSize}%`, ref };
};
</pre>
<h4 id="usage">Example usage</h4>
<pre class="javascript">
import React from "react";
import useFitText from "use-fit-text";
const Example = () => {
const { fontSize, ref } = useFitText();
return (
<div ref={ref} style={{ fontSize, height: 40, width: 100 }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</div>
);
}
</pre>
How to useMemo to improve the performance of a React table
2019-03-13T21:30:56-07:00https://www.saltycrane.com/blog/2019/03/how-to-usememo-improve-performance-react-table/<p>
These are notes on how I improved the React rendering time of a large table with
<a href="https://reactjs.org/docs/hooks-reference.html#usememo">
<code>useMemo</code></a>, the
<a href="https://github.com/facebook/react-devtools">
React Devtools</a>, and consideration of referential equality of my data.
</p>
<h4 id="summary">Summary of steps</h4>
<ul>
<li>Profile using the React Devtools Profiler to find components that are rendering excessively</li>
<li>Add
<a href="https://reactjs.org/docs/react-api.html#reactmemo"><code>memo</code></a>,
<code>useMemo</code>, or
<a href="https://reactjs.org/docs/react-api.html#reactpurecomponent"><code>PureComponent</code></a>
to prevent the excessive rendering
</li>
<li>If using <code>memo</code> or <code>PureComponent</code>, ensure the props passed in are referentially equal. Something like <a href="https://www.npmjs.com/package/use-why-did-you-update"><code>use-why-did-you-update</code></a> can help find unexpected inequalities. If using <code>useMemo</code>, ensure the dependencies in the dependency array are referentially equal. Hooks like <code>useMemo</code> and <code>useCallback</code> can help preserve referential equality of props or dependencies. If using Redux,
<a href="https://github.com/reduxjs/reselect"><code>reselect</code></a>
memoizes selectors to prevent excessive referential inequalities. And immutable libraries like
<a href="https://github.com/immerjs/immer"><code>immer</code></a>
help preserve referential equality by preserving references to data if the values do not change.
</li>
</ul>
<h4 id="problem">Problem</h4>
<p>
I had a table of 100 rows of select inputs. Changing
a select input had a noticeable lag.
</p>
<img class="img-responsive" alt="react table screenshot" src="/site_media/image/blog/react-ui.png" />
<h4 id="profiler">React Profiler</h4>
<p>
I profiled the table with the Profiler in React Devtools and found that all the rows were
re-rendering even though only one of them changed. The screenshot below shows rendering of
my <code>Table</code> component took 239ms. All the colored bars beneath the <code>Table</code>
means each of the 100 rows are rendering even though only one of them changed.
For more information, see
<a href="https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html">
this article on how to use the React Profiler</a>.
</p>
<img class="img-responsive" alt="react table screenshot" src="/site_media/image/blog/react-profile-slow.png" />
<h4 id="row">Row component</h4>
<p>
The table was built using
<a href="https://reactjs.org/docs/hooks-intro.html">React hooks</a> and I sprinkled
<code>useMemo</code> liberally in my code. Most of my data was memoized, but
React was still re-rendering. Here is my row component:
</p>
<pre class="javascript">
const MappingRow = ({ id }) => {
// ...
const mapping = useMapping(state, id);
const enabled = useEnabledFields(state, id);
const { makeOptions, modelFamilyOptions, modelParentOptions, segmentOptions } = useMappingRowApis(id);
const handleChange = field => selected => {
const value = selected && selected.value;
if (value === mapping[field]) {
return;
}
const update = { [field]: value };
dispatch({
type: "save_mapping",
promise: saveMapping(id, update),
id,
timeSaved: Date.now(),
update,
});
};
return (
<tr>
<Cell>{mapping.source}</Cell>
<SelectCell
isDisabled={!enabled.mappableMakeName}
onChange={handleChange("makeCode")}
options={makeOptions}
value={mapping.makeCode}
/>
<SelectCell
isDisabled={!enabled.mappableModelParent}
onChange={handleChange("modelParentId")}
options={modelParentOptions}
value={mapping.modelParentId}
/>
<SelectCell
isDisabled={!enabled.mappableModelFamilyName}
onChange={handleChange("modelFamilyName")}
options={modelFamilyOptions}
value={mapping.modelFamilyName}
/>
<SelectCell
isDisabled={!enabled.mappableSegmentName}
onChange={handleChange("segmentCode")}
options={segmentOptions}
value={mapping.segmentCode}
/>
</tr>
);
};</pre>
<h4 id="memo"><code>memo</code> HOC</h4>
<p>
Even though the data provided by my custom hooks was memoized, I realized I still needed to apply React's
<a href="https://reactjs.org/docs/react-api.html#reactmemo">
<code>memo</code></a> higher order component (HOC) to prevent re-rendering.
I extracted out a new <code>MemoizedRow</code> component, so that I could wrap it with React's <code>memo</code> HOC.
(Note: if this seems undesirable to you, see the
<a href="#use-memo">end of this post</a>.)
</p>
<pre class="javascript">
const MappingRow = ({ id }) => {
// ...
const mapping = useMapping(state, id);
const enabled = useEnabledFields(state, id);
const { makeOptions, modelFamilyOptions, modelParentOptions, segmentOptions } = useMappingRowApis(id);
const handleChange = field => selected => {
// ...
};
return (
<MemoizedRow
enabled={enabled}
handleChange={handleChange}
makeOptions={makeOptions}
mapping={mapping}
modelFamilyOptions={modelFamilyOptions}
modelParentOptions={modelParentOptions}
segmentOptions={segmentOptions}
/>
);
};
const MemoizedRow = memo(props => {
const {
enabled,
handleChange,
makeOptions,
mapping,
modelFamilyOptions,
modelParentOptions,
segmentOptions,
sourceConfig,
} = props;
return (
<tr>
<Cell>{mapping.source}</Cell>
<SelectCell
isDisabled={!enabled.mappableMakeName}
onChange={handleChange("makeCode")}
options={makeOptions}
value={mapping.makeCode}
/>
<SelectCell
isDisabled={!enabled.mappableModelParent}
onChange={handleChange("modelParentId")}
options={modelParentOptions}
value={mapping.modelParentId}
/>
<SelectCell
isDisabled={!enabled.mappableModelFamilyName}
onChange={handleChange("modelFamilyName")}
options={modelFamilyOptions}
value={mapping.modelFamilyName}
/>
<SelectCell
isDisabled={!enabled.mappableSegmentName}
onChange={handleChange("segmentCode")}
options={segmentOptions}
value={mapping.segmentCode}
/>
</tr>
);
});</pre>
<h4 id="referential-equality">Referential equality or shallow equality</h4>
<p>
I applied the <code>memo</code> HOC, but profiling showed no change in performance.
I thought I should
<a href="https://usehooks.com/useWhyDidYouUpdate/"><code>useWhyDidYouUpdate</code></a>.
This revealed some of my props were not equal when I expected them to be.
One of them was my <code>handleChange</code>
callback function. This function is created every render. The reference to the function from
one render does not compare as equal to the reference to the function in another render.
Wrapping this function with
<a href="https://reactjs.org/docs/hooks-reference.html#usecallback">
<code>useCallback</code></a> memoized the function so it will
compare equally unless one of the dependencies change
(<code>mapping</code> or <code>id</code>).
</p>
<pre class="javascript">
const MappingRow = ({ id }) => {
//...
const handleChange = useCallback(
field => selected => {
const value = selected && selected.value;
if (value === mapping[field]) {
return;
}
const update = { [field]: value };
dispatch({
type: "save_mapping",
promise: saveMapping(id, update),
id,
timeSaved: Date.now(),
update,
});
},
[mapping, id],
);
return (
<MemoizedRow
enabled={enabled}
handleChange={handleChange}
makeOptions={makeOptions}
mapping={mapping}
modelFamilyOptions={modelFamilyOptions}
modelParentOptions={modelParentOptions}
segmentOptions={segmentOptions}
/>
);
};</pre>
<p>
Another problem was my <code>mapping</code> data object was changing for every row
even though I only actually changed one of the rows. I was using the
<a href="https://github.com/mweststrate/immer">Immer</a> library to create immutable
data structures. I had learned that using immutable data structures allows updating
a slice of data in an object without changing the reference to a sibling slice of data
so that it would compare equally when used with the <code>memo</code> HOC or <code>PureComponent</code>.
I had thought my data was properly isolated and memoized, however there was one piece of
my state that was breaking the memoization. Here is my code to return a single mapping data object
for a row:
</p>
<pre class="javascript">
export const useMapping = (state, id) => {
const {
optimisticById,
optimisticIds,
readonlyById,
writableById,
} = state.mappings;
const optimisticMapping = optimisticById[id];
const readonlyMapping = readonlyById[id];
const writableMapping = writableById[id];
return useMemo(() => {
const mapping = { ...readonlyMapping, ...writableMapping };
return optimisticIds.includes(id)
? { ...mapping, ...optimisticMapping }
: mapping;
}, [id, optimisticIds, optimisticMapping, readonlyMapping, writableMapping]);
};</pre>
<p>
The <code>optimisticIds</code> state was used to store a list of ids of mapping items
that had been updated by the user, but had not yet been saved to the database.
This list changed whenever a row was edited, but it was used in creating the mapping
data for every row in the table. The <code>optimisticIds</code> is in the <code>useMemo</code>
dependency array, so when it changes, the mapping data is re-calculated and a new value is returned. The important part is not that running the code in this function is expensive. The important
part is that the function returns a newly created object literal. Like the <code>handleChange</code>
function created in the component above, object literals created at different times
do not compare equally even if the contents of the object are the same. e.g. The following is not true in JavaScript: <code>{} === {}</code>. I realized I did not need the <code>optimisticIds</code> state,
so I removed it. This left a memoized function that only recalculated when data for its
corresponding row in the table changed:
</p>
<pre class="javascript">
export const useMapping = (state, id) => {
const { optimisticById, readonlyById, writableById } = state.mappings;
const optimisticMapping = optimisticById[id];
const readonlyMapping = readonlyById[id];
const writableMapping = writableById[id];
return useMemo(() => {
const mapping = { ...readonlyMapping, ...writableMapping };
return optimisticMapping ? { ...mapping, ...optimisticMapping } : mapping;
}, [optimisticMapping, readonlyMapping, writableMapping]);
};</pre>
<h4 id="20x">20X improvement</h4>
<p>
After fixing these referential inequalities, the <code>memo</code> HOC
eliminated the re-rendering of all but the edited row.
The React profiler now showed the table rendered in 10ms, a 20X improvement.
</p>
<img class="img-responsive" alt="react table screenshot" src="/site_media/image/blog/react-profile-fast.png" />
<h4 id="use-memo">Refactoring to <code>useMemo</code></h4>
<p>
To use the <code>memo</code> HOC, I had to extract out a separate component
for the sole purpose of applying the <code>memo</code> HOC. I started to
convert the HOC to a render prop so I could use it inline. Then I thought,
aren't hooks supposed to replace most HOCs and render props? Someone should
make a <code>useMemo</code> hook to do what the <code>memo</code> HOC does.
Wait there is a <code>useMemo</code> hook already... I wonder if...
</p>
<pre class="javascript">
const MappingRow = ({ id }) => {
const mapping = useMapping(id);
const enabled = useEnabledFields(id);
const { makeOptions, modelFamilyOptions, modelParentOptions, segmentOptions } = useMappingRowApis(id);
const handleChange = useCallback(
field => selected => {
const value = selected && selected.value;
if (value === mapping[field]) {
return;
}
const update: MappingUpdate = { [field]: value };
dispatch({
type: "save_mapping",
promise: saveMapping(id, update),
id,
timeSaved: Date.now(),
update,
});
},
[dispatch, mapping, id],
);
return useMemo(
() => (
<tr>
<Cell>{mapping.source}</Cell>
<SelectCell
isDisabled={!enabled.mappableMakeName}
onChange={handleChange("makeCode")}
options={makeOptions}
value={mapping.makeCode}
/>
<SelectCell
isDisabled={!enabled.mappableModelParent}
onChange={handleChange("modelParentId")}
options={modelParentOptions}
value={mapping.modelParentId}
/>
<SelectCell
isDisabled={!enabled.mappableModelFamilyName}
onChange={handleChange("modelFamilyName")}
options={modelFamilyOptions}
value={mapping.modelFamilyName}
/>
<SelectCell
isDisabled={!enabled.mappableSegmentName}
onChange={handleChange("segmentCode")}
options={segmentOptions}
value={mapping.segmentCode}
/>
</tr>
),
[
enabled,
handleChange,
mapping,
makeOptions,
modelParentOptions,
modelFamilyOptions,
segmentOptions,
],
);
};
</pre>
<p>
Yes applying <code>useMemo</code> to the returned JSX element tree had the
same effect as applying the <code>memo</code> HOC without the intrusive
component refactor. I thought that was pretty cool.
Dan Abramov tweeted about wrapping React elements with <code>useMemo</code> also:
<blockquote class="twitter-tweet" data-conversation="none" data-lang="en"><p lang="en" dir="ltr">Interestingly, it also lets you skip re-rendering without changing the child export. Like a one-off memo(). <a href="https://t.co/8HhpE1qE8z">pic.twitter.com/8HhpE1qE8z</a></p>— Dan Abramov (@dan_abramov) <a href="https://twitter.com/dan_abramov/status/1055690372951629824?ref_src=twsrc%5Etfw">October 26, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</p>
Caching a filtered list of results w/ Redux, React Router, and redux-promise-memo
2018-06-29T13:33:21-07:00https://www.saltycrane.com/blog/2018/06/caching-filtered-list-results-w-redux-react-router-and-redux-promise-memo/<style>
.postsum h5 {
margin-bottom: 6px;
margin-top: 8px;
}
</style>
<p>
This post shows how to cache API data for a
<a href="https://reactjs.org/">React</a> +
<a href="https://redux.js.org/">Redux</a>
application using ideas from my library, <a href="https://github.com/saltycrane/redux-promise-memo">redux-promise-memo</a>.
The
<a href="https://filtered-vehicles1.saltycrane.com/vehicles">example app</a>
displays a filtered list of vehicles, a sidebar with make and model filters, and a detail page for each vehicle.
Caching is used to prevent re-fetching API data when navigating between the detail pages and the list page.
</p>
<p>
In addition to React and Redux, this example uses
<a href="https://reacttraining.com/react-router/">React Router</a>
and <a href="https://github.com/pburtchaell/redux-promise-middleware">redux-promise-middleware</a>, though alternatives like
<a href="https://github.com/lelandrichardson/redux-pack">redux-pack</a> or
<a href="https://github.com/TrueCar/gluestick">Gluestick</a>'s promise middleware
can be used also.
</p>
<p>
The example is broken into 3 sections:
<a href="#basic-features">1. Basic features (no caching)</a>,
<a href="#caching-manual">2. Caching (manual setup)</a>, and
<a href="#redux-memo">3. Caching with redux-promise-memo</a>.
</p>
<h5>Basic features</h5>
<ul>
<li>filtering vehicles by make and model is done by the backend vehicles API</li>
<li>when a make is selected, the models API populates the models filter for the selected make</li>
<li>filter parameters (make and model) are stored in the URL query string to support deep linking to a page of filtered results and to support browser "back" and "forward" navigation</li>
<li>each vehicle detail page also has a unique route using the vehicle id in the route</li>
</ul>
<h5>Highlighted feature - caching</h5>
<ul>
<li>API responses are not re-fetched when moving back and forward between pages</li>
</ul>
<h4 id="basic-features">Code for basic features (no caching)</h4>
<p>
The full example code is
<a href="https://github.com/saltycrane/redux-promise-memo/tree/master/examples/filtered-vehicles/01-demonstrate-problem/src">here on github</a>.
A demo is <a href="https://filtered-vehicles1.saltycrane.com/vehicles">deployed here</a>.
</p>
<h5 id="VehicleFilters-basic">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/01-demonstrate-problem/src/VehiclesFilters.js">
<code>VehicleFilters.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">VehiclesFilters</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">React.Component</span> <span class="rainbow-delimiters-depth-1"><span class="custom">{</span></span>
<span class="web-mode-function-name">componentDidMount</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">_fetchData</span><span class="rainbow-delimiters-depth-3">()</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">componentDidUpdate</span><span class="rainbow-delimiters-depth-2">(</span>prevProps<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">if</span> <span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-constant">this</span>.props.query !== prevProps.query<span class="rainbow-delimiters-depth-3">)</span> <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">_fetchData</span><span class="rainbow-delimiters-depth-4">()</span>;
<span class="rainbow-delimiters-depth-3">}</span>
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">_fetchData</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">let</span> <span class="rainbow-delimiters-depth-3">{</span> dispatch, query <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-3">(</span>actions.<span class="web-mode-function-call">fetchModels</span><span class="rainbow-delimiters-depth-4">(</span>query.make<span class="rainbow-delimiters-depth-4">)</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2"><span class="custom-1">{</span></span>
<span class="web-mode-keyword">let</span> <span class="rainbow-delimiters-depth-3">{</span> changeQuery, models, query <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3"><span class="custom-2">(</span></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Container</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Select</span>
<span class="web-mode-html-attr-name">label</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"Make"</span>
<span class="web-mode-html-attr-name">onChange</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>e =>
<span class="web-mode-function-call">changeQuery</span><span class="rainbow-delimiters-depth-5">(</span><span class="rainbow-delimiters-depth-6">{</span> <span class="web-mode-variable-name">make</span>: e.currentTarget.<span class="web-mode-variable-name">value</span>, <span class="web-mode-variable-name">model</span>: <span class="web-mode-javascript-string">""</span> <span class="rainbow-delimiters-depth-6">}</span><span class="rainbow-delimiters-depth-5">)</span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">options</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="rainbow-delimiters-depth-5">[</span><span class="web-mode-javascript-string">"All makes"</span>, <span class="web-mode-javascript-string">"Acura"</span>, <span class="web-mode-javascript-string">"BMW"</span>, <span class="web-mode-javascript-string">"Cadillac"</span>, <span class="web-mode-javascript-string">"..."</span><span class="rainbow-delimiters-depth-5">]</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">value</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>query.make || <span class="web-mode-javascript-string">""</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>query.make && <span class="rainbow-delimiters-depth-5">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Select</span>
<span class="web-mode-html-attr-name">label</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"Model"</span>
<span class="web-mode-html-attr-name">onChange</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-6">{</span></span>e => <span class="web-mode-function-call">changeQuery</span><span class="rainbow-delimiters-depth-7">(</span><span class="rainbow-delimiters-depth-8">{</span> <span class="web-mode-variable-name">model</span>: e.currentTarget.<span class="web-mode-variable-name">value</span> <span class="rainbow-delimiters-depth-8">}</span><span class="rainbow-delimiters-depth-7">)</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-6">}</span></span>
<span class="web-mode-html-attr-name">options</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-6">{</span></span><span class="rainbow-delimiters-depth-7">[</span><span class="web-mode-javascript-string">"All models"</span>, ...models<span class="rainbow-delimiters-depth-7">]</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-6">}</span></span>
<span class="web-mode-html-attr-name">value</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-6">{</span></span>query.model || <span class="web-mode-javascript-string">""</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-6">}</span></span>
<span class="web-mode-html-tag-bracket">/></span>
<span class="rainbow-delimiters-depth-5">)</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Container</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-3"><span class="custom-2">)</span></span>;
<span class="rainbow-delimiters-depth-2"><span class="custom-1">}</span></span>
<span class="rainbow-delimiters-depth-1"><span class="custom">}</span></span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">default</span> <span class="web-mode-function-call">compose</span><span class="rainbow-delimiters-depth-1">(</span>
withVehiclesRouter,
<span class="web-mode-function-call">connect</span><span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-function-name">state</span> => <span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span> <span class="web-mode-variable-name">models</span>: state.<span class="web-mode-variable-name">models</span> <span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span><span class="rainbow-delimiters-depth-2">)</span>
<span class="rainbow-delimiters-depth-1">)(</span>VehiclesFilters<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<p>The <code>VehicleFilters</code> component has <code><select></code> inputs for the "Make" and "Model" filters.</p>
<ol>
<li>when a user changes the make to "BMW", the <code>changeQuery</code> function adds the <code>?make=BMW</code> query string to the URL</li>
<li>when the query string is updated, <code>componentDidUpdate</code> calls <code>fetchModels</code> which calls the models API</li>
<li>when the API responds, the models list is stored in Redux at <code>state.models</code></li>
<li>when the Redux state changes, the "Model" <code><select></code> is updated with the new list of models</li>
</ol>
<p>Notes:</p>
<ul>
<li><p><code>withVehiclesRouter</code> is a higher-order component that adds the following props to <code>VehicleFilters</code>:</p>
<ul>
<li><code>query</code> - the parsed query string</li>
<li><code>changeQuery</code> - a function used to update the query string</li>
</ul>
<p>See the implementation <a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/01-demonstrate-problem/src/withVehiclesRouter.js">here</a></p></li>
<li><p>the route is the single source of truth for the make and model parameters. They are stored only in the route and <strong>not</strong> in Redux.</p></li>
</ul>
<h5 id="VehiclesList-basic">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/01-demonstrate-problem/src/VehiclesList.js">
<code>VehiclesList.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">VehiclesList</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">React.Component</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-function-name">componentDidMount</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">_fetchData</span><span class="rainbow-delimiters-depth-3">()</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">componentDidUpdate</span><span class="rainbow-delimiters-depth-2">(</span>prevProps<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">if</span> <span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-constant">this</span>.props.query !== prevProps.query<span class="rainbow-delimiters-depth-3">)</span> <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">_fetchData</span><span class="rainbow-delimiters-depth-4">()</span>;
<span class="rainbow-delimiters-depth-3">}</span>
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">_fetchData</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">let</span> <span class="rainbow-delimiters-depth-3">{</span> dispatch, query <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-3">(</span>actions.<span class="web-mode-function-call">fetchVehicles</span><span class="rainbow-delimiters-depth-4">(</span>query<span class="rainbow-delimiters-depth-4">)</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">let</span> <span class="rainbow-delimiters-depth-3">{</span> isLoading, vehicles <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Container</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>isLoading ? <span class="rainbow-delimiters-depth-5">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Spinner</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="rainbow-delimiters-depth-5">)</span> : <span class="rainbow-delimiters-depth-5">(</span>
vehicles.<span class="web-mode-function-call">map</span><span class="rainbow-delimiters-depth-6">(</span><span class="web-mode-function-name">vehicle</span> => <span class="rainbow-delimiters-depth-7">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Link</span> <span class="web-mode-html-attr-name">key</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-8">{</span></span>vehicle.id<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-8">}</span></span> <span class="web-mode-html-attr-name">to</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-8">{</span></span><span class="web-mode-javascript-string">`/vehicles/</span><span class="web-mode-variable-name">${vehicle.id}</span><span class="web-mode-javascript-string">`</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-8">}</span></span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">VehicleCard</span> <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-8">{</span></span><span class="web-mode-block-delimiter">...</span>vehicle<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-8">}</span></span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Link</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-7">)</span><span class="rainbow-delimiters-depth-6">)</span>
<span class="rainbow-delimiters-depth-5">)</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Container</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">default</span> <span class="web-mode-function-call">compose</span><span class="rainbow-delimiters-depth-1">(</span>
withVehiclesRouter,
<span class="web-mode-function-call">connect</span><span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-function-name">state</span> => <span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-variable-name">isLoading</span>: state.isLoading,
<span class="web-mode-variable-name">vehicles</span>: state.vehicles
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span><span class="rainbow-delimiters-depth-2">)</span>
<span class="rainbow-delimiters-depth-1">)(</span>VehiclesList<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<p>The <code>VehiclesList</code> component gets data the same way the "Model" <code><select></code> input does.</p>
<ol>
<li>the previous <code>VehicleFilters</code> component updates the route query string with a make or model</li>
<li>when the route query string is updated, this component's <code>componentDidUpdate</code> calls <code>fetchVehicles</code> which calls the vehicles API</li>
<li>when the API responds, the vehicle list is stored in Redux at <code>state.vehicles</code></li>
<li>when the Redux state changes, this component is updated with the new list of vehicles.</li>
</ol>
<p>Each vehicle card is wrapped with a <code>react-router</code> <code><Link></code>. Clicking the vehicle card navigates to a new route, <code>/vehicles/{vehicleId}</code>.</p>
<h5 id="VehicleDetail-basic">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/01-demonstrate-problem/src/VehicleDetail.js">
<code>VehicleDetail.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">VehicleDetail</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">React.Component</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-function-name">componentDidMount</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">let</span> <span class="rainbow-delimiters-depth-3">{</span> dispatch, vehicleId <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-3">(</span>actions.<span class="web-mode-function-call">fetchVehicle</span><span class="rainbow-delimiters-depth-4">(</span>vehicleId<span class="rainbow-delimiters-depth-4">)</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">let</span> <span class="rainbow-delimiters-depth-3">{</span> isLoading, vehicle <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">return</span> isLoading ? <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Spinner</span> <span class="web-mode-html-tag-bracket">/></span> : <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">VehicleCard</span> <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span><span class="web-mode-block-delimiter">...</span>vehicle<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span> <span class="web-mode-html-tag-bracket">/></span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">default</span> <span class="web-mode-function-call">compose</span><span class="rainbow-delimiters-depth-1">(</span>
withVehiclesRouter,
<span class="web-mode-function-call">connect</span><span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-function-name">state</span> => <span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-variable-name">isLoading</span>: state.isLoading,
<span class="web-mode-variable-name">vehicle</span>: state.vehicle
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span><span class="rainbow-delimiters-depth-2">)</span>
<span class="rainbow-delimiters-depth-1">)(</span>VehicleDetail<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<p><code>VehicleDetail</code> gets data in the same way as the "Model" filter and <code>VehiclesList</code>. One difference is that it doesn't need to use <code>componentDidUpdate</code> because the API input parameter (<code>vehicleId</code>) never changes.</p>
<ol>
<li>to display a vehicle detail page, a vehicle <code><Link></code> is clicked in the previous <code>VehiclesList</code> component which changes the route to <code>/vehicles/{vehicleId}</code></li>
<li>when the route changes, this component is rendered and passed the <code>vehicleId</code> prop.</li>
<li>when this component is rendered, <code>componentDidMount</code> calls <code>fetchVehicle</code> which calls the vehicle detail API</li>
</ol>
<p>The <code>withVehiclesRouter</code> higher-order component takes <code>match.params.vehicleId</code> from <code>react-router</code> and passes it to <code>VehicleDetail</code> as <code>vehicleId</code>.</p>
<h5 id="App-basic">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/01-demonstrate-problem/src/index.js">
<code>App.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">store</span> = <span class="web-mode-function-call">createStore</span><span class="rainbow-delimiters-depth-1">(</span>reducer, <span class="web-mode-function-call">applyMiddleware</span><span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-function-call">promiseMiddleware</span><span class="rainbow-delimiters-depth-3">()</span><span class="rainbow-delimiters-depth-2">)</span><span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">VehiclesPage</span> = <span class="rainbow-delimiters-depth-1">()</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">React</span>.Fragment<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">VehiclesFilters</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">VehiclesList</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">React</span>.Fragment<span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">App</span> = <span class="rainbow-delimiters-depth-1">()</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Provider</span> <span class="web-mode-html-attr-name">store</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">{</span></span>store<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">}</span></span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">BrowserRouter</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Switch</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">{</span></span>VehicleDetail<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">}</span></span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/vehicles/:vehicleId"</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">{</span></span>VehiclesPage<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">}</span></span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/vehicles"</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Switch</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">BrowserRouter</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Provider</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<p>This shows <code>react-router</code> route configuration for the app and also the addition of <code>redux-promise-middleware</code>.</p>
<h5 id="actions-basic">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/01-demonstrate-problem/src/actions.js">
<code>actions.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">fetchModels</span> = <span class="web-mode-variable-name">make</span> => <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: <span class="web-mode-javascript-string">"FETCH_MODELS"</span>,
<span class="web-mode-variable-name">payload</span>: <span class="web-mode-function-call">fakeModelsApi</span><span class="rainbow-delimiters-depth-3">(</span>make<span class="rainbow-delimiters-depth-3">)</span>
<span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">fetchVehicle</span> = <span class="web-mode-variable-name">vehicleId</span> => <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: <span class="web-mode-javascript-string">"FETCH_VEHICLE"</span>,
<span class="web-mode-variable-name">payload</span>: <span class="web-mode-function-call">fakeVehicleApi</span><span class="rainbow-delimiters-depth-3">(</span>vehicleId<span class="rainbow-delimiters-depth-3">)</span>
<span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">fetchVehicles</span> = <span class="web-mode-variable-name">params</span> => <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: <span class="web-mode-javascript-string">"FETCH_VEHICLES"</span>,
<span class="web-mode-variable-name">payload</span>: <span class="web-mode-function-call">fakeVehiclesApi</span><span class="rainbow-delimiters-depth-3">(</span>params<span class="rainbow-delimiters-depth-3">)</span>
<span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<h5 id="reducers-basic">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/01-demonstrate-problem/src/reducers.js">
<code>reducers.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">let</span> <span class="web-mode-function-name">isLoading</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="web-mode-variable-name">state</span> = <span class="web-mode-constant">false</span>, <span class="web-mode-variable-name">action</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">switch</span> <span class="rainbow-delimiters-depth-2">(</span>action.type<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-javascript-string">"FETCH_VEHICLE_PENDING"</span>:
<span class="web-mode-keyword">case</span> <span class="web-mode-javascript-string">"FETCH_VEHICLES_PENDING"</span>:
<span class="web-mode-keyword">return</span> <span class="web-mode-constant">true</span>;
<span class="web-mode-keyword">case</span> <span class="web-mode-javascript-string">"FETCH_VEHICLE_FULFILLED"</span>:
<span class="web-mode-keyword">case</span> <span class="web-mode-javascript-string">"FETCH_VEHICLES_FULFILLED"</span>:
<span class="web-mode-keyword">return</span> <span class="web-mode-constant">false</span>;
<span class="web-mode-keyword">default</span>:
<span class="web-mode-keyword">return</span> state;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-function-name">models</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="web-mode-variable-name">state</span> = <span class="rainbow-delimiters-depth-2">[]</span>, <span class="web-mode-variable-name">action</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">switch</span> <span class="rainbow-delimiters-depth-2">(</span>action.type<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-javascript-string">"FETCH_MODELS_FULFILLED"</span>:
<span class="web-mode-keyword">return</span> action.payload;
<span class="web-mode-keyword">default</span>:
<span class="web-mode-keyword">return</span> state;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-function-name">vehicle</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="web-mode-variable-name">state</span> = <span class="rainbow-delimiters-depth-2">[]</span>, <span class="web-mode-variable-name">action</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">switch</span> <span class="rainbow-delimiters-depth-2">(</span>action.type<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-javascript-string">"FETCH_VEHICLE_FULFILLED"</span>:
<span class="web-mode-keyword">return</span> action.payload;
<span class="web-mode-keyword">default</span>:
<span class="web-mode-keyword">return</span> state;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-function-name">vehicles</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="web-mode-variable-name">state</span> = <span class="rainbow-delimiters-depth-2">[]</span>, <span class="web-mode-variable-name">action</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">switch</span> <span class="rainbow-delimiters-depth-2">(</span>action.type<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-javascript-string">"FETCH_VEHICLES_FULFILLED"</span>:
<span class="web-mode-keyword">return</span> action.payload;
<span class="web-mode-keyword">default</span>:
<span class="web-mode-keyword">return</span> state;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">default</span> <span class="web-mode-function-call">combineReducers</span><span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span>
isLoading,
models,
vehicle,
vehicles
<span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<p>These are the actions and reducers that are using <code>redux-promise-middleware</code>.</p>
<ul>
<li><code>fakeModelsApi</code>, <code>fakeVehicleApi</code>, and <code>fakeVehiclesApi</code> are meant to mimick a HTTP client like <code>fetch</code> or <code>axios</code>. They return a promise that resolves with some canned data after a 1 second delay.</li>
<li>the <code>models</code>, <code>vehicle</code>, and <code>vehicles</code> reducers store the API responses in the Redux state</li>
</ul>
<h4 id="caching-manual">Caching (manual setup)</h4>
<p>In the above setup, API calls are made unecessarily when navigating back and forth between vehicle details pages and the main vehicles list. The goal is to eliminate the uncessary calls.</p>
<p>The vehicle data is already "cached" in Redux. But the app needs to be smarter about when to fetch new data. In some apps, a component can check if API data exisits in Redux and skip the API call if it is present. In this case, however, doing this would not allow updating the results when the make or model filters change.</p>
<p>The approach I took in <code>redux-promise-memo</code> was to fetch only when API input parameters changed:</p>
<ul>
<li>the API input parameters (e.g. make, model) are stored in Redux</li>
<li>when deciding whether to make a new API call, the current API input paramters are tested to see if they match the parameters previously stored in Redux</li>
<li>if they match, the API call is skipped, and the data already stored in Redux is used</li>
</ul>
<p>Below are changes that can be made to implement this idea <strong>without</strong> using the library. The solution with the <code>redux-promise-memo</code> library is shown at the end.</p>
<p>
The full example code is <a href="https://github.com/saltycrane/redux-promise-memo/tree/master/examples/filtered-vehicles/02-manual-solution/src">here on github</a>.
A demo is <a href="https://filtered-vehicles2.saltycrane.com/vehicles">deployed here</a>.
</p>
<h5 id="actions-manual">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/02-manual-solution/src/actions.js">
<code>actions.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">fetchVehicles</span> = <span class="web-mode-variable-name">params</span> => <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: <span class="web-mode-javascript-string">"FETCH_VEHICLES"</span>,
<span class="web-mode-variable-name">payload</span>: <span class="web-mode-function-call">fakeVehiclesApi</span><span class="rainbow-delimiters-depth-3">(</span>params<span class="rainbow-delimiters-depth-3">)</span>,
<span class="web-mode-variable-name">meta</span>: <span class="rainbow-delimiters-depth-3">{</span> params <span class="rainbow-delimiters-depth-3">}</span> <span class="web-mode-javascript-comment">// <= NEW: add the API params to the action</span>
<span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<h5 id="reducers-manual">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/02-manual-solution/src/reducers.js">
<code>reducers.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-javascript-comment">// NEW: add this vehiclesCacheParams reducer</span>
<span class="web-mode-keyword">let</span> <span class="web-mode-function-name">vehiclesCacheParams</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="web-mode-variable-name">state</span> = <span class="web-mode-constant">null</span>, <span class="web-mode-variable-name">action</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">switch</span> <span class="rainbow-delimiters-depth-2">(</span>action.type<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-javascript-string">"FETCH_VEHICLES_FULFILLED"</span>:
<span class="web-mode-keyword">return</span> action.meta.params;
<span class="web-mode-keyword">default</span>:
<span class="web-mode-keyword">return</span> state;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>;
</pre>
<p>The actions and reducers are updated to store the API parameters.</p>
<ul>
<li>in the <code>fetchVehicles</code> action creator, the API params is added to the action</li>
<li>a new reducer, <code>vehiclesCacheParams</code>, is added which stores those params in Redux when the API succeeds</li>
</ul>
<h5 id="VehiclesList-manual">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/02-manual-solution/src/VehiclesList.js">
<code>VehiclesList.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">VehiclesList</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">React.Component</span> <span class="rainbow-delimiters-depth-1"><span class="custom-1">{</span></span>
<span class="web-mode-function-name">_fetchData</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2"><span class="custom">{</span></span>
<span class="web-mode-keyword">let</span> <span class="rainbow-delimiters-depth-3">{</span> cacheParams, dispatch, query <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-javascript-comment">// NEW: add this "if" statement to check if API params have changed</span>
<span class="web-mode-keyword">if</span> <span class="rainbow-delimiters-depth-3">(</span>JSON.<span class="web-mode-function-call">stringify</span><span class="rainbow-delimiters-depth-4">(</span>query<span class="rainbow-delimiters-depth-4">)</span> !== JSON.<span class="web-mode-function-call">stringify</span><span class="rainbow-delimiters-depth-4">(</span>cacheParams<span class="rainbow-delimiters-depth-4">)</span><span class="rainbow-delimiters-depth-3">)</span> <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-4">(</span>actions.<span class="web-mode-function-call">fetchVehicles</span><span class="rainbow-delimiters-depth-5">(</span>query<span class="rainbow-delimiters-depth-5">)</span><span class="rainbow-delimiters-depth-4">)</span>;
<span class="rainbow-delimiters-depth-3">}</span>
<span class="rainbow-delimiters-depth-2"><span class="custom">}</span></span>
<span class="web-mode-javascript-comment">// ...the rest is the same as before</span>
<span class="rainbow-delimiters-depth-1"><span class="custom-1">}</span></span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">default</span> <span class="web-mode-function-call">compose</span><span class="rainbow-delimiters-depth-1">(</span>
withVehiclesRouter,
<span class="web-mode-function-call">connect</span><span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-function-name">state</span> => <span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-variable-name">cacheParams</span>: state.vehiclesCacheParams, <span class="web-mode-javascript-comment">// <= NEW line here</span>
<span class="web-mode-variable-name">isLoading</span>: state.isLoading,
<span class="web-mode-variable-name">vehicles</span>: state.vehicles
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span><span class="rainbow-delimiters-depth-2">)</span>
<span class="rainbow-delimiters-depth-1">)(</span>VehiclesList<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<ul>
<li>in <code>VehiclesList</code>, the <code>vehicleCacheParams</code> state is added to the <code>connect</code> call</li>
<li>then an "if" condition is added around the <code>fetchVehicles</code> action dispatch. <code>JSON.stringify</code> is used to compare arguments that are objects or arrays.</li>
</ul>
<h4 id="redux-memo">Caching using <code>redux-promise-memo</code></h4>
<p>To avoid adding this boilerplate for every API, I abstracted the above idea into the <code>redux-promise-memo</code> library.</p>
<ul>
<li>it uses a reducer to store the API input parameters like the manual solution</li>
<li>it provides a <code>memoize</code> function to wrap promise-based action creators (like those created with <code>redux-promise-middleware</code> above)</li>
<li>the <code>memoize</code> wrapper checks if the API input arguments have changed. If the input arguments match what is stored in Redux, the API call is skipped.</li>
<li>it also stores the "loading" and "success" state of the API. It skips the API call if the previous API call is loading or sucessful. But it re-fetches if there was an error.</li>
<li>it has an option to support multiple caches per API. If data is stored in a different places in Redux per set of input arguments, this option can be used.</li>
<li>it provides support for "invalidating" the cache using any Redux actions.</li>
<li>it provides support for other libraries besides <code>redux-promise-middleware</code>. Custom matchers for other libraries can be written. Examples of matchers are <a href="https://github.com/saltycrane/redux-promise-memo/blob/master/src/config.js">here</a></li>
</ul>
<p>
The full example code is <a href="https://github.com/saltycrane/redux-promise-memo/tree/master/examples/filtered-vehicles/03-redux-promise-memo-solution/src">here on github</a>.
A demo is <a href="https://filtered-vehicles3.saltycrane.com/vehicles">deployed here</a>.
</p>
<h5 id="install">
Install <code>redux-promise-memo</code>
</h5>
<p><code>redux-promise-memo</code> uses <code>redux-thunk</code> so it needs to be installed as well.</p>
<pre><code>npm install redux-promise-memo redux-thunk
</code></pre>
<h5 id="index-memo">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/03-redux-promise-memo-solution/src/index.js">
<code>index.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">import</span> thunk <span class="web-mode-keyword">from</span> <span class="web-mode-javascript-string">"redux-thunk"</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">store</span> = <span class="web-mode-function-call">createStore</span><span class="rainbow-delimiters-depth-1">(</span>reducer, <span class="web-mode-function-call">applyMiddleware</span><span class="rainbow-delimiters-depth-2">(</span>thunk, <span class="web-mode-function-call">promiseMiddleware</span><span class="rainbow-delimiters-depth-3">()</span><span class="rainbow-delimiters-depth-2">)</span><span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<p>Add <code>redux-thunk</code> to redux middleware</p>
<h5 id="reducers-memo">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/03-redux-promise-memo-solution/src/reducers.js">
<code>reducers.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-javascript-comment">// NEW: remove the vehiclesCacheParams reducer</span>
<span class="web-mode-javascript-comment">// NEW: add the _memo reducer</span>
<span class="web-mode-keyword">import</span> <span class="rainbow-delimiters-depth-1">{</span>
createMemoReducer,
reduxPromiseMiddlewareConfig
<span class="rainbow-delimiters-depth-1">}</span> <span class="web-mode-keyword">from</span> <span class="web-mode-javascript-string">"redux-promise-memo"</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">_memo</span> = <span class="web-mode-function-call">createMemoReducer</span><span class="rainbow-delimiters-depth-1">(</span>reduxPromiseMiddlewareConfig<span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">rootReducer</span> = <span class="web-mode-function-call">combineReducers</span><span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span>
_memo,
isLoading,
models,
vehicle,
vehicles
<span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<ul>
<li>create the <code>redux-promise-memo</code> reducer and add it to the root reducer. <em>IMPORTANT:</em> the Redux state slice must be named <code>_memo</code> for it to work. I tried to create a Redux enhancer to handle this automatically, but did not get it to work reliably with other libraries.</li>
<li>remove the <code>*CacheParams</code> reducers to store API input params from our manual solution</li>
</ul>
<h5 id="actions-memo">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/03-redux-promise-memo-solution/src/actions.js">
<code>actions.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">import</span> <span class="rainbow-delimiters-depth-1">{</span> memoize <span class="rainbow-delimiters-depth-1">}</span> <span class="web-mode-keyword">from</span> <span class="web-mode-javascript-string">"redux-promise-memo"</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">_fetchModels</span> = <span class="web-mode-variable-name">make</span> => <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: <span class="web-mode-javascript-string">"FETCH_MODELS"</span>,
<span class="web-mode-variable-name">payload</span>: <span class="web-mode-function-call">fakeModelsApi</span><span class="rainbow-delimiters-depth-3">(</span>make<span class="rainbow-delimiters-depth-3">)</span>
<span class="web-mode-javascript-comment">// NEW: remove the API params from the action</span>
<span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-javascript-comment">// NEW: wrap the action creator with `memoize`</span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">memoizedFetchModels</span> = <span class="web-mode-function-call">memoize</span><span class="rainbow-delimiters-depth-1">(</span>_fetchModels, <span class="web-mode-javascript-string">"FETCH_MODELS"</span><span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">_fetchVehicle</span> = <span class="web-mode-variable-name">vehicleId</span> => <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: <span class="web-mode-javascript-string">"FETCH_VEHICLE"</span>,
<span class="web-mode-variable-name">payload</span>: <span class="web-mode-function-call">fakeVehicleApi</span><span class="rainbow-delimiters-depth-3">(</span>vehicleId<span class="rainbow-delimiters-depth-3">)</span>
<span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">memoizedFetchVehicle</span> = <span class="web-mode-function-call">memoize</span><span class="rainbow-delimiters-depth-1">(</span>_fetchVehicle, <span class="web-mode-javascript-string">"FETCH_VEHICLE"</span><span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">_fetchVehicles</span> = <span class="web-mode-variable-name">params</span> => <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: <span class="web-mode-javascript-string">"FETCH_VEHICLES"</span>,
<span class="web-mode-variable-name">payload</span>: <span class="web-mode-function-call">fakeVehiclesApi</span><span class="rainbow-delimiters-depth-3">(</span>params<span class="rainbow-delimiters-depth-3">)</span>
<span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">let</span> <span class="web-mode-variable-name">memoizedFetchVehicles</span> = <span class="web-mode-function-call">memoize</span><span class="rainbow-delimiters-depth-1">(</span>_fetchVehicles, <span class="web-mode-javascript-string">"FETCH_VEHICLES"</span><span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<ul>
<li>wrap the action creators with the <code>memoize</code> higher order function</li>
<li>specify a "key" to be used to separate parameters in the reducer. The action type is recommended to be used as the key.</li>
</ul>
<h5 id="VehiclesList-memo">
<a href="https://github.com/saltycrane/redux-promise-memo/blob/master/examples/filtered-vehicles/03-redux-promise-memo-solution/src/VehiclesList.js">
<code>VehiclesList.js</code>
</a>
</h5>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">VehiclesList</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">React.Component</span> <span class="rainbow-delimiters-depth-1"><span class="custom">{</span></span>
<span class="web-mode-function-name">componentDidUpdate</span><span class="rainbow-delimiters-depth-2">(</span>prevProps<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-javascript-comment">// NEW: removed "if" condition here</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">_fetchData</span><span class="rainbow-delimiters-depth-3">()</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">_fetchData</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">let</span> <span class="rainbow-delimiters-depth-3">{</span> dispatch, query <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-javascript-comment">// NEW: removed "if" condition here</span>
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-3">(</span>actions.<span class="web-mode-function-call">memoizedFetchVehicles</span><span class="rainbow-delimiters-depth-4">(</span>query<span class="rainbow-delimiters-depth-4">)</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-javascript-comment">// ...the rest is the same as before</span>
<span class="rainbow-delimiters-depth-1"><span class="custom">}</span></span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">default</span> <span class="web-mode-function-call">compose</span><span class="rainbow-delimiters-depth-1">(</span>
withVehiclesRouter,
<span class="web-mode-function-call">connect</span><span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-function-name">state</span> => <span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-javascript-comment">// NEW: removed the cacheParams line here</span>
<span class="web-mode-variable-name">isLoading</span>: state.isLoading,
<span class="web-mode-variable-name">vehicles</span>: state.vehicles
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span><span class="rainbow-delimiters-depth-2">)</span>
<span class="rainbow-delimiters-depth-1">)(</span>VehiclesList<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<ul>
<li>update components to remove "if" conditions because the library does the check</li>
<li>remove the use of <code>state.vehicleCacheParams</code></li>
</ul>
<p>This solution should behave similarly to the manual solution with less boilerplate code. In the development environment only, it also logs console messages showing if the API is requesting, loading, or cached.</p>
What does Redux's combineReducers do?
2018-04-25T21:58:26-07:00https://www.saltycrane.com/blog/2018/04/what-does-redux-combinereducers-do/<p>
Redux uses a
<a href="https://redux.js.org/recipes/structuring-reducers/basic-reducer-structure#basic-reducer-structure">single root reducer</a>
function that
<a href="https://redux.js.org/recipes/structuring-reducers/prerequisite-concepts">
accepts the current state (and an action) as input and returns a new state</a>.
Users of Redux may write the root reducer function in many different ways, but a recommended
<a href="https://redux.js.org/recipes/structuring-reducers/splitting-reducer-logic">
common</a>
<a href="https://redux.js.org/recipes/structuring-reducers/using-combinereducers">
practice</a>
is breaking up the state object into slices and using a separate sub reducer to operate on each slice of the state.
Usually, Redux's helper utility, <code>combineReducers</code> is used to do this. <code>combineReducers</code> is a nice shortcut because it encourages the good practice of reducer composition, but the abstraction can
<a href="https://github.com/reactjs/redux/issues/428#issuecomment-129223274">
prevent understanding
</a>
the simplicity of Redux reducers.
</p>
<p>
The example below shows how a root reducer could be written without <code>combineReducers</code>:
</p>
<p>
Given a couple of reducers:
</p>
<pre class="javascript">
function apples(state, action) {
// do stuff
return state;
};
function bananas(state, action) {
// do stuff
return state;
};
</pre>
<p>
This reducer created with <code>combineReducers</code>:
</p>
<pre class="javascript">
const rootReducer = combineReducers({ apples, bananas });
</pre>
<p>
is equivalent to this reducer:
</p>
<pre class="javascript">
function rootReducer(state = {}, action) {
return {
apples: apples(state.apples, action),
bananas: bananas(state.bananas, action),
};
};
</pre>
<h4 id="without-concise-properties">Usage without ES6 concise properties</h4>
<p>
The above example used
<a href="/blog/2016/03/es6-features-used-react-development/#concise-properties">ES6 concise properties</a>,
but <code>combineReducers</code> can also be used without concise properties.
This reducer created with <code>combineReducers</code>:
</p>
<pre class="javascript">
const rootReducer = combineReducers({
a: apples,
b: bananas
});
</pre>
<p>
is equivalent to this reducer:
</p>
<pre class="javascript">
function rootReducer(state = {}, action) {
return {
a: apples(state.a, action),
b: bananas(state.b, action),
};
};
</pre>
<p>
Understanding how <code>combineReducers</code> works can be helpful in learning
<a href="https://redux.js.org/recipes/structuring-reducers/beyond-combinereducers">
other ways</a> reducers can be used.
</p>
<h4 id="references">References / see also </h4>
<ul>
<li><a href="https://redux.js.org/basics/reducers">https://redux.js.org/basics/reducers</a></li>
<li><a href="https://redux.js.org/recipes/structuring-reducers/using-combinereducers">
https://redux.js.org/recipes/structuring-reducers/using-combinereducers</a></li>
<li><a href="https://redux.js.org/api-reference/combinereducers">
https://redux.js.org/api-reference/combinereducers</a></li>
</ul>
How to set up a React Apollo client with a Graphcool GraphQL server
2018-01-03T22:39:24-08:00https://www.saltycrane.com/blog/2018/01/how-set-react-apollo-client-with-graphcool-graphql-server/<p>
<a href="https://www.graph.cool/">Graphcool</a> is a service similar to Firebase except it is used to create
<a href="http://graphql.org/">GraphQL</a> APIs instead of RESTful ones.
<a href="https://www.apollographql.com/client">Apollo Client</a>
is a GraphQL client (alternative to
<a href="https://facebook.github.io/relay/">Relay</a>)
that can be used with React (and other frontend frameworks).
Below is how to create a Graphcool GraphQL service and query it from a React frontend using Apollo Client. I also used
<a href="https://github.com/zeit/next.js">Next.js</a> to set up the React project.
I am running Node 8.4.0 on macOS Sierra.
The code for this example is on github:
<a href="https://github.com/saltycrane/graphcool-apollo-example">
https://github.com/saltycrane/graphcool-apollo-example</a>.
</p>
<p>
Jump to:
<a href="#graphcool">Graphcool setup</a>,
<a href="#nextjs">Next.js setup</a>, or
<a href="#apollo-client">Apollo setup</a>.
</p>
<h4 id="graphcool">Graphcool GraphQL server</h4>
<p>
Graphcool Apollo Quickstart: <a href="https://www.graph.cool/docs/quickstart/frontend/react/apollo-tijghei9go">
https://www.graph.cool/docs/quickstart/frontend/react/apollo-tijghei9go
</a>
</p>
<ul>
<li>Install the Graphcool command-line tool (step 2 in quickstart)
<pre class="console">
$ npm install -g graphcool-framework </pre>
<pre class="console">
$ graphcool-framework --version
graphcool-framework/0.11.5 (darwin-x64) node-v8.4.0 </pre>
</li>
<li>Create a Graphcool service (step 3 in quickstart)
<pre class="console">
$ cd /tmp
$ mkdir graphcool-apollo-example
$ cd graphcool-apollo-example </pre>
<pre class="console">
$ graphcool-framework init myserver
Creating a new Graphcool service in myserver... ✔
Written files:
├─ types.graphql
├─ src
│ ├─ hello.js
│ └─ hello.graphql
├─ graphcool.yml
└─ package.json
To get started, cd into the new directory:
cd myserver
To deploy your Graphcool service:
graphcool deploy
To start your local Graphcool cluster:
graphcool local up
To add facebook authentication to your service:
graphcool add-template auth/facebook
You can find further instructions in the graphcool.yml file,
which is the central project configuration.
</pre>
</li>
<li>Add a <code>Post</code> type definition. (step 4 in quickstart) Edit <code>myserver/types.graphql</code>:
<pre>
type Post @model {
id: ID! @isUnique # read-only (managed by Graphcool)
createdAt: DateTime! # read-only (managed by Graphcool)
updatedAt: DateTime! # read-only (managed by Graphcool)
description: String!
imageUrl: String!
}</pre>
</li>
<li>Deploy the Graphcool server (step 5 in quickstart). After running the <code>deploy</code> command, it will ask to select a cluster, select a name, and create an account with Graphcool.
<pre class="console">
$ cd myserver
$ graphcool-framework deploy
? Please choose the cluster you want to deploy to
shared-eu-west-1
Auth URL: https://console.graph.cool/cli/auth?cliToken=xxxxxxxxxxxxxxxxxxxxxxxxx&authTrigger=auth
Authenticating... ✔
Creating service myserver in cluster shared-eu-west-1... ✔
Bundling functions... 2.3s
Deploying... 1.3s
Success! Created the following service:
Types
Post
+ A new type with the name `Post` is created.
├─ + A new field with the name `createdAt` and type `DateTime!` is created.
├─ + A new field with the name `updatedAt` and type `DateTime!` is created.
├─ + A new field with the name `description` and type `String!` is created.
└─ + A new field with the name `imageUrl` and type `String!` is created.
Resolver Functions
hello
+ A new resolver function with the name `hello` is created.
Permissions
Wildcard Permission
? The wildcard permission for all operations is added.
Here are your GraphQL Endpoints:
Simple API: https://api.graph.cool/simple/v1/cjc2uk4kx0vzo01603rkov391
Relay API: https://api.graph.cool/relay/v1/cjc2uk4kx0vzo01603rkov391
Subscriptions API: wss://subscriptions.graph.cool/v1/cjc2uk4kx0vzo01603rkov391 </pre>
</li>
<li>Run some queries in the Graphcool playground (step 6 in quickstart).
Run the following command to open a new browser tab with the Graphcool playground:
<pre class="console">
$ graphcool-framework playground </pre>
Run a query to create a post:
<pre>
mutation {
createPost(
description: "A rare look into the Graphcool office"
imageUrl: "https://media2.giphy.com/media/xGWD6oKGmkp6E/200_s.gif"
) {
id
}
}</pre>
Run a query to retrieve all posts:
<pre>
query {
allPosts {
id
description
imageUrl
}
}</pre>
</li>
<li>Done with Graphcool server. Also see
<a href="https://console.graph.cool/server/schema/types">
https://console.graph.cool/server/schema/types</a>
</li>
</ul>
<h4 id="nextjs">Next.js React frontend</h4>
<p>Set up a React frontend using the Next.js framework. Next.js setup:
<a href="https://github.com/zeit/next.js#setup">
https://github.com/zeit/next.js#setup</a>
</p>
<ul>
<li>Create a <code>myclient</code> directory alongside the <code>myserver</code> directory:
<pre class="console">
$ cd /tmp/graphcool-apollo-example
$ mkdir myclient
$ cd myclient </pre>
</li>
<li>Create <code>myclient/package.json</code>:
<pre>
{
"dependencies": {
"next": "4.2.1",
"react": "16.2.0",
"react-dom": "16.2.0"
},
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}</pre>
</li>
<li>Install Next.js and React:
<pre class="console">
$ npm install
npm WARN deprecated npmconf@2.1.2: this package has been reintegrated into npm and is now out of date with respect to npm
npm WARN deprecated @semantic-release/last-release-npm@2.0.2: Use @semantic-release/npm instead
> fsevents@1.1.3 install /private/tmp/graphcool-apollo-example/myclient/node_modules/fsevents
> node install
[fsevents] Success: "/private/tmp/graphcool-apollo-example/myclient/node_modules/fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node" already installed
Pass --update-binary to reinstall or --build-from-source to recompile
> uglifyjs-webpack-plugin@0.4.6 postinstall /private/tmp/graphcool-apollo-example/myclient/node_modules/uglifyjs-webpack-plugin
> node lib/post_install.js
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN myclient No description
npm WARN myclient No repository field.
npm WARN myclient No license field.
+ react-dom@16.2.0
+ react@16.2.0
+ next@4.2.1
added 838 packages in 20.012s</pre>
</li>
<li>Create a Hello World page
<pre class="console">
$ mkdir pages</pre>
Create <code>myclient/pages/index.js</code>:
<pre class="javascript">
const Home = () => <div>Hello World</div>;
export default Home;</pre>
</li>
<li>Run the Next.js dev server
<pre class="console">
$ npm run dev </pre>
Go to <a href="http://localhost:3000">http://localhost:3000</a> in the browser
</li>
</ul>
<h4 id="apollo-client">Apollo Client</h4>
<p>
Set up Apollo Client to query the Graphcool server.
Apollo Client setup:
<a href="https://www.apollographql.com/docs/react/basics/setup.html">
https://www.apollographql.com/docs/react/basics/setup.html</a>
</p>
<ul>
<li>Install Apollo Client
<pre class="console">
$ cd /tmp/graphcool-apollo-example/myclient </pre>
<pre class="console">
$ npm install apollo-client-preset react-apollo graphql-tag graphql
npm WARN apollo-link-http@1.3.2 requires a peer of graphql@^0.11.0 but none is installed. You must install peer dependencies yourself.
npm WARN myclient No description
npm WARN myclient No repository field.
npm WARN myclient No license field.
+ react-apollo@2.0.4
+ graphql-tag@2.6.1
+ apollo-client-preset@1.0.6
+ graphql@0.12.3
added 20 packages in 5.476s</pre>
</li>
<li>Set up Apollo Client. Edit <code>myclient/pages/index.js</code>:
<pre class="javascript">
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloClient } from "apollo-client";
import { HttpLink } from "apollo-link-http";
import { ApolloProvider } from "react-apollo";
const client = new ApolloClient({
link: new HttpLink({
// Replace this with your Graphcool server URL
uri: "https://api.graph.cool/simple/v1/cjc2uk4kx0vzo01603rkov391",
}),
cache: new InMemoryCache(),
});
const Home = () => <div>Hello World</div>;
const App = () => (
<ApolloProvider client={client}>
<Home />
</ApolloProvider>
);
export default App;</pre>
</li>
<li>Try running the dev server
<pre class="console">
$ npm run dev </pre>
Go to <a href="http://localhost:3000">http://localhost:3000</a> in the browser
</li>
<li>But, get this error:
<pre>
Error: fetch is not found globally and no fetcher passed, to fix pass a fetch for
your environment like https://www.npmjs.com/package/node-fetch.
For example:
import fetch from 'node-fetch';
import { createHttpLink } from 'apollo-link-http';
const link = createHttpLink({ uri: '/graphql', fetch: fetch });
at warnIfNoFetch (/private/tmp/graphcool-apollo-example/myclient/node_modules/apollo-link-http/lib/httpLink.js:72:15)
at createHttpLink (/private/tmp/graphcool-apollo-example/myclient/node_modules/apollo-link-http/lib/httpLink.js:89:5)
at new HttpLink (/private/tmp/graphcool-apollo-example/myclient/node_modules/apollo-link-http/lib/httpLink.js:159:34)
at Object.<anonymous> (/private/tmp/graphcool-apollo-example/myclient/.next/dist/pages/index.js:25:9)
at Module._compile (module.js:573:30)
at Module._compile (/private/tmp/graphcool-apollo-example/myclient/node_modules/source-map-support/source-map-support.js:492:25)
at Object.Module._extensions..js (module.js:584:10)
at Module.load (module.js:507:32)
at tryModuleLoad (module.js:470:12)
at Function.Module._load (module.js:462:3)</pre>
</li>
<li>Install <code>node-fetch</code>. This is needed because Next.js runs on a Node server in addition to the browser.
<pre class="console">
$ npm install node-fetch
npm WARN apollo-link-http@1.3.2 requires a peer of graphql@^0.11.0 but none is installed. You must install peer dependencies yourself.
npm WARN myclient No description
npm WARN myclient No repository field.
npm WARN myclient No license field.
+ node-fetch@1.7.3
updated 1 package in 4.028s</pre>
</li>
<li>Update the code to use <code>node-fetch</code> as described in the error message:
<pre class="javascript">
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloClient } from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import gql from "graphql-tag";
import fetch from "node-fetch";
import { ApolloProvider } from "react-apollo";
const client = new ApolloClient({
link: createHttpLink({
// Replace this with your Graphcool server URL
uri: "https://api.graph.cool/simple/v1/cjc2uk4kx0vzo01603rkov391",
fetch: fetch,
}),
cache: new InMemoryCache(),
});
class Home extends React.Component {
componentDidMount() {
client
.query({
query: gql`
{
allPosts {
id
description
imageUrl
}
}
`,
})
.then(console.log);
}
render() {
return <div>Look in the devtools console</div>;
}
}
const App = () => (
<ApolloProvider client={client}>
<Home />
</ApolloProvider>
);
export default App;</pre>
</li>
<li>Try running the dev server again
<pre class="console">
$ npm run dev </pre>
Go to <a href="http://localhost:3000">http://localhost:3000</a> in the browser
</li>
<li>It works. Open the browser devtools console and see the result of the query:
<pre>
{
"data": {
"allPosts": [
{
"id": "cjbffxjq5rrvd0192qmptpm2f",
"description": "A rare look into the Graphcool office",
"imageUrl": "https://media2.giphy.com/media/xGWD6oKGmkp6E/200_s.gif",
"__typename": "Post"
}
]
},
"loading": false,
"networkStatus": 7,
"stale": false
}</pre>
</li>
<li>Use the <code>graphql</code> higher-order component to make things nicer. Edit <code>myclient/pages/index.js</code>:
<pre class="javascript">
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloClient } from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import gql from "graphql-tag";
import fetch from "node-fetch";
import { ApolloProvider, graphql } from "react-apollo";
const client = new ApolloClient({
link: createHttpLink({
// Replace this with your Graphcool server URL
uri: "https://api.graph.cool/simple/v1/cjc2uk4kx0vzo01603rkov391",
fetch: fetch,
}),
cache: new InMemoryCache(),
});
const MY_QUERY = gql`
{
allPosts {
id
description
imageUrl
}
}
`;
const Home = ({ data }) => <pre>{JSON.stringify(data, null, 2)}</pre>;
const HomeWithData = graphql(MY_QUERY)(Home);
const App = () => (
<ApolloProvider client={client}>
<HomeWithData />
</ApolloProvider>
);
export default App;</pre>
</li>
<li>Run the dev server
<pre class="console">
$ npm run dev </pre>
Go to <a href="http://localhost:3000">http://localhost:3000</a> in the browser
and see this result on the page:
<pre>
{
"variables": {},
"loading": false,
"networkStatus": 7,
"allPosts": [
{
"id": "cjbffxjq5rrvd0192qmptpm2f",
"description": "A rare look into the Graphcool office",
"imageUrl": "https://media2.giphy.com/media/xGWD6oKGmkp6E/200_s.gif",
"__typename": "Post"
}
]
}</pre>
</li>
</ul>
What are Redux selectors? Why use them?
2017-05-27T14:40:11-07:00https://www.saltycrane.com/blog/2017/05/what-are-redux-selectors-why-use-them/<p>
Selectors are functions that take Redux state as an argument and return some
data to pass to the component.
</p>
<p>They can be as simple as:</p>
<pre class="javascript">
const getDataType = state => state.editor.dataType;
</pre>
<p>Or they can do more complicated data transformations like filter a list.</p>
<p>
They are typically used in <code>mapStateToProps</code
><sup><a href="#fn-1">1</a></sup> in <code>react-redux</code>'s
<code>connect</code>:
</p>
<p>For example this</p>
<pre class="javascript">
export default connect(
(state) => ({
dataType: state.editor.dataType,
})
)(MyComponent);
</pre>
<p>becomes this:</p>
<pre class="javascript">
export default connect(
(state) => ({
dataType: getDataType(state),
})
)(MyComponent);
</pre>
<h4 id="why">Why?</h4>
<p>One reason is to avoid duplicated data in Redux.</p>
<p>
Redux state can be thought of like a database and selectors like SELECT
queries to get useful data from the database. A good example is a filtered
list.
</p>
<p>
If we have a list of items in Redux but want to show a filtered list of the
items in the UI, we could filter the list, store it in Redux then pass it to
the component. The problem is there are two copies of some of the items and it
is easier for data to get out of sync. If we wanted to update an item, we'd
have to update it in two places.
</p>
<p>
With selectors, a <code>filterBy</code> value would be stored in Redux and a
selector would be used to compute the filtered list from the Redux state.
</p>
<h4 id="why-not-component">
Why not perform the data transformations in the component?
</h4>
<p>
Performing data transformations in the component makes them more coupled to
the Redux state and less generic/reusable. Also, as Dan Abramov points out, it
makes sense to keep selectors near reducers because they operate on the same
state. If the state schema changes, it is easier to update the selectors than
to update the components.
</p>
<h4 id="performance">Performance</h4>
<p>
<code>mapStateToProps</code> gets called a lot so performing expensive
calculations there is not good. This is where the
<a href="https://github.com/reactjs/reselect"><code>reselect</code></a>
library comes in. Selectors created with <code>reselects</code>'s
<code>createSelector</code> will memoize to avoid unnecessary recalculations.
Note if performance is not an issue, <code>createSelector</code> is not
needed.
</p>
<p>
<em>Update</em>: in my experience, even more important than memoizing
expensive calcuations in selectors is preserving referential equality of
values returned by selectors that are passed as props to
<code>memo</code>-wrapped function components or
<code>PureComponent</code> derived class components to prevent re-rendering.
See
<a href="/blog/2019/03/how-to-usememo-improve-performance-react-table/"
>my post on React perfomance</a
>
for more information.
</p>
<h4 id="references">References / See also</h4>
<ul>
<li>
I got interested in selectors after watching Dan Abramov's video:
<a
href="https://egghead.io/lessons/javascript-redux-colocating-selectors-with-reducers"
>
https://egghead.io/lessons/javascript-redux-colocating-selectors-with-reducers</a
>
</li>
<li>
Redux docs on selectors:
<a href="http://redux.js.org/docs/recipes/ComputingDerivedData.html">
http://redux.js.org/docs/recipes/ComputingDerivedData.html</a
>
</li>
<li>
Reselect:
<a href="https://github.com/reactjs/reselect">
https://github.com/reactjs/reselect</a
>
</li>
</ul>
<hr />
<p id="fn-1">
1. <code>mapStateToProps</code> can be considered a selector itself
</p>
Using Firebase, Next.js, React, Redux, styled-components, and Reactstrap to build a Todo app
2017-05-24T22:33:51-07:00https://www.saltycrane.com/blog/2017/05/using-firebase-nextjs-react-redux-todo-app/ <p>
Here are some notes on making yet another task manager app using
<a href="https://firebase.google.com/">Firebase</a>,
<a href="https://github.com/zeit/next.js">Next.js</a>,
<a href="https://facebook.github.io/react/">React</a>,
<a href="https://github.com/reactjs/redux">Redux</a>,
<a href="https://github.com/styled-components/styled-components">styled-components</a>,
<a href="https://reactstrap.github.io/">Reactstrap</a> <a href="https://v4-alpha.getbootstrap.com/">(Bootstrap 4 alpha)</a>,
<a href="https://flow.org/">Flow</a>, and
<a href="https://github.com/prettier/prettier">Prettier</a>.
I realize <a href="https://hnpwa.com/">Hacker News clones</a>
are the new <a href="http://todomvc.com/">Todo app</a>,
but I built an old Todo app since I'm old. I starting out making a different app, but then I started putting my todo notes in my app so...
</p>
<p>
The app is here: <a href="https://kage.saltycrane.com">https://kage.saltycrane.com</a>
and the code is here: <a href="https://github.com/saltycrane/kage">https://github.com/saltycrane/kage</a>.
</p>
<h4>Notes</h4>
<ul>
<li>Uses Firebase password authentication, Google authentication, and anonymous authentication when a user does not sign in.</li>
<li>Uses the Firebase database to persist tasks.</li>
<li>Next.js supports server-side rendering but I'm not taking advantage of this because I could not figure out how to configure Firebase auth on the server side.</li>
<li>
I'm a fan of using Redux for applications. I think it makes complicated interactions easy
to follow and debug. However, Redux is not good at handling complicated asynchronous side
effects. I use <a href="https://github.com/gaearon/redux-thunk"><code>redux-thunk</code></a>
a little to sequence asynchronous actions and <code>async</code>+<code>await</code> in some
places. I tried to keep it's usage to a minimum because it is easy to misuse/overuse.
I've <a href="https://github.com/saltycrane/react-chained-modals-comparison">played with</a>
<a href="https://github.com/redux-saga/redux-saga"><code>redux-saga</code></a>
and it seems good. Maybe I will use it if I build something more complicated.
</li>
<li>
Uses my own
<a href="https://github.com/saltycrane/redux-promise-memo">
<code>redux-promise-memo</code></a> library.
It contains promise middleware copied from
<a href="https://github.com/TrueCar/gluestick">Gluestick</a>,
a reducer to store arguments for "memoization" and API status,
and a <code>memoize</code> decorator which will prevent firing a promise-based action if has
already been fired with the same arguments. "Memoization" is in quotes because it does not
manage storing the cached data. It only manages whether to dispatch the action or not.
Assume the user has stored the data in Redux.
</li>
<li>
Initially I didn't understand how to use styled-components.
Later I realized styled-components could be used in place of sub objects in a <code>styles</code> object for a component when using e.g.
Radium or
Aphrodite.
I like how it makes the JSX look clean.
I've heard performance is a weakness of styled-components but they are working to improve it.
</li>
<li>
I'm a former backend developer so I don't know how to make things pretty.
I guess I'll go with Bootstrap.
</li>
<li>
Flow has a lot of pain points but in-editor feedback is useful and it helps me write
simpler code.
</li>
<li>Prettier is great.</li>
</ul>
<h4>Other ideas to try</h4>
<ul>
<li>
<a href="https://www.graph.cool/">Graphcool</a> instead of Firebase
</li>
<li>
<a href="http://dev.apollodata.com/">Apollo</a> or
<a href="https://facebook.github.io/relay/">Relay Modern</a>
in addition to or in place of Redux
</li>
<li>
Make a
<a href="https://developers.google.com/web/progressive-web-apps/">Progressive Web App</a> and add
<a href="https://github.com/jevakallio/redux-offline">redux-offline</a>
</li>
</ul>
<br>
<p>
Here's how to try out Next.js on Mac OS X
</p>
<h4 id="install-node-yarn">Install Node.js and Yarn</h4>
<ul>
<li>
Install Node.js 6.10 LTS using
<a href="https://brew.sh/">Homebrew</a>
<pre class="console">$ brew install node@6 </pre>
This is an alternate version of Node.js so the PATH environment variable needs to be updated to find it.
Update <code>~/.bash_profile</code> to add Node 6 to the beginning of the PATH variable
<pre class="console">$ echo 'export PATH="/usr/local/opt/node@6/bin:$PATH"' >> ~/.bash_profile </pre>
Restart the terminal to source <code>~/.bash_profile</code> to update the PATH variable. After restarting
the terminal, check that Node is installed:
<pre class="console">$ node --version
v6.10.3</pre>
</li>
<li>Install the
<a href="https://yarnpkg.com/en/">Yarn</a>
package manager using the npm package manager that comes with Node.js
<pre class="console">$ npm install -g yarn </pre>
Verify yarn is installed:
<pre class="console">$ yarn --version
0.24.5</pre>
</li>
</ul>
<h4 id="next-js-hello-world">Hello World with Next.js</h4>
<ul>
<li>
Create a new project folder
<pre class="console">
$ mkdir ~/myproject
$ cd ~/myproject </pre>
</li>
<li>
Create a new file named <code>package.json</code> with the following:
<pre class="json">{
"dependencies": {
"next": "3.0.0-beta16",
"react": "15.6.1",
"react-dom": "15.6.1"
},
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}</pre>
</li>
<li>
Install Next.js and React in the local project directory
<pre class="console">$ yarn </pre>
</li>
<li>
Create a <code>pages</code> directory and a new file <code>pages/index.js</code>:
<pre class="javascript">import React from "react";
export default class Hello extends React.Component {
render() {
return <div>Hello World</div>;
}
}</pre>
</li>
<li>
Start the dev server
<pre class="console">$ yarn dev </pre>
</li>
<li>
Go to <a href="http://localhost:3000">http://localhost:3000</a> in the browser
</li>
</ul>
Flow type cheat sheet
2016-06-09T17:16:04-07:00https://www.saltycrane.com/blog/2016/06/flow-type-cheat-sheet/<style>
#flow a {
color: #337ab7;
}
#flow a:visited {
color: #002540;
}
</style>
<div id="flow">
<p>
<a href="https://flow.org/">Flow</a> is a static type checker for Javascript.
This is a list of Flow types generated from the source code in <a href="https://github.com/facebook/flow/tree/v0.52.0/">https://github.com/facebook/flow/tree/v0.52.0/</a>
The script to generate this list is on <a href="https://github.com/saltycrane/flow-cheatsheet">github</a>.
Fixes welcome.
</p>
<p>Note: I created a separate section for "private" or "magic" types with a <code>$</code> in the name.
See the <a href="http://sitr.us/2015/05/31/advanced-features-in-flow.html">note here</a> and <a href="https://github.com/facebook/flow/issues/2197#issuecomment-238001710">comment here</a>. <em>Update</em>: Some these types are now <a href="https://flow.org/en/docs/types/utilities/">documented here</a>.</p><p>Flow version: v0.52.0</p>
<ul class="list-unstyled">
<li><a href="#builtins">Built-in types</a></li> <li><a href="#lib/core.js">Core</a></li>
<li><a href="#lib/react.js">React</a></li>
<li><a href="#lib/dom.js">Document Object Model (DOM)</a></li>
<li><a href="#lib/bom.js">Browser Object Model (BOM)</a></li>
<li><a href="#lib/cssom.js">CSS Object Model (CSSOM)</a></li>
<li><a href="#lib/indexeddb.js">indexedDB</a></li>
<li><a href="#lib/node.js">Node.js</a></li>
<li><a href="#lib/serviceworkers.js">Service Workers</a></li>
<li><a href="#lib/streams.js">Streams</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=builtins>Built-ins</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://flow.org/en/docs/types/utilities/#toc-the-existential-type">* (Existential type)</a></li>
<li><a href="https://flow.org/en/docs/types/any/">any</a></li>
<li><a href="https://flow.org/en/docs/types/primitives/#toc-booleans">boolean</a></li>
<li><a href="https://flow.org/en/docs/types/primitives/#toc-null-and-void">null</a></li>
<li><a href="https://flow.org/en/docs/types/primitives/#toc-numbers">number</a></li>
<li><a href="https://flow.org/en/docs/types/mixed/">mixed</a></li>
<li><a href="https://flow.org/en/docs/types/primitives/#toc-strings">string</a></li>
<li><a href="https://flow.org/en/docs/types/primitives/#toc-null-and-void">void</a></li>
<li><a href="https://flow.org/en/docs/types/arrays/">Arrays</a></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://flow.org/en/docs/types/utilities/#toc-class">Class<T></a></li>
<li><a href="https://flow.org/en/docs/types/classes/">Classes</a></li>
<li><a href="https://flow.org/en/docs/types/objects/#toc-exact-object-types">Exact objects ({||} syntax)</a></li>
<li><a href="https://flow.org/en/docs/types/functions/">Functions</a></li>
<li><a href="https://flow.org/en/docs/types/generics/">Generics</a></li>
<li><a href="https://flow.org/en/docs/types/interfaces/">Interfaces</a></li>
<li><a href="https://flow.org/en/docs/types/intersections/">Intersection types</a></li>
<li><a href="https://flow.org/en/docs/types/literals/">Literal types</a></li>
<li><a href="https://flow.org/en/docs/types/maybe/">Maybe types</a></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://flow.org/en/docs/types/objects/">Objects</a></li>
<li><a href="https://flow.org/en/docs/types/opaque-types/">Opaque types</a></li>
<li><a href="https://flow.org/en/docs/types/tuples/">Tuples</a></li>
<li><a href="https://flow.org/en/docs/types/aliases/">Type aliases</a></li>
<li><a href="https://flow.org/en/docs/types/typeof/">Typeof</a></li>
<li><a href="https://flow.org/en/docs/types/unions/">Union types</a></li>
<li><a href="https://flow.org/en/docs/types/variables/">Variable types</a></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=builtins-private>Built-in "private" types</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://flow.org/en/docs/types/utilities/#toc-abstract">$Abstract<T></a></li>
<li><a href="https://flow.org/en/docs/types/utilities/#toc-diff">$Diff<A, B></a></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/src/typing/type_annotation.ml#L242">$ElementType<T, string> acts as the type of the string elements in object</a></li>
<li><a href="https://flow.org/en/docs/types/utilities/#toc-exact">$Exact<T></a></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/src/typing/type_annotation.ml#L300">$Exports<'M'> is the type of the exports of module 'M' </a></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://flow.org/en/docs/types/utilities/#toc-keys">$Keys<T></a></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/src/typing/type_annotation.ml#L253">$NonMaybeType<T> acts as the type T without null and void </a></li>
<li><a href="https://flow.org/en/docs/types/utilities/#toc-objmap">$ObjMap<T, F></a></li>
<li><a href="https://flow.org/en/docs/types/utilities/#toc-propertytype">$PropertyType<T, x></a></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/src/typing/type_annotation.ml#L203">$ReadOnlyArray<T> is the supertype of all tuples and all arrays </a></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/src/typing/type_annotation.ml#L261">$Shape<T> matches the shape of T </a></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/src/typing/type_annotation.ml#L217">$Subtype<T> acts as any over subtypes of T </a></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/src/typing/type_annotation.ml#L210">$Supertype<T> acts as any over supertypes of T </a></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/src/typing/type_annotation.ml#L285">$Values<T> is a union of all the own enumerable value types of T </a></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/core.js>Core</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L241">Array<T> </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L686">ArrayBuffer</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L596">AsyncIterable<+T></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L591">AsyncIterator<+T></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L106">Boolean</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L490">CallSite</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L873">console</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L770">DataView</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L434">Date</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L508">Error</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L532">EvalError </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L870">exports</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L767">Float32Array </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L768">Float64Array </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L96">Function</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L853">global</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L15">Infinity</a> <small>(var)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L763">Int16Array </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L765">Int32Array </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L760">Int8Array </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L574">Iterable<+T></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L569">Iterator<+T></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L561">IteratorResult<Yield,Return></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L550">JSON</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L609">Map<K, V></a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L151">Math</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L855">module</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L14">NaN</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L126">Number</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L28">Object</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L660">Promise<+R></a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L845">Proxy<T></a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L535">RangeError </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L538">ReferenceError </a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L806">Reflect</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L401">RegExp</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L864">require</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L634">Set<T></a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L290">String</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L75">Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L541">SyntaxError </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L544">TypeError </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L764">Uint16Array </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L766">Uint32Array </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L761">Uint8Array </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L762">Uint8ClampedArray </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L16">undefined</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L547">URIError </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L626">WeakMap<K, V></a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L649">WeakSet<T</a> <small>(class)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/core.js-private>Core "private" types</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L697">$ArrayBufferView</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L199">$ReadOnlyArray<+T></a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L64">$SymboIsConcatSpreadable mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L63">$SymbolHasInstance mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L65">$SymbolIterator mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L66">$SymbolMatch mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L67">$SymbolReplace mixins Symbol</a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L68">$SymbolSearch mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L69">$SymbolSpecies mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L70">$SymbolSplit mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L71">$SymbolToPrimitive mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L72">$SymbolToStringTag mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L73">$SymbolUnscopables mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L702">$TypedArray</a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L418">Date$LocaleOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L113">Number$LocaleOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L841">Proxy$revocable<T></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L825">Proxy$traps<T></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/core.js#L333">RegExp$flags</a> <small>(type)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/react.js>React</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L135">_ReactClass< DefaultProps, Props, Config</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L96">LegacyReactComponent< DefaultProps, Props, State,> </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L164">react</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L165">Children</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L204">Component</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L180">createClass</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L195">createElement</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L166">DOM</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L206">Element</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L167">PropTypes</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L205">PureComponent</a> <small>(var)</small></li>
</ul></ul></div>
<div class="col-sm-4"><ul><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L168">version</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L213">React</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L214">exports</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L217">react-dom</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L229">version</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L247">react-dom/server</a> <small>(module)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L134">ReactClass<Config></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L262">ReactPropsChainableTypeChecker</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L256">ReactPropsCheckType</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L282">ReactPropTypes</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L318">SyntheticClipboardEvent </a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L322">SyntheticCompositionEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L372">SyntheticDragEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L300">SyntheticEvent</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L336">SyntheticFocusEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L326">SyntheticInputEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L340">SyntheticKeyboardEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L355">SyntheticMouseEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L383">SyntheticTouchEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L331">SyntheticUIEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L376">SyntheticWheelEvent </a> <small>(class)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/react.js-private>React "private" types</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L150">$DefaultPropsOf<Config></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L394">$JSXIntrinsics</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L149">$PropsOf<Config></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L22">React$Component<DefaultProps, Props, State></a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L157">React$Element<Config></a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L12">React$Node<Config></a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L267">React$PropTypes$arrayOf</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L269">React$PropTypes$instanceOf</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L271">React$PropTypes$objectOf</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L273">React$PropTypes$oneOf</a> <small>(type)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L275">React$PropTypes$oneOfType</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L278">React$PropTypes$shape</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/react.js#L74">React$PureComponent< DefaultProps, Props, State,> </a> <small>(class)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/dom.js>Document Object Model (DOM)</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L372">AnimationEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L139">AnimationEventHandler </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L140">AnimationEventListener</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L149">AnimationEventTypes</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L501">Attr </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2552">AudioTrack</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2560">AudioTrackList </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L12">Blob</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1608">BufferDataSource</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1359">CanvasDrawingStyles</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1344">CanvasFillRule</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1330">CanvasGradient</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1346">CanvasImageSource</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1334">CanvasPattern</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1438">CanvasRenderingContext2D</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3078">CharacterData </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3036">ClientRect</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3045">ClientRectList</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3101">Comment </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L105">CustomElementRegistry</a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3275">customElements</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L246">CustomEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L66">DataTransfer</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L88">DataTransferItem</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L78">DataTransferItemList</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1015">document</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L553">Document </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L943">DocumentFragment </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3063">DocumentType </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L97">DOMError</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3054">DOMImplementation</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1019">DOMTokenList</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L316">DragEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L137">DragEventHandler </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L138">DragEventListener</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L148">DragEventTypes</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1035">Element </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L521">ElementRegistrationOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L213">Event</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3254">event</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L123">EventHandler </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L124">EventListener</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L151">EventListenerOptionsOrUseCapture</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L157">EventTarget</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L47">File </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L57">FileList</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L24">FileReader </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L305">FocusEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L127">FocusEventHandler </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L128">FocusEventListener</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L143">FocusEventTypes</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1348">HitRegionOptions</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2895">HTMLAnchorElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2964">HTMLAppletElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2703">HTMLAudioElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1321">HTMLBaseElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2972">HTMLBRElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2810">HTMLButtonElement </a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2447">HTMLCanvasElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L512">HTMLCollection<Elem</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2460">HTMLDetailsElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2960">HTMLDivElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2974">HTMLDListElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1185">HTMLElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2966">HTMLEmbedElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2486">HTMLFieldSetElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2464">HTMLFormElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2968">HTMLHeadingElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2970">HTMLHRElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2501">HTMLIFrameElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2517">HTMLImageElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2740">HTMLInputElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2922">HTMLLabelElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2497">HTMLLegendElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2984">HTMLLIElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2928">HTMLLinkElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2642">HTMLMediaElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1316">HTMLMenuElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2990">HTMLMetaElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2976">HTMLOListElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2890">HTMLOptGroupElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2879">HTMLOptionElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2873">HTMLOptionsCollection </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2956">HTMLParagraphElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2988">HTMLPreElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2938">HTMLScriptElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2855">HTMLSelectElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1274">HTMLSlotElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2714">HTMLSourceElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2962">HTMLSpanElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2948">HTMLStyleElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1295">HTMLTableCaptionElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1303">HTMLTableCellElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1279">HTMLTableElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1309">HTMLTableRowElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1299">HTMLTableSectionElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1326">HTMLTemplateElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2821">HTMLTextAreaElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2982">HTMLUListElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2706">HTMLVideoElement </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2533">Image </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1338">ImageBitmap</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1426">ImageData</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L354">KeyboardEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L129">KeyboardEventHandler </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L130">KeyboardEventListener</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L144">KeyboardEventTypes</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3263">localStorage</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2537">MediaError</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3125">MediaSource </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L347">MessageEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L279">MouseEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L125">MouseEventHandler </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L126">MouseEventListener</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L142">MouseEventTypes</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L488">NamedNodeMap</a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L423">Node </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3196">NodeFilter</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3187">NodeFilterCallback </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3192">NodeFilterInterface</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3218">NodeIterator<RootNodeT, WhatToShowT></a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L476">NodeList<T></a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3268">parent</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1404">Path2D</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L320">ProgressEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L135">ProgressEventHandler </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L136">ProgressEventListener</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L147">ProgressEventTypes</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L336">PromiseRejectionEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L981">Range</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2444">RenderingContext</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L956">Selection</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2738">SelectionDirection</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2739">SelectionMode</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3270">self</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3271">sessionStorage</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L111">ShadowRoot </a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3136">SourceBuffer </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3156">SourceBufferList </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3272">status</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3162">Storage</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1377">SVGMatrix</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1610">TexImageSource</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3094">Text </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1386">TextMetrics</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2996">TextRange</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2618">TextTrack </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2590">TextTrackCue </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2611">TextTrackCueList</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2634">TextTrackList </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2546">TimeRanges</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3273">top</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L389">Touch</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L411">TouchEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L131">TouchEventHandler </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L132">TouchEventListener</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L145">TouchEventTypes</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L403">TouchList</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3177">TrackDefault</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3172">TrackDefaultList</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3232">TreeWalker<RootNodeT, WhatToShowT></a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L259">UIEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L3105">URL</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2724">ValidityState</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1617">VertexAttribFVSource</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2571">VideoTrack</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2579">VideoTrackList </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1561">WebGLContextAttributes</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L2439">WebGLContextEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L1622">WebGLRenderingContext</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L309">WheelEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L133">WheelEventHandler </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L134">WheelEventListener</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L146">WheelEventTypes</a> <small>(type)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/dom.js-private>Document Object Model (DOM) "private" types</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L242">CustomEvent$Init</a> <small>(type)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L206">Event$Init</a> <small>(type)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/dom.js#L264">MouseEvent$MouseEventInit</a> <small>(type)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/bom.js>Browser Object Model (BOM)</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L735">AnalyserNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L665">AudioBuffer</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L675">AudioBufferSourceNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L590">AudioContext</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L647">AudioDestinationNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L651">AudioListener </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L624">AudioNode</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L636">AudioParam </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L73">BatteryManager</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L747">BiquadFilterNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L856">BodyInit</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L845">CacheType</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L756">ChannelMergerNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L757">ChannelSplitterNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L385">CloseEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L758">ConvolverNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L567">Coordinates</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L846">CredentialsType</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L445">DedicatedWorkerGlobalScope </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L763">DelayNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L306">DOMParser</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L767">DynamicsCompressorNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L312">FormData</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L310">FormDataEntryValue</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L776">GainNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L59">Gamepad</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L41">GamepadButton</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L45">GamepadHapticActuator</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L49">GamepadPose</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L548">Geolocation</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L816">Headers</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L811">HeadersInit</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L272">History</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L283">history</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L285">Location</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L302">location</a> <small>(var)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L727">MediaElementAudioSourceNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L687">MediaStream </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L728">MediaStreamAudioSourceNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L705">MediaStreamTrack</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1181">MessageChannel</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1173">MessagePort </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L140">MimeType</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L147">MimeTypeArray</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L847">ModeType</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L358">MutationObserver</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L346">MutationObserverInitRequired</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L334">MutationRecord</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L138">navigator</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L95">Navigator mixins NavigatorCommon</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L84">NavigatorCommon</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L365">NodeFilter</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L780">OscillatorNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L789">PannerNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L253">Performance</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L270">performance</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L215">PerformanceEntry</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L208">PerformanceEntryFilterOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L198">PerformanceNavigation</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L240">PerformanceNavigationTiming </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L224">PerformanceResourceTiming </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L174">PerformanceTiming</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L802">PeriodicWave </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L154">Plugin</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L165">PluginArray</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L562">Position</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L576">PositionError</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L584">PositionOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L848">RedirectType</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L849">ReferrerPolicyType</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L908">Request</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L858">RequestInfo</a> <small>(type)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L860">RequestOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L882">Response</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L876">ResponseOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L854">ResponseType</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L12">Screen</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L38">screen</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L730">ScriptProcessorNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L423">SharedWorker </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L450">SharedWorkerGlobalScope </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1165">TextDecoder</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L939">TextEncoder</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L830">URLSearchParams</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1186">VRDisplay </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1213">VRDisplayCapabilities</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1259">VRDisplayEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1254">VRDisplayEventInit</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1252">VRDisplayEventReason</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1220">VREye</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1240">VREyeParameters</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1231">VRFrameData</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1207">VRLayerInit</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1222">VRPose</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1205">VRSource</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L1246">VRStageParameters</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L803">WaveShaperNode </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L391">WebSocket </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L39">window</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L415">Worker </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L431">WorkerGlobalScope </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L455">WorkerLocation</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L466">WorkerNavigator mixins NavigatorCommon</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L468">XDomainRequest</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L487">XMLHttpRequest </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L534">XMLHttpRequestEventTarget </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L544">XMLSerializer</a> <small>(class)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/bom.js-private>Browser Object Model (BOM) "private" types</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L947">TextDecoder$availableEncodings</a> <small>(type)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/bom.js#L937">TextEncoder$availableEncodings</a> <small>(type)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/cssom.js>CSS Object Model (CSSOM)</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/cssom.js#L44">CSSRule</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/cssom.js#L67">CSSRuleList</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/cssom.js#L74">CSSStyleDeclaration</a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/cssom.js#L37">CSSStyleSheet </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/cssom.js#L27">MediaList</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/cssom.js#L11">StyleSheet</a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/cssom.js#L21">StyleSheetList</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/cssom.js#L384">TransitionEvent </a> <small>(class)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/indexeddb.js>indexedDB</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L104">IDBCursor</a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L115">IDBCursorWithValue </a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L30">IDBDatabase </a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L6">IDBDirection</a> <small>(type)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L2">IDBEnvironment</a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L9">IDBFactory</a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L80">IDBIndex </a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L93">IDBKeyRange</a> <small>(interface)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L58">IDBObjectStore</a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L25">IDBOpenDBRequest </a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L15">IDBRequest </a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/indexeddb.js#L46">IDBTransaction </a> <small>(interface)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/node.js>Node.js</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1813">__dirname</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1812">__filename</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1662">assert</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1663">AssertionError </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1664">exports</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L119">buffer</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L121">INSPECT_MAX_BYTES</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L120">kMaxLength</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L29">Buffer </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L254">child_process</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L255">ChildProcess</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L329">cluster</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L330">Cluster </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L350">exports</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L478">crypto</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L479">DEFAULT_ENCODING</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L481">Sign </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L482">Verify </a> <small>(class)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L569">dgram</a> <small>(module)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L573">dns</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L574">ADDRGETNETWORKPARAMS</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L575">BADFAMILY</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L576">BADFLAGS</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L577">BADHINTS</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L579">BADNAME</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L578">BADQUERY</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L580">BADRESP</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L581">BADSTR</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L582">CANCELLED</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L583">CONNREFUSED</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L584">DESTRUCTION</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L585">EOF</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L586">FILE</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L587">FORMER</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L588">LOADIPHLPAPI</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L589">NODATA</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L590">NOMEM</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L591">NONAME</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L592">NOTFOUND</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L593">NOTIMP</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L594">NOTINITIALIZED</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L595">REFUSED</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L596">SERVFAIL</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L597">TIMEOUT</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L694">domain</a> <small>(module)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1313">duplexStreamOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L672">events</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L674">EventEmitter </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L678">exports</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L698">fs</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L863">constants</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L857">F_OK</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L728">FSWatcher </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L858">R_OK</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L732">ReadStream </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L699">Stats</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L859">W_OK</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L792">write</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L736">WriteStream </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L794">writeSync</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L860">X_OK</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1699">HeapSpaceStatistics</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1687">HeapStatistics</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L962">http</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L973">Agent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L974">ClientRequest </a> <small>(class)</small></li>
</ul></ul></div>
<div class="col-sm-4"><ul><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L975">IncomingMessage </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L990">METHODS</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L963">Server </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L976">ServerResponse </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L991">STATUS_CODES</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L994">https</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1004">Agent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1005">ClientRequest </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1006">IncomingMessage </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L995">Server </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1007">ServerResponse </a> <small>(class)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1081">net</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1083">Server </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1084">Socket </a> <small>(class)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1150">os</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1167">EOL</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1170">path</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1180">delimiter</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1195">posix</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1179">sep</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1196">win32</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1810">process</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1750">Process </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1199">punycode</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1204">ucs2</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1208">version </a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1211">querystring</a> <small>(module)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1270">readableStreamOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1252">readline</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1253">Interface </a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1721">repl</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1722">REPL_MODE_MAGIC</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1723">REPL_MODE_SLOPPY</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1724">REPL_MODE_STRICT</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1726">REPLServer </a> <small>(class)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1351">stream</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1354">Duplex </a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1356">PassThrough </a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1352">Readable </a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1355">Transform </a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1353">Writable </a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1383">string_decoder</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1384">StringDecoder </a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1411">tls</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1412">CLIENT_RENEG_LIMIT</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1413">CLIENT_RENEG_WINDOW</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1415">DEFAULT_CIPHERS</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1416">DEFAULT_ECDH_CURVE</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1422">SecureContext</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1424">Server</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1414">SLAB_BUFFER_SIZE</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1423">TLSSocket</a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1370">tty</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1373">ReadStream </a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1374">WriteStream </a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1431">url</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1482">URL</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1466">URLSearchParams</a> <small>(class)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1508">util</a> <small>(module)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1707">v8</a> <small>(module)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1543">vm</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1544">Script </a> <small>(var)</small></li></ul></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1286">writableStreamOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1574">zlib</a> <small>(module)</small><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1618">codes</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1646">deflate</a> <small>(var)</small></li>
</ul></ul></div>
<div class="col-sm-4"><ul><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1632">Deflate </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1650">deflateRaw</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1636">DeflateRaw </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1651">deflateRawSync</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1647">deflateSync</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1656">gunzip</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1635">Gunzip </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1657">gunzipSync</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1648">gzip</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1634">Gzip </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1649">gzipSync</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1654">inflate</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1633">Inflate </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1658">inflateRaw</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1637">InflateRaw </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1659">inflateRawSync</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1655">inflateSync</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1652">unzip</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1638">Unzip </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1653">unzipSync</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1602">Z_ASCII</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1593">Z_BEST_COMPRESSION</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1592">Z_BEST_SPEED</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1600">Z_BINARY</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1580">Z_BLOCK</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1589">Z_BUF_ERROR</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1587">Z_DATA_ERROR</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1606">Z_DEFAULT_CHUNK</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1594">Z_DEFAULT_COMPRESSION</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1607">Z_DEFAULT_LEVEL</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1608">Z_DEFAULT_MEMLEVEL</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1599">Z_DEFAULT_STRATEGY</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1609">Z_DEFAULT_WINDOWBITS</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1604">Z_DEFLATED</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1585">Z_ERRNO</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1595">Z_FILTERED</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1579">Z_FINISH</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1598">Z_FIXED</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1578">Z_FULL_FLUSH</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1596">Z_HUFFMAN_ONLY</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1610">Z_MAX_CHUNK</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1611">Z_MAX_LEVEL</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1612">Z_MAX_MEMLEVEL</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1613">Z_MAX_WINDOWBITS</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1588">Z_MEM_ERROR</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1614">Z_MIN_CHUNK</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1615">Z_MIN_LEVEL</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1616">Z_MIN_MEMLEVEL</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1617">Z_MIN_WINDOWBITS</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1584">Z_NEED_DICT</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1591">Z_NO_COMPRESSION</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1575">Z_NO_FLUSH</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1605">Z_NULL</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1582">Z_OK</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1576">Z_PARTIAL_FLUSH</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1597">Z_RLE</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1583">Z_STREAM_END</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1586">Z_STREAM_ERROR</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1577">Z_SYNC_FLUSH</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1601">Z_TEXT</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1581">Z_TREES</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1603">Z_UNKNOWN</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1590">Z_VERSION_ERROR</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1629">Zlib </a> <small>(class)</small></li></ul></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/node.js-private>Node.js "private" types</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1717">$SymbolReplModeMagic mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1718">$SymbolReplModeSloppy mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1719">$SymbolReplModeStrict mixins Symbol</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L26">buffer$Encoding</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L18">buffer$NonBufferEncoding</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L27">buffer$ToJSONRet</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L234">child_process$ChildProcess </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L137">child_process$Error </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L142">child_process$execCallback </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L169">child_process$execFileCallback </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L158">child_process$execFileOpts</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L171">child_process$execFileSyncOpts</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L125">child_process$execOpts</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L144">child_process$execSyncOpts</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L184">child_process$forkOpts</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L195">child_process$Handle</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L197">child_process$spawnOpts</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L208">child_process$spawnRet</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L218">child_process$spawnSyncOpts</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L232">child_process$spawnSyncRet</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L322">cluster$setupMasterOpts</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L306">cluster$Worker </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L355">crypto$Cipher </a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L353">crypto$createCredentialsDetails</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L384">crypto$Credentials</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L403">crypto$Decipher </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L388">crypto$DiffieHellman</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L432">crypto$Hash </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L439">crypto$Hmac </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L450">crypto$Sign </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L446">crypto$Sign$private_key</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L465">crypto$Verify </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L541">dgram$Socket </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L681">domain$Domain </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L652">events$EventEmitter</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L912">http$Agent</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L905">http$agentOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L937">http$ClientRequest </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L924">http$IncomingMessage </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L946">http$ServerResponse </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1071">net$connectOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1057">net$Server </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1024">net$Socket </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L539">net$Socket$address</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1114">os$CPU</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1126">os$NetIFAddr</a> <small>(type)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1134">os$UserInfo$buffer</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1142">os$UserInfo$string</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1237">readline$Interface </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1233">readline$InterfaceCompleter</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1713">repl$DefineCommandOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1318">stream$Duplex </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1349">stream$PassThrough </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1271">stream$Readable </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1268">stream$Stream </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1339">stream$Transform </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1287">stream$Writable </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1377">string_decoder$StringDecoder</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1401">tls$Server </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1387">tls$TLSSocket </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1359">tty$ReadStream </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1364">tty$WriteStream </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1501">util$InspectOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1541">vm$Context</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1531">vm$Script</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1521">vm$ScriptOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1568">zlib$asyncFn</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1553">zlib$options</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/node.js#L1563">zlib$syncFn</a> <small>(type)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/serviceworkers.js>Service Workers</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L143">Cache</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L136">CacheQueryOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L170">caches</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L159">CacheStorage</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L21">Client</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L51">ClientQueryOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L57">Clients</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L169">clients</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L50">ClientType</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L29">ExtendableEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L42">FetchEvent </a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L33">ForeignFetchOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L11">FrameType</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L38">InstallEvent </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L84">NavigationPreloadManager</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L79">NavigationPreloadState</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L173">onactivate</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L175">onfetch</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L176">onforeignfetch</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L174">oninstall</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L177">onmessage</a> <small>(var)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L171">registration</a> <small>(var)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L107">RegistrationOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L70">ServiceWorker </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L112">ServiceWorkerContainer </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L128">ServiceWorkerMessageEvent</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L91">ServiceWorkerRegistration </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L64">ServiceWorkerState</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L12">VisibilityState</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L14">WindowClient </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/serviceworkers.js#L105">WorkerType</a> <small>(type)</small></li>
</ul></div>
</div></div></div>
<div class="panel panel-default">
<div class="panel-heading"><h4 id=lib/streams.js>Streams</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L75">PipeToOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L81">QueuingStrategy </a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L41">ReadableByteStreamController </a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L87">ReadableStream</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L32">ReadableStreamBYOBRequest</a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L17">ReadableStreamController</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L51">ReadableStreamReader</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L11">TextEncodeOptions</a> <small>(type)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L13">TextEncoder</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L70">TransformStream</a> <small>(class)</small></li>
</ul></div>
<div class="col-sm-4"><ul>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L106">UnderlyingSink</a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L61">UnderlyingSource</a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L127">WritableStream</a> <small>(class)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L102">WritableStreamController</a> <small>(interface)</small></li>
<li><a href="https://github.com/facebook/flow/tree/v0.52.0/lib/streams.js#L116">WritableStreamWriter</a> <small>(interface)</small></li>
</ul></div>
</div></div></div>
</div>
Comparing vanilla React, Redux, and Redux Thunk - Chained modals exercise part 3
2016-05-06T23:55:22-07:00https://www.saltycrane.com/blog/2016/05/comparing-vanilla-react-redux-and-redux-thunk-chained-modals/<p>
This is a comparison of three methods for building a sequence of modals using:
1) <a href="https://facebook.github.io/react/">React</a> only
2) React+<a href="https://github.com/reactjs/redux">Redux</a> and
3) React+Redux+<a href="https://github.com/gaearon/redux-thunk">Redux Thunk</a>.
</p>
<p>In
<a href="/blog/2016/02/chained-modals-example-react-bootstrap/">part 1</a> and
<a href="/blog/2016/03/chained-modals-example-react-router-part-2/">part 2</a>,
I created a configurable sequence of modals using React and
React Router. Each modal had a form input. Clicking the "Next" button made an
AJAX request and, on success, advanced to the next modal. The user was able to
navigate between modals using the browser's history.
This example adds better management of the form input state. Form input state
can be pre-populated from the server and the input state is maintained when
navigating forward or backward between modals.
</p>
<ul>
<li><a href="#react-only">Solution 1: React only</a></li>
<li><a href="#redux">Solution 2: Redux</a></li>
<li><a href="#redux-thunk">Solution 3: Redux Thunk</a></li>
<li><a href="#references">References / Further Reading</a></li>
</ul>
<h4 id="react-only">React only</h4>
<p>
The first solution uses a parent component to manage
the state of the chained modals.
The full
<a href="https://github.com/saltycrane/react-chained-modals-comparison/tree/master/L3.methodB.react.router/src">code is here</a> and the
<a href="http://saltycrane.github.io/react-chained-modals-comparison/L3.methodB.react.router/">demo is here</a>.
</p>
<h5 id="react-App"><code>App.js</code></h5>
<p>
<code>RoutedApp</code> is the top level component which contains the routing configuration.
It is unchanged from
<a href="/blog/2016/03/chained-modals-example-react-router-part-2/">part 2</a>
except a <code>formData</code> prop is now passed in addition to
the <code>modalList</code> prop to allow pre-filling form data from the server.
Assume that "Servur" is data provided by the backend server.
</p>
<p>
This uses ES'15
<a href="/blog/2016/03/es6-features-used-react-development/#arrow-functions">
arrow functions</a>,
<a href="/blog/2016/03/es6-features-used-react-development/#destructuring-function-args">
argument destructuring</a> and
<a href="/blog/2016/03/es6-features-used-react-development/#rest-spread">
JSX spread</a>.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodB.react.router/src/containers/App.js">App.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">RoutedApp</span> = <span class="rainbow-delimiters-depth-1">()</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Router</span> <span class="web-mode-html-attr-name">history</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">{</span></span>hashHistory<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">}</span></span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">{</span></span>App<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">}</span></span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/"</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">{</span></span>
<span class="web-mode-function-call">partial</span><span class="rainbow-delimiters-depth-3">(</span>ChainedModals, <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-variable-name">modalList</span>: <span class="rainbow-delimiters-depth-5">[</span><span class="web-mode-javascript-string">'/name'</span>, <span class="web-mode-javascript-string">'/phone'</span>, <span class="web-mode-javascript-string">'/done'</span><span class="rainbow-delimiters-depth-5">]</span>,
<span class="web-mode-variable-name">formData</span>: <span class="rainbow-delimiters-depth-5">{</span><span class="web-mode-variable-name">name</span>: <span class="web-mode-javascript-string">'Servur'</span><span class="rainbow-delimiters-depth-5">}</span>
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">}</span></span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/name"</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">{</span></span>ModalName<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">}</span></span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/phone"</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">{</span></span>ModalPhone<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">}</span></span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">IndexRedirect</span> <span class="web-mode-html-attr-name">to</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/name"</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Route</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/done"</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Route</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Router</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">App</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span> children <span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">PageBehindModals</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="rainbow-delimiters-depth-2">{</span>children<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">partial</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="web-mode-variable-name">Comp</span>, <span class="web-mode-variable-name">props</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">(</span><span class="web-mode-variable-name">fprops</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Comp</span> <span class="rainbow-delimiters-depth-1">{</span><span class="web-mode-block-delimiter">...</span>props<span class="rainbow-delimiters-depth-1">}</span> <span class="rainbow-delimiters-depth-1">{</span><span class="web-mode-block-delimiter">...</span>fprops<span class="rainbow-delimiters-depth-1">}</span> <span class="web-mode-html-tag-bracket">/></span>;
</pre>
<h5 id="react-ChaineModals"><code>ChainedModals.js</code></h5>
<p>
<code>ChainedModals</code> manages two things: 1) form input state for all the
modals and 2) advancing to the next modal in the sequence.
To manage sequencing, the index of the current modal is stored in the component
state. Using React's lifecycle methods <code>componentWillMount</code> and
<code>componentWillReceiveProps</code> it determines the current index from
the route before rendering. When a modal's "Next" button is
clicked, it uses the current index to determine the next route and navigates to it.
</p>
<p>
<code>ChainedModals</code> also stores form data in its component
state and defines methods for each modal to store their form data
(<code>_storeName</code> and <code>_storePhone</code>).
One problem with this approach is that specific modal functionality is
defined in <code>ChainedModals</code> so it no longer works with an arbitrary list
of modals passed in <code>modalList</code> as it did in parts 1 and 2.
</p>
<p>
This uses ES'15
<a href="/blog/2016/03/es6-features-used-react-development/#nested-destructuring">
nested destructuring</a> and classes, and
ES'17 class properties.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodB.react.router/src/containers/ChainedModals.js">ChainedModals.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">ChainedModals</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">Component</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-function-call">constructor</span><span class="rainbow-delimiters-depth-2">(</span>props<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">super</span><span class="rainbow-delimiters-depth-3">(</span>props<span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> formData <span class="rainbow-delimiters-depth-3">}</span> = props;
<span class="web-mode-constant">this</span>.state = <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-variable-name">currIndex</span>: <span class="web-mode-constant">null</span>,
<span class="web-mode-variable-name">formData</span>: <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-variable-name">name</span>: <span class="web-mode-constant">null</span>,
<span class="web-mode-variable-name">phone</span>: <span class="web-mode-constant">null</span>,
...formData
<span class="rainbow-delimiters-depth-4">}</span>
<span class="rainbow-delimiters-depth-3">}</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-call">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> children <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex, formData <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">modalElement</span> = children && React.<span class="web-mode-function-call">cloneElement</span><span class="rainbow-delimiters-depth-3">(</span>children, <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-variable-name">step</span>: currIndex + 1,
<span class="web-mode-variable-name">formData</span>: formData,
<span class="web-mode-variable-name">storeName</span>: <span class="web-mode-constant">this</span>._storeName,
<span class="web-mode-variable-name">storePhone</span>: <span class="web-mode-constant">this</span>._storePhone,
<span class="web-mode-variable-name">gotoNext</span>: <span class="web-mode-constant">this</span>._gotoNext,
<span class="web-mode-variable-name">backdrop</span>: <span class="web-mode-constant">false</span>,
<span class="web-mode-variable-name">show</span>: <span class="web-mode-constant">true</span>
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">ModalBackdrop</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="rainbow-delimiters-depth-4">{</span>modalElement<span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-call">componentWillMount</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">_setIndexFromRoute</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-constant">this</span>.props<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-call">componentWillReceiveProps</span><span class="rainbow-delimiters-depth-2">(</span>nextProps<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">_setIndexFromRoute</span><span class="rainbow-delimiters-depth-3">(</span>nextProps<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-call">_setIndexFromRoute</span><span class="rainbow-delimiters-depth-2">(</span>props<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> modalList, <span class="web-mode-variable-name">location</span>: <span class="rainbow-delimiters-depth-4">{</span> pathname <span class="rainbow-delimiters-depth-4">}</span> <span class="rainbow-delimiters-depth-3">}</span> = props;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">index</span> = modalList.<span class="web-mode-function-call">findIndex</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-function-name">path</span> => path === pathname<span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-variable-name">currIndex</span>: index<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">_gotoNext</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> modalList <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">nextRoute</span> = modalList<span class="rainbow-delimiters-depth-3">[</span>currIndex + 1<span class="rainbow-delimiters-depth-3">]</span>;
hashHistory.<span class="web-mode-function-call">push</span><span class="rainbow-delimiters-depth-3">(</span>nextRoute<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="web-mode-function-name">_storeName</span> = <span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-variable-name">name</span><span class="rainbow-delimiters-depth-2">)</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-variable-name">formData</span>: <span class="rainbow-delimiters-depth-5">{</span>
...<span class="web-mode-constant">this</span>.state.formData,
<span class="web-mode-variable-name">name</span>: name
<span class="rainbow-delimiters-depth-5">}</span>
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="web-mode-function-name">_storePhone</span> = <span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-variable-name">phone</span><span class="rainbow-delimiters-depth-2">)</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-variable-name">formData</span>: <span class="rainbow-delimiters-depth-5">{</span>
...<span class="web-mode-constant">this</span>.state.formData,
<span class="web-mode-variable-name">phone</span>: phone
<span class="rainbow-delimiters-depth-5">}</span>
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h5 id="react-ModalName"><code>ModalName.js</code></h5>
<p>
<code>ModalName</code> changed to accept some form data from its parent
and it also still manages the AJAX request and the state related to it.
</p>
<p>
This uses ES'15
<a href="/blog/2016/03/es6-features-used-react-development/#destructuring">
destructuring</a>, classes, and promises, and ES'17
<a href="/blog/2016/03/es6-features-used-react-development/#rest-spread">
rest/spread</a> and class properties.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodB.react.router/src/components/ModalName.js">ModalName.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">ModalName</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">Component</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-function-call">constructor</span><span class="rainbow-delimiters-depth-2">(</span>props<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">super</span><span class="rainbow-delimiters-depth-3">(</span>props<span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> <span class="web-mode-variable-name">formData</span>: <span class="rainbow-delimiters-depth-4">{</span> name <span class="rainbow-delimiters-depth-4">}</span> <span class="rainbow-delimiters-depth-3">}</span> = props;
<span class="web-mode-constant">this</span>.state = <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-variable-name">name</span>: name || <span class="web-mode-javascript-string">''</span>,
<span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">false</span>,
<span class="web-mode-variable-name">errorMsg</span>: <span class="web-mode-constant">null</span>
<span class="rainbow-delimiters-depth-3">}</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-call">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> step, ...rest <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> name, isRequesting, errorMsg <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span> <span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-block-delimiter">...</span>rest<span class="rainbow-delimiters-depth-4">}</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Header <span class="web-mode-html-attr-name">closeButton</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Title<span class="web-mode-html-tag-bracket">></span>Step <span class="rainbow-delimiters-depth-4">{</span>step<span class="rainbow-delimiters-depth-4">}</span> - Name<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Title<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Header<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Body<span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-4">{</span>isRequesting && <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">><</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></span>Making fake ajax request...<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">></span><span class="rainbow-delimiters-depth-4">}</span>
<span class="rainbow-delimiters-depth-4">{</span>errorMsg && <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">><</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></span><span class="rainbow-delimiters-depth-5">{</span>errorMsg<span class="rainbow-delimiters-depth-5">}</span><span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">></span><span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Input</span>
<span class="web-mode-html-attr-name">label</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"Enter your name"</span>
<span class="web-mode-html-attr-name">type</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"text"</span>
<span class="web-mode-html-attr-name">bsSize</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"large"</span>
<span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-block-delimiter">...</span><span class="rainbow-delimiters-depth-5">(</span>errorMsg ? <span class="rainbow-delimiters-depth-6">{</span><span class="web-mode-variable-name">bsStyle</span>: <span class="web-mode-javascript-string">'error'</span><span class="rainbow-delimiters-depth-6">}</span> : <span class="rainbow-delimiters-depth-6">{}</span><span class="rainbow-delimiters-depth-5">)</span><span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-attr-name">value</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-4">{</span>name<span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-attr-name">onChange</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-constant">this</span>._handleInputChange<span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-attr-name">ref</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-4">{</span><span class="rainbow-delimiters-depth-5">(</span>c<span class="rainbow-delimiters-depth-5">)</span> => <span class="web-mode-constant">this</span>._input = c<span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Body<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Footer<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Button</span> <span class="web-mode-html-attr-name">bsStyle</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"primary"</span> <span class="web-mode-html-attr-name">onClick</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-constant">this</span>._handleClickNext<span class="rainbow-delimiters-depth-4">}</span><span class="web-mode-html-tag-bracket">></span>Next<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Button</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Footer<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">_handleInputChange</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-variable-name">name</span>: <span class="web-mode-constant">this</span>._input.<span class="web-mode-function-call">getValue</span><span class="rainbow-delimiters-depth-5">()</span><span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="web-mode-function-name">_handleClickNext</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> storeName, gotoNext <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">name</span> = <span class="web-mode-constant">this</span>._input.<span class="web-mode-function-call">getValue</span><span class="rainbow-delimiters-depth-3">()</span>;
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">true</span>, <span class="web-mode-variable-name">errorMsg</span>: <span class="web-mode-constant">null</span><span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-function-call">request</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-javascript-string">'/api/name'</span>, name<span class="rainbow-delimiters-depth-3">)</span>
.<span class="web-mode-function-call">then</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">()</span> => <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-function-call">storeName</span><span class="rainbow-delimiters-depth-5">(</span>name<span class="rainbow-delimiters-depth-5">)</span>;
<span class="web-mode-function-call">gotoNext</span><span class="rainbow-delimiters-depth-5">()</span>;
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>
.<span class="web-mode-keyword">catch</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">(</span>error<span class="rainbow-delimiters-depth-4">)</span> => <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-5">(</span><span class="rainbow-delimiters-depth-6">{</span><span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">false</span>, <span class="web-mode-variable-name">errorMsg</span>: error<span class="rainbow-delimiters-depth-6">}</span><span class="rainbow-delimiters-depth-5">)</span>;
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h4 id="redux">Redux</h4>
<p>
Redux provides a way to manage application state outside of components.
Examples of application state are the index of the current modal, the form
input value, and status of the AJAX request.
Using Redux to manage state makes React components more simple and reusable.
Redux also makes it easier to manage complex interactions that affect
multiple parts of the application.
</p>
<p>
Like React, data flows in one direction in Redux. Actions describe changes
to be made, then reducers make changes to the state based on the actions,
finally, the new state is passed to React components via props. Actions
are simple objects. Reducers are pure functions that accept a state and action
and return a new state. Because reducers are pure functions, they can be
composed of many smaller reducers that each operate on a smaller slice of
the state.
See the <a href="http://redux.js.org/docs/introduction/ThreePrinciples.html">
Three Principles</a> of Redux.
</p>
<p>
The
<a href="https://github.com/saltycrane/react-chained-modals-comparison/tree/master/L3.methodC.react.router.redux/src">code for the redux solution is here</a> and the
<a href="http://saltycrane.github.io/react-chained-modals-comparison/L3.methodC.react.router.redux/">demo is here</a>.
</p>
<h5 id="redux-App"><code>App.js</code></h5>
<p>
A Redux store is created to hold the application state.
The state
is initialized with <code>modalList</code> and <code>formData</code> that were previously
passed into the <code>ChainedModals</code> component.
The application element tree is wrapped with <code>Provider</code>
which makes the Redux store available to it's child components.
</p>
<p>
I also added an event handler which dispatches a Redux action whenever the
route changes. This idea was taken from this
<a href="https://github.com/reactjs/react-router-redux/issues/257">Redux issue</a>
and this related
<a href="https://github.com/reactjs/redux/pull/1414">Redux pull request</a>.
Note: not all imports are shown in the snippet. See all the imports in
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodC.react.router.redux/src/containers/App.js">App.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">import</span> <span class="rainbow-delimiters-depth-1">{</span> Provider <span class="rainbow-delimiters-depth-1">}</span> <span class="web-mode-keyword">from</span> <span class="web-mode-javascript-string">'react-redux'</span>;
<span class="web-mode-keyword">import</span> <span class="rainbow-delimiters-depth-1">{</span> createStore <span class="rainbow-delimiters-depth-1">}</span> <span class="web-mode-keyword">from</span> <span class="web-mode-javascript-string">'redux'</span>;
<span class="web-mode-keyword">import</span> <span class="rainbow-delimiters-depth-1">{</span> routeChanged <span class="rainbow-delimiters-depth-1">}</span> <span class="web-mode-keyword">from</span> <span class="web-mode-javascript-string">'../actions'</span>;
<span class="web-mode-keyword">import</span> reducer <span class="web-mode-keyword">from</span> <span class="web-mode-javascript-string">'../reducers'</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">initialState</span> = <span class="rainbow-delimiters-depth-1"><span class="ATTRLIST">{</span></span>
<span class="web-mode-variable-name">modalList</span>: <span class="rainbow-delimiters-depth-2">[</span>
<span class="web-mode-javascript-string">'/name'</span>,
<span class="web-mode-javascript-string">'/phone'</span>,
<span class="web-mode-javascript-string">'/done'</span>
<span class="rainbow-delimiters-depth-2">]</span>,
<span class="web-mode-variable-name">currIndex</span>: <span class="web-mode-constant">null</span>,
<span class="web-mode-variable-name">formData</span>: <span class="rainbow-delimiters-depth-2"><span class="ATTRLIST-1">{</span></span>
<span class="web-mode-variable-name">name</span>: <span class="web-mode-javascript-string">'Servur'</span>,
<span class="web-mode-variable-name">phone</span>: <span class="web-mode-constant">null</span>
<span class="rainbow-delimiters-depth-2"><span class="ATTRLIST-1">}</span></span>
<span class="rainbow-delimiters-depth-1"><span class="ATTRLIST">}</span></span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">store</span> = <span class="web-mode-function-call">createStore</span><span class="rainbow-delimiters-depth-1">(</span>reducer, initialState<span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-javascript-comment">// Dispatch an action when the route changes.</span>
hashHistory.<span class="web-mode-function-call">listen</span><span class="rainbow-delimiters-depth-1">(</span><span class="web-mode-function-name">location</span> => store.<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-function-call">routeChanged</span><span class="rainbow-delimiters-depth-3">(</span>location<span class="rainbow-delimiters-depth-3">)</span><span class="rainbow-delimiters-depth-2">)</span><span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">RoutedApp</span> = <span class="rainbow-delimiters-depth-1">()</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Provider</span> <span class="web-mode-html-attr-name">store</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>store<span class="rainbow-delimiters-depth-2">}</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Router</span> <span class="web-mode-html-attr-name">history</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>hashHistory<span class="rainbow-delimiters-depth-2">}</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>App<span class="rainbow-delimiters-depth-2">}</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/"</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>ChainedModals<span class="rainbow-delimiters-depth-2">}</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/name"</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>ModalName<span class="rainbow-delimiters-depth-2">}</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/phone"</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>ModalPhone<span class="rainbow-delimiters-depth-2">}</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">IndexRedirect</span> <span class="web-mode-html-attr-name">to</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/name"</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Route</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/done"</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Route</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Router</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Provider</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">App</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span> children <span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">PageBehindModals</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="rainbow-delimiters-depth-2">{</span>children<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<h5 id="redux-actions"><code>actions.js</code></h5>
<p>
I defined an action creator for when a route is changed and two for storing data from a user.
The action creators return the action which is a simple object that has a type and some
other simple data. The reducers will change the state based on the actions.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodC.react.router.redux/src/actions.js">actions.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">ROUTE_CHANGED</span> = <span class="web-mode-javascript-string">'ROUTE_CHANGED'</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">STORE_NAME</span> = <span class="web-mode-javascript-string">'STORE_NAME'</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">STORE_PHONE</span> = <span class="web-mode-javascript-string">'STORE_PHONE'</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">function</span> <span class="web-mode-function-name">routeChanged</span><span class="rainbow-delimiters-depth-1">(</span>location<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: ROUTE_CHANGED,
<span class="web-mode-variable-name">location</span>: location
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">function</span> <span class="web-mode-function-name">storeName</span><span class="rainbow-delimiters-depth-1">(</span>name<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: STORE_NAME,
<span class="web-mode-variable-name">name</span>: name
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">function</span> <span class="web-mode-function-name">storePhone</span><span class="rainbow-delimiters-depth-1">(</span>phone<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: STORE_PHONE,
<span class="web-mode-variable-name">phone</span>: phone
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h5 id="redux-reducers"><code>reducers.js</code></h5>
<p>
The <code>_sequencing</code> reducer sets the current index based on the route when the route
changes. Previously this was done in the <code>ChainedModals</code> component. The
<code>_formData</code>
reducer stores data (either name or phone) from the user in the state.
I wrap statements for each case in curly braces so that <code>const</code> and
<code>let</code> declarations will have a more reasonable scope. <em>Thanks Or!</em>
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodC.react.router.redux/src/reducers.js">reducers.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">modalsReducer</span><span class="rainbow-delimiters-depth-1">(</span>state, action<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
...<span class="web-mode-function-call">_sequencing</span><span class="rainbow-delimiters-depth-3">(</span>state, action<span class="rainbow-delimiters-depth-3">)</span>,
<span class="web-mode-variable-name">formData</span>: <span class="web-mode-function-call">_formData</span><span class="rainbow-delimiters-depth-3">(</span>state.formData, action<span class="rainbow-delimiters-depth-3">)</span>
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">_sequencing</span><span class="rainbow-delimiters-depth-1">(</span>state, action<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">switch</span> <span class="rainbow-delimiters-depth-2">(</span>action.type<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">ROUTE_CHANGED</span>: <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-4">{</span> <span class="web-mode-variable-name">location</span>: <span class="rainbow-delimiters-depth-5">{</span> pathname <span class="rainbow-delimiters-depth-5">}</span> <span class="rainbow-delimiters-depth-4">}</span> = action;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">index</span> = state.modalList.<span class="web-mode-function-call">findIndex</span><span class="rainbow-delimiters-depth-4">(</span><span class="web-mode-function-name">path</span> => path === pathname<span class="rainbow-delimiters-depth-4">)</span>;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-4">{</span>
...state,
<span class="web-mode-variable-name">currIndex</span>: index
<span class="rainbow-delimiters-depth-4">}</span>;
<span class="rainbow-delimiters-depth-3">}</span>
<span class="web-mode-keyword">default</span>:
<span class="web-mode-keyword">return</span> state;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">_formData</span><span class="rainbow-delimiters-depth-1">(</span>state, action<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">switch</span> <span class="rainbow-delimiters-depth-2">(</span>action.type<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">STORE_NAME</span>: <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-4">{</span>
...state,
<span class="web-mode-variable-name">name</span>: action.name
<span class="rainbow-delimiters-depth-4">}</span>
<span class="rainbow-delimiters-depth-3">}</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">STORE_PHONE</span>: <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-4">{</span>
...state,
<span class="web-mode-variable-name">phone</span>: action.phone
<span class="rainbow-delimiters-depth-4">}</span>
<span class="rainbow-delimiters-depth-3">}</span>
<span class="web-mode-keyword">default</span>:
<span class="web-mode-keyword">return</span> state;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h5 id="redux-ChainedModals"><code>ChainedModals.js</code></h5>
<p>The <code>ChainedModals</code> component is now connected to Redux.
Properties from the Redux state (<code>currIndex</code>, <code>modalList</code>, and
<code>formData</code>) are passed into the React component as props with the same name.
Similarly,
the Redux actions <code>storeName</code> and <code>storePhone</code> are passed in as
props with the same name.
A lot of the code to manage the form state and sequencing is now removed.
However it still defines a <code>_gotoNext</code> method which is used to navigate
to the next route.
Note in <code>render()</code>, I pull out the <code>children</code> and
<code>currIndex</code> props and pass the rest of
the props (e.g. <code>formData</code>, <code>storeName</code>, <code>gotoNext</code>)
onto the child modal using the ES'17
<a href="/blog/2016/03/es6-features-used-react-development/#rest-spread">
object rest/spread operators</a>.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodC.react.router.redux/src/containers/ChainedModals.js">ChainedModals.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">ChainedModals</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">Component</span> <span class="rainbow-delimiters-depth-1"><span class="ATTRLIST">{</span></span>
<span class="web-mode-function-call">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> children, currIndex, ...rest <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">modalElement</span> = children && React.<span class="web-mode-function-call">cloneElement</span><span class="rainbow-delimiters-depth-3">(</span>children, <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-variable-name">step</span>: currIndex + 1,
<span class="web-mode-variable-name">backdrop</span>: <span class="web-mode-constant">false</span>,
<span class="web-mode-variable-name">show</span>: <span class="web-mode-constant">true</span>,
<span class="web-mode-variable-name">gotoNext</span>: <span class="web-mode-constant">this</span>._gotoNext,
...rest
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">ModalBackdrop</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="rainbow-delimiters-depth-4">{</span>modalElement<span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">_gotoNext</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex, modalList <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">nextRoute</span> = modalList<span class="rainbow-delimiters-depth-3">[</span>currIndex + 1<span class="rainbow-delimiters-depth-3">]</span>;
hashHistory.<span class="web-mode-function-call">push</span><span class="rainbow-delimiters-depth-3">(</span>nextRoute<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1"><span class="ATTRLIST">}</span></span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">default</span> <span class="web-mode-function-call">connect</span><span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">mapStateToProps</span><span class="rainbow-delimiters-depth-2">(</span>state<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex, modalList, formData <span class="rainbow-delimiters-depth-3">}</span> = state;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex, modalList, formData <span class="rainbow-delimiters-depth-3">}</span>;
<span class="rainbow-delimiters-depth-2">}</span>,
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">mapDispatchToProps</span><span class="rainbow-delimiters-depth-2">(</span>dispatch<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-variable-name">storeName</span>: <span class="rainbow-delimiters-depth-4">(</span>...args<span class="rainbow-delimiters-depth-4">)</span> => <span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-4">(</span><span class="web-mode-function-call">storeName</span><span class="rainbow-delimiters-depth-5">(</span>...args<span class="rainbow-delimiters-depth-5">)</span><span class="rainbow-delimiters-depth-4">)</span>,
<span class="web-mode-variable-name">storePhone</span>: <span class="rainbow-delimiters-depth-4">(</span>...args<span class="rainbow-delimiters-depth-4">)</span> => <span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-4">(</span><span class="web-mode-function-call">storePhone</span><span class="rainbow-delimiters-depth-5">(</span>...args<span class="rainbow-delimiters-depth-5">)</span><span class="rainbow-delimiters-depth-4">)</span>
<span class="rainbow-delimiters-depth-3">}</span>
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">)(</span>ChainedModals<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<h5 id="redux-ModalName"><code>ModalName.js</code></h5>
<p>The individual modal components remain the same. They still make the
ajax calls and manage the state related to that.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodC.react.router.redux/src/components/ModalName.js">ModalName.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">ModalName</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">Component</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-function-call">constructor</span><span class="rainbow-delimiters-depth-2">(</span>props<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">super</span><span class="rainbow-delimiters-depth-3">(</span>props<span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> <span class="web-mode-variable-name">formData</span>: <span class="rainbow-delimiters-depth-4">{</span> name <span class="rainbow-delimiters-depth-4">}</span> <span class="rainbow-delimiters-depth-3">}</span> = props;
<span class="web-mode-constant">this</span>.state = <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-variable-name">name</span>: name || <span class="web-mode-javascript-string">''</span>,
<span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">false</span>,
<span class="web-mode-variable-name">errorMsg</span>: <span class="web-mode-constant">null</span>
<span class="rainbow-delimiters-depth-3">}</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-call">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> step, ...rest <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> name, isRequesting, errorMsg <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span> <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-block-delimiter">...</span>rest<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Header <span class="web-mode-html-attr-name">closeButton</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Title<span class="web-mode-html-tag-bracket">></span>Step <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>step<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span> - Name<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Title<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Header<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Body<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>isRequesting && <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">><</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></span>Making fake ajax request...<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">></span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Input</span>
<span class="web-mode-html-attr-name">label</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"Enter your name"</span>
<span class="web-mode-html-attr-name">type</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"text"</span>
<span class="web-mode-html-attr-name">bsSize</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"large"</span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-block-delimiter">...</span><span class="rainbow-delimiters-depth-5">(</span>errorMsg ? <span class="rainbow-delimiters-depth-6">{</span><span class="web-mode-variable-name">bsStyle</span>: <span class="web-mode-javascript-string">'error'</span><span class="rainbow-delimiters-depth-6">}</span> : <span class="rainbow-delimiters-depth-6">{}</span><span class="rainbow-delimiters-depth-5">)</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">help</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>errorMsg && <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-5">{</span></span>errorMsg<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-5">}</span></span><span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">value</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>name<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">onChange</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-constant">this</span>._handleInputChange<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">ref</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="rainbow-delimiters-depth-5">(</span>c<span class="rainbow-delimiters-depth-5">)</span> => <span class="web-mode-constant">this</span>._input = c<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Body<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Footer<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Button</span> <span class="web-mode-html-attr-name">bsStyle</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"primary"</span> <span class="web-mode-html-attr-name">onClick</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-constant">this</span>._handleClickNext<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span><span class="web-mode-html-tag-bracket">></span>Next<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Button</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Footer<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">_handleInputChange</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-variable-name">name</span>: <span class="web-mode-constant">this</span>._input.<span class="web-mode-function-call">getValue</span><span class="rainbow-delimiters-depth-5">()</span><span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="web-mode-function-name">_handleClickNext</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> storeName, gotoNext <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">name</span> = <span class="web-mode-constant">this</span>._input.<span class="web-mode-function-call">getValue</span><span class="rainbow-delimiters-depth-3">()</span>;
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">true</span>, <span class="web-mode-variable-name">errorMsg</span>: <span class="web-mode-constant">null</span><span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-function-call">request</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-javascript-string">'/api/name'</span>, name<span class="rainbow-delimiters-depth-3">)</span>
.<span class="web-mode-function-call">then</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">()</span> => <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-function-call">storeName</span><span class="rainbow-delimiters-depth-5">(</span>name<span class="rainbow-delimiters-depth-5">)</span>;
<span class="web-mode-function-call">gotoNext</span><span class="rainbow-delimiters-depth-5">()</span>;
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>
.<span class="web-mode-keyword">catch</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">(</span>error<span class="rainbow-delimiters-depth-4">)</span> => <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-5">(</span><span class="rainbow-delimiters-depth-6">{</span><span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">false</span>, <span class="web-mode-variable-name">errorMsg</span>: error<span class="rainbow-delimiters-depth-6">}</span><span class="rainbow-delimiters-depth-5">)</span>;
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h4 id="redux-thunk">Redux Thunk</h4>
<p>Using Redux added some boilerplate
but made React components simpler by moving their state to the Redux store.
However there is still some state and logic managed by the components.
Redux Thunk is middleware for Redux that enables creating actions with side effects such as
making asynchronous requests and changing the route.
Redux Thunk
<a href="http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559">
legitimizes the pattern of providing dispatch to actions</a>. See the
<a href="https://github.com/reactjs/redux/blob/master/docs/advanced/AsyncActions.md">
Async Actions Redux documentation</a> for detailed information about
using Redux Thunk for asynchronous actions.
Other alternatives for handling asynchronous actions are
<a href="http://redux.js.org/docs/advanced/Middleware.html">
writing custom Redux middleware</a> or using
<a href="https://github.com/yelouafi/redux-saga">Redux Saga</a>.
</p>
<p>
The
<a href="https://github.com/saltycrane/react-chained-modals-comparison/tree/master/L3.methodD.react.router.redux.thunk/src">code for the redux-thunk solution is here</a> and the
<a href="http://saltycrane.github.io/react-chained-modals-comparison/L3.methodD.react.router.redux.thunk/">demo is here</a>.
</p>
<h5 id="thunk-App"><code>App.js</code></h5>
<p>App.js is the same except I applied the redux thunk middleware.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodD.react.router.redux.thunk/src/containers/App.js">App.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">import</span> <span class="rainbow-delimiters-depth-1">{</span> createStore, applyMiddleware <span class="rainbow-delimiters-depth-1">}</span> <span class="web-mode-keyword">from</span> <span class="web-mode-javascript-string">'redux'</span>;
<span class="web-mode-keyword">import</span> thunk <span class="web-mode-keyword">from</span> <span class="web-mode-javascript-string">'redux-thunk'</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">store</span> = <span class="web-mode-function-call">createStore</span><span class="rainbow-delimiters-depth-1">(</span>reducer, initialState, <span class="web-mode-function-call">applyMiddleware</span><span class="rainbow-delimiters-depth-2">(</span>thunk<span class="rainbow-delimiters-depth-2">)</span><span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<h5 id="thunk-actions"><code>actions.js</code></h5>
<p>In actions.js I created three action creators with side effects: <code>storeName</code> and
<code>storePhone</code> make asynchronous requests <code>gotoNext</code> changes the route.
Note: though I used <code>getState</code> in my action creators, in general this is considered an
<a href="http://stackoverflow.com/questions/35667249/accessing-redux-state-in-an-action-creator/35674575#35674575">anti-pattern</a>.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodD.react.router.redux.thunk/src/actions.js">actions.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">ROUTE_CHANGED</span> = <span class="web-mode-javascript-string">'ROUTE_CHANGED'</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">STORE_NAME_REQUESTED</span> = <span class="web-mode-javascript-string">'STORE_NAME_REQUESTED'</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">STORE_NAME_SUCCEEDED</span> = <span class="web-mode-javascript-string">'STORE_NAME_SUCCEEDED'</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">STORE_NAME_FAILED</span> = <span class="web-mode-javascript-string">'STORE_NAME_FAILED'</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">STORE_PHONE_REQUESTED</span> = <span class="web-mode-javascript-string">'STORE_PHONE_REQUESTED'</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">STORE_PHONE_SUCCEEDED</span> = <span class="web-mode-javascript-string">'STORE_PHONE_SUCCEEDED'</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">STORE_PHONE_FAILED</span> = <span class="web-mode-javascript-string">'STORE_PHONE_FAILED'</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">function</span> <span class="web-mode-function-name">routeChanged</span><span class="rainbow-delimiters-depth-1">(</span>location<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: ROUTE_CHANGED,
<span class="web-mode-variable-name">location</span>: location
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">function</span> <span class="web-mode-function-name">gotoNext</span><span class="rainbow-delimiters-depth-1">()</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">(</span>dispatch, getState<span class="rainbow-delimiters-depth-2">)</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex, modalList <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-function-call">getState</span><span class="rainbow-delimiters-depth-3">()</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">nextRoute</span> = modalList<span class="rainbow-delimiters-depth-3">[</span>currIndex + 1<span class="rainbow-delimiters-depth-3">]</span>;
hashHistory.<span class="web-mode-function-call">push</span><span class="rainbow-delimiters-depth-3">(</span>nextRoute<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">function</span> <span class="web-mode-function-name">storeName</span><span class="rainbow-delimiters-depth-1">(</span>name, onSuccess<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="web-mode-variable-name">dispatch</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-function-call">_storeNameRequested</span><span class="rainbow-delimiters-depth-4">()</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-keyword">return</span> <span class="web-mode-function-call">request</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-javascript-string">'/api/name'</span>, name<span class="rainbow-delimiters-depth-3">)</span>
.<span class="web-mode-function-call">then</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">()</span> => <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-5">(</span><span class="web-mode-function-call">_storeNameSucceeded</span><span class="rainbow-delimiters-depth-6">(</span>name<span class="rainbow-delimiters-depth-6">)</span><span class="rainbow-delimiters-depth-5">)</span>;
<span class="web-mode-function-call">onSuccess</span><span class="rainbow-delimiters-depth-5">()</span>;
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>
.<span class="web-mode-keyword">catch</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-function-name">error</span> => <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-5">(</span><span class="web-mode-function-call">_storeNameFailed</span><span class="rainbow-delimiters-depth-6">(</span>error<span class="rainbow-delimiters-depth-6">)</span><span class="rainbow-delimiters-depth-5">)</span>;
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">function</span> <span class="web-mode-function-name">storePhone</span><span class="rainbow-delimiters-depth-1">(</span>phone, onSuccess<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="web-mode-variable-name">dispatch</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-function-call">_storePhoneRequested</span><span class="rainbow-delimiters-depth-4">()</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-keyword">return</span> <span class="web-mode-function-call">request</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-javascript-string">'/api/phone'</span>, phone<span class="rainbow-delimiters-depth-3">)</span>
.<span class="web-mode-function-call">then</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">()</span> => <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-5">(</span><span class="web-mode-function-call">_storePhoneSucceeded</span><span class="rainbow-delimiters-depth-6">(</span>phone<span class="rainbow-delimiters-depth-6">)</span><span class="rainbow-delimiters-depth-5">)</span>;
<span class="web-mode-function-call">onSuccess</span><span class="rainbow-delimiters-depth-5">()</span>;
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>
.<span class="web-mode-keyword">catch</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-function-name">error</span> => <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-5">(</span><span class="web-mode-function-call">_storePhoneFailed</span><span class="rainbow-delimiters-depth-6">(</span>error<span class="rainbow-delimiters-depth-6">)</span><span class="rainbow-delimiters-depth-5">)</span>;
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">_storeNameRequested</span><span class="rainbow-delimiters-depth-1">()</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: STORE_NAME_REQUESTED
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">_storeNameSucceeded</span><span class="rainbow-delimiters-depth-1">(</span>name<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: STORE_NAME_SUCCEEDED,
<span class="web-mode-variable-name">name</span>: name
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">_storeNameFailed</span><span class="rainbow-delimiters-depth-1">(</span>errorMsg<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: STORE_NAME_FAILED,
<span class="web-mode-variable-name">errorMsg</span>: errorMsg
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">_storePhoneRequested</span><span class="rainbow-delimiters-depth-1">()</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: STORE_PHONE_REQUESTED
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">_storePhoneSucceeded</span><span class="rainbow-delimiters-depth-1">(</span>phone<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: STORE_PHONE_SUCCEEDED,
<span class="web-mode-variable-name">phone</span>: phone
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">_storePhoneFailed</span><span class="rainbow-delimiters-depth-1">(</span>errorMsg<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">type</span>: STORE_PHONE_FAILED,
<span class="web-mode-variable-name">errorMsg</span>: errorMsg
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h5 id="thunk-reducers"><code>reducers.js</code></h5>
<p>reducers.js now updates some state based on the status of the API request
which is used by the modal components to show the spinner or validation errors.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodD.react.router.redux.thunk/src/reducers.js">reducers.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">modalsReducer</span><span class="rainbow-delimiters-depth-1">(</span>state, action<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span>
...<span class="web-mode-function-call">_sequencing</span><span class="rainbow-delimiters-depth-3">(</span>state, action<span class="rainbow-delimiters-depth-3">)</span>,
<span class="web-mode-variable-name">formData</span>: <span class="web-mode-function-call">_formData</span><span class="rainbow-delimiters-depth-3">(</span>state.formData, action<span class="rainbow-delimiters-depth-3">)</span>
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">_sequencing</span><span class="rainbow-delimiters-depth-1">(</span>state, action<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">switch</span> <span class="rainbow-delimiters-depth-2">(</span>action.type<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">ROUTE_CHANGED</span>: <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-4">{</span> <span class="web-mode-variable-name">location</span>: <span class="rainbow-delimiters-depth-5">{</span> pathname <span class="rainbow-delimiters-depth-5">}</span> <span class="rainbow-delimiters-depth-4">}</span> = action;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">index</span> = state.modalList.<span class="web-mode-function-call">findIndex</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-function-name">path</span> => path === pathname<span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-4">{</span>
...state,
<span class="web-mode-variable-name">requestStatus</span>: <span class="web-mode-constant">null</span>,
<span class="web-mode-variable-name">currIndex</span>: index
<span class="rainbow-delimiters-depth-3">}</span>;
<span class="rainbow-delimiters-depth-3">}</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">STORE_NAME_REQUESTED</span>:
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">STORE_PHONE_REQUESTED</span>: <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-4">{</span>
...state,
<span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">true</span>,
<span class="web-mode-variable-name">errorMsg</span>: <span class="web-mode-constant">null</span>
<span class="rainbow-delimiters-depth-4">}</span>
<span class="rainbow-delimiters-depth-3">}</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">STORE_NAME_SUCCEEDED</span>:
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">STORE_PHONE_SUCCEEDED</span>: <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-4">{</span>
...state,
<span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">false</span>,
<span class="web-mode-variable-name">errorMsg</span>: <span class="web-mode-constant">null</span>
<span class="rainbow-delimiters-depth-4">}</span>
<span class="rainbow-delimiters-depth-3">}</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">STORE_NAME_FAILED</span>:
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">STORE_PHONE_FAILED</span>: <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-4">{</span>
...state,
<span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">false</span>,
<span class="web-mode-variable-name">errorMsg</span>: action.errorMsg
<span class="rainbow-delimiters-depth-4">}</span>
<span class="rainbow-delimiters-depth-3">}</span>
<span class="web-mode-keyword">default</span>:
<span class="web-mode-keyword">return</span> state;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">_formData</span><span class="rainbow-delimiters-depth-1">(</span>state, action<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">switch</span> <span class="rainbow-delimiters-depth-2">(</span>action.type<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">STORE_NAME_SUCCEEDED</span>: <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-4">{</span>
...state,
<span class="web-mode-variable-name">name</span>: action.name
<span class="rainbow-delimiters-depth-3">}</span>
<span class="rainbow-delimiters-depth-3">}</span>
<span class="web-mode-keyword">case</span> <span class="web-mode-variable-name">STORE_PHONE_SUCCEEDED</span>: <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-4">{</span>
...state,
<span class="web-mode-variable-name">phone</span>: action.phone
<span class="rainbow-delimiters-depth-3">}</span>
<span class="rainbow-delimiters-depth-3">}</span>
<span class="web-mode-keyword">default</span>:
<span class="web-mode-keyword">return</span> state;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h5 id="thunk-ChainedModals"><code>ChainedModals.js</code></h5>
<p>ChainedModals is now a simpler functional component.
It's purpose is connecting child modal components to redux
and setting some default props for the modals. It is also used to
display the backdrop behind the modals.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodD.react.router.redux.thunk/src/containers/ChainedModals.js">ChainedModals.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-function-name">ChainedModals</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span> <span class="web-mode-variable-name">children</span>, ...rest <span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">modalElement</span> = children && React.<span class="web-mode-function-call">cloneElement</span><span class="rainbow-delimiters-depth-2">(</span>children, rest<span class="rainbow-delimiters-depth-2">)</span>;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">ModalBackdrop</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span>modalElement<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-2">)</span>;
<span class="rainbow-delimiters-depth-1">}</span>;
<span class="web-mode-keyword">export</span> <span class="web-mode-keyword">default</span> <span class="web-mode-function-call">connect</span><span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">mapStateToProps</span><span class="rainbow-delimiters-depth-2">(</span>state<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex, isRequesting, errorMsg, formData <span class="rainbow-delimiters-depth-3">}</span> = state;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-variable-name">backdrop</span>: <span class="web-mode-constant">false</span>,
<span class="web-mode-variable-name">show</span>: <span class="web-mode-constant">true</span>,
<span class="web-mode-variable-name">step</span>: currIndex + 1,
isRequesting,
errorMsg,
formData
<span class="rainbow-delimiters-depth-3">}</span>;
<span class="rainbow-delimiters-depth-2">}</span>,
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">mapDispatchToProps</span><span class="rainbow-delimiters-depth-2">(</span>dispatch<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-variable-name">gotoNext</span>: <span class="rainbow-delimiters-depth-4">(</span>...args<span class="rainbow-delimiters-depth-4">)</span> => <span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-4">(</span><span class="web-mode-function-call">gotoNext</span><span class="rainbow-delimiters-depth-5">(</span>...args<span class="rainbow-delimiters-depth-5">)</span><span class="rainbow-delimiters-depth-4">)</span>,
<span class="web-mode-variable-name">storeName</span>: <span class="rainbow-delimiters-depth-4">(</span>...args<span class="rainbow-delimiters-depth-4">)</span> => <span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-4">(</span><span class="web-mode-function-call">storeName</span><span class="rainbow-delimiters-depth-5">(</span>...args<span class="rainbow-delimiters-depth-5">)</span><span class="rainbow-delimiters-depth-4">)</span>,
<span class="web-mode-variable-name">storePhone</span>: <span class="rainbow-delimiters-depth-4">(</span>...args<span class="rainbow-delimiters-depth-4">)</span> => <span class="web-mode-function-call">dispatch</span><span class="rainbow-delimiters-depth-4">(</span><span class="web-mode-function-call">storePhone</span><span class="rainbow-delimiters-depth-5">(</span>...args<span class="rainbow-delimiters-depth-5">)</span><span class="rainbow-delimiters-depth-4">)</span>
<span class="rainbow-delimiters-depth-3">}</span>
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">)(</span>ChainedModals<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<h5 id="thunk-ModalName"><code>ModalName.js</code></h5>
<p>Individual modal components now only use state for
<a href="https://facebook.github.io/react/docs/forms.html#controlled-components">
controlled inputs</a>.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L3.methodD.react.router.redux.thunk/src/components/ModalName.js">ModalName.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">ModalName</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">Component</span> <span class="rainbow-delimiters-depth-1"><span class="ATTRLIST">{</span></span>
<span class="web-mode-function-call">constructor</span><span class="rainbow-delimiters-depth-2">(</span>props<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">super</span><span class="rainbow-delimiters-depth-3">(</span>props<span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> <span class="web-mode-variable-name">formData</span>: <span class="rainbow-delimiters-depth-4">{</span> name <span class="rainbow-delimiters-depth-4">}</span> <span class="rainbow-delimiters-depth-3">}</span> = props;
<span class="web-mode-constant">this</span>.state = <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-variable-name">name</span>: name || <span class="web-mode-javascript-string">''</span>
<span class="rainbow-delimiters-depth-3">}</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-call">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2"><span class="ATTRLIST-1">{</span></span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> step, isRequesting, errorMsg, ...rest <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> name <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3"><span class="ATTRLIST-2">(</span></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span> <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-block-delimiter">...</span>rest<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Header <span class="web-mode-html-attr-name">closeButton</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Title<span class="web-mode-html-tag-bracket">></span>Step <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>step<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span> - Name<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Title<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Header<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Body<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>isRequesting && <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">><</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></span>Making fake ajax request...<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">></span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Input</span>
<span class="web-mode-html-attr-name">label</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"Enter your name"</span>
<span class="web-mode-html-attr-name">type</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"text"</span>
<span class="web-mode-html-attr-name">bsSize</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"large"</span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-block-delimiter">...</span><span class="rainbow-delimiters-depth-5">(</span>errorMsg ? <span class="rainbow-delimiters-depth-6">{</span><span class="web-mode-variable-name">bsStyle</span>: <span class="web-mode-javascript-string">'error'</span><span class="rainbow-delimiters-depth-6">}</span> : <span class="rainbow-delimiters-depth-6">{}</span><span class="rainbow-delimiters-depth-5">)</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">help</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>errorMsg && <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-5">{</span></span>errorMsg<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-5">}</span></span><span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">value</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>name<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">onChange</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-constant">this</span>._handleInputChange<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">ref</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="rainbow-delimiters-depth-5">(</span>c<span class="rainbow-delimiters-depth-5">)</span> => <span class="web-mode-constant">this</span>._input = c<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Body<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Footer<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Button</span> <span class="web-mode-html-attr-name">bsStyle</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"primary"</span> <span class="web-mode-html-attr-name">onClick</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-constant">this</span>._handleClickNext<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span><span class="web-mode-html-tag-bracket">></span>Next<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Button</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Footer<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-3"><span class="ATTRLIST-2">)</span></span>;
<span class="rainbow-delimiters-depth-2"><span class="ATTRLIST-1">}</span></span>
<span class="web-mode-function-name">_handleInputChange</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-variable-name">name</span>: <span class="web-mode-constant">this</span>._input.<span class="web-mode-function-call">getValue</span><span class="rainbow-delimiters-depth-5">()</span><span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="web-mode-function-name">_handleClickNext</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> storeName, gotoNext <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">name</span> = <span class="web-mode-constant">this</span>._input.<span class="web-mode-function-call">getValue</span><span class="rainbow-delimiters-depth-3">()</span>;
<span class="web-mode-function-call">storeName</span><span class="rainbow-delimiters-depth-3">(</span>name, gotoNext<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1"><span class="ATTRLIST">}</span></span>
</pre>
<h4 id="references">
References / Further Reading
<small>(mostly from Redux author, Dan Abramov)</small>
</h4>
<ul>
<li>
<a href="https://egghead.io/courses/getting-started-with-redux">Dan Abramov's first (free) Egghead Redux course</a>
</li>
<li>
<a href="https://egghead.io/courses/building-react-applications-with-idiomatic-redux">Dan Abramov's second (free) Egghead Redux course</a>
</li>
<li>
<a href="http://redux.js.org/docs/FAQ.html">Redux FAQ</a>
</li>
<li><a href="http://stackoverflow.com/questions/36631761/when-should-i-add-redux-to-a-react-app">
When should I add Redux to a React app?
</a></li>
<li><a href="https://www.safaribooksonline.com/blog/2015/10/29/react-local-component-state/">
Why Local Component State is a Trap - Richard Feldman
</a></li>
<li><a href="https://twitter.com/dan_abramov/status/725089243836588032">
Good thread. Don’t use Redux unless you *tried* local component state and were dissatisfied.
- Dan Abramov on Twitter
</a></li>
<li><a href="http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559">
How to dispatch a Redux action with a timeout? <em>(how Redux Thunk works)</em>
</a></li>
<li><a href="http://stackoverflow.com/questions/35623656/how-can-i-display-a-modal-dialog-in-redux-that-performs-asynchronous-actions/35641680#35641680">
How can I display a modal dialog in Redux that performs asynchronous actions?
</a></li>
<li><a href="http://stackoverflow.com/questions/34098125/how-to-divide-the-logic-between-redux-reducers-and-action-creators/34135166#34135166">
How to divide the logic between Redux reducers and action creators?
<em>(ensure reducers are pure)</em>
</a></li>
<li><a href="http://stackoverflow.com/questions/35406707/do-events-and-actions-have-a-11-relationship-in-redux/35410524#35410524">
Do events and actions have a 1:1 relationship in Redux?
</a></li>
<li><a href="http://stackoverflow.com/questions/32846337/how-to-fetch-the-new-data-in-response-to-react-router-change-with-redux/32922868#32922868">
How to fetch the new data in response to React Router change with Redux?
</a></li>
<li><a href="http://stackoverflow.com/questions/32612418/transition-to-another-route-on-successful-async-redux-action/32922381#32922381">
Transition to another route on successful async redux action
</a></li>
<li><a href="http://stackoverflow.com/questions/35456935/how-to-focus-a-textarea-in-react-redux-app-while-fetching-the-data-events-or-st/35643128#35643128">
How to show a loading indicator in React Redux app while fetching the data?
</a></li>
<li><a href="http://stackoverflow.com/questions/35493352/can-i-dispatch-multiple-actions-without-redux-thunk-middleware/35642783#35642783">
Can I dispatch multiple actions without Redux Thunk middleware?
</a></li>
<li><a href="http://stackoverflow.com/questions/33924429/what-are-your-best-practices-for-preloading-initialstate-in-your-redux-apps/33924707#33924707">
What are your best practices for preloading initialState in your Redux apps?
</a></li>
<li><a href="http://stackoverflow.com/questions/33637740/should-i-use-one-or-several-action-types-to-represent-this-async-action/33816695#33816695">
Should I use one or several action types to represent this async action?
</a></li>
<li><a href="http://stackoverflow.com/questions/35665724/with-react-redux-router-how-should-i-access-the-state-of-the-route/35674710#35674710">
With React Redux Router, how should I access the state of the route?
</a></li>
<li><a href="http://stackoverflow.com/questions/35667249/accessing-redux-state-in-an-action-creator/35674575#35674575">
Accessing Redux state in an action creator?
</a></li>
<li><a href="http://stackoverflow.com/questions/35706835/react-router-redirect-after-action-redux/35723458#35723458">
React router redirect after action redux
</a></li>
<li><a href="https://github.com/reactjs/redux/issues/1390">Component Loading (w/ Redux
and React Router) - Github issue #1390
</a></li>
</ul>
Some ES6+ features used in React development
2016-03-26T09:07:54-07:00https://www.saltycrane.com/blog/2016/03/es6-features-used-react-development/<p>
The newest versions of JavaScript,
<a href="https://github.com/lukehoban/es6features">ES2015 (ES6)</a>,
<a href="http://2ality.com/2016/01/ecmascript-2016.html">ES2016 (ES7)</a>,
<a href="http://2ality.com/2016/02/ecmascript-2017.html">ES2017</a> and
<a href="https://github.com/tc39/proposals/blob/master/README.md">beyond</a>
have many features that can be used today via
<a href="https://babeljs.io/">Babel</a>.
Here are a few features I've used in React development.
</p>
<h4 id="arrow-functions">Arrow functions <small>(ES2015)</small></h4>
<p>
Arrow functions provide a shorthand syntax for defining functions.
They also do not bind a <code>this</code> context so <code>this</code>
from the lexical scope is used instead.
If no curly braces are used, the value after the arrow is returned.
There are other subtleties (e.g.
<a href="http://adripofjavascript.com/blog/drips/variable-and-function-hoisting">hoisting</a>
and <a href="https://kangax.github.io/nfe/">naming</a>)
associated with
<a href="https://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/">function expressions vs. function declarations</a>.
I like using arrow functions in blog posts for brevity, but I haven't decided
if I like them in all circumstances yet. More information:
<a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch2.md#arrow-functions">Arrow Functions - YDKJS: ES6 & Beyond</a>,
<a href="http://exploringjs.com/es6/ch_arrow-functions.html">
Arrow functions - Exploring ES6</a>, and
<a href="https://blog.getify.com/arrow-this/">
Arrow This | getiblog</a> for an explanation about <code>this</code>.
</p>
<p>Here is a stateless React component defined using an arrow function:</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">App</span> = <span class="rainbow-delimiters-depth-1">()</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">PageBehindModals</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">ChainedModals</span> <span class="web-mode-html-attr-name">modalList</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">{</span></span><span class="rainbow-delimiters-depth-3">[</span>ModalName, ModalPhone<span class="rainbow-delimiters-depth-3">]</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-2">}</span></span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<p>Without arrow functions, it could be written as:</p>
<pre class="htmlize">
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">App</span><span class="rainbow-delimiters-depth-1">()</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">PageBehindModals</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">ChainedModals</span> <span class="web-mode-html-attr-name">modalList</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span><span class="rainbow-delimiters-depth-4">[</span>ModalName, ModalPhone<span class="rainbow-delimiters-depth-4">]</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-2">)</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h4 id="destructuring">Destructuring <small>(ES2015)</small></h4>
<p>
The shorthand destructuring shown assigns properties of an object to variables
of the same name. There is also a longhand syntax that allows you to assign to
variables of different names. Destructuring works with nested objects, with arrays,
and can be used in variable declarations, function return values and function
arguments.
More information:
<a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch2.md#destructuring">
Destructuring - YDKJS: ES6 & Beyond</a>
</p>
<p>Here is an example destructuring the objects <code>this.props</code> and <code>this.state</code>:
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">ChainedModals</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">Component</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-function-call">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> modalList <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex, showModal <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-javascript-comment">// ..</span>
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<p>Without destructuring, it could be written as:</p>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">ChainedModals</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">Component</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-function-call">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">modalList</span> = <span class="web-mode-constant">this</span>.props.modalList;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">currIndex</span> = <span class="web-mode-constant">this</span>.state.currIndex;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">showModal</span> = <span class="web-mode-constant">this</span>.state.showModal;
<span class="web-mode-javascript-comment">// ..</span>
<span class="rainbow-delimiters-depth-2">}</span>
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h4 id="destructuring-function-args">Destructuring function arguments <small>(ES2015)</small></h4>
<p>
Destructuring can be applied to function arguments that are objects or arrays.
More information:
<a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch2.md#destructuring-parameters">
Destructuring Parameters - YDKJS: ES6 & Beyond</a>
</p>
<p>This function expects a single object as an argument and it is destructured into
<code>onClickNext</code> and <code>step</code>.
</p>
<pre class="htmlize">
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">ModalName</span><span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span> onClickNext, step <span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">h1</span><span class="web-mode-html-tag-bracket">></span>Step <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span>step<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span> - Name<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">h1</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Button</span> <span class="web-mode-html-attr-name">onClick</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span>onClickNext<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span><span class="web-mode-html-tag-bracket">></span>Next<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Button</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-2">)</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<p>Without destructuring, it could be written as:</p>
<pre class="htmlize">
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">ModalName</span><span class="rainbow-delimiters-depth-1">(</span>props<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">onClickNext</span> = props.onClickNext;
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">step</span> = props.step;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">h1</span><span class="web-mode-html-tag-bracket">></span>Step <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span>step<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span> - Name<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">h1</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Button</span> <span class="web-mode-html-attr-name">onClick</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span>onClickNext<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span><span class="web-mode-html-tag-bracket">></span>Next<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Button</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-2">)</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h4 id="nested-destructuring">Nested destructuring <small>(ES2015)</small></h4>
<p>
Destructuring also applies to objects nested in objects.
More information:
<a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch2.md#nested-destructuring">
Nested Destructuring - YDKJS: ES6 & Beyond</a>
</p>
<p>Here is an example destructuring the nested <code>props</code> object:</p>
<pre class="htmlize">
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">setIndexFromRoute</span><span class="rainbow-delimiters-depth-1">(</span>props<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-2">{</span> modalList, <span class="web-mode-variable-name">location</span>: <span class="rainbow-delimiters-depth-3">{</span> pathname <span class="rainbow-delimiters-depth-3">}</span> <span class="rainbow-delimiters-depth-2">}</span> = props;
<span class="web-mode-javascript-comment">// ..</span>
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<p>Without destructuring, it could be written as:</p>
<pre class="htmlize">
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">setIndexFromRoute</span><span class="rainbow-delimiters-depth-1">(</span>props<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">modalList</span> = props.modalList;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">pathname</span> = props.location.pathname;
<span class="web-mode-javascript-comment">// ..</span>
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h4 id="rest-spread">Object rest/spread operator <small>(ES2018)</small></h4>
<p>
The <code>...</code> rest operator gathers the <em>rest</em> of the items in
the props object argument and puts them in the variable <code>rest</code>.
The <code>...</code> in the JSX is actually
<a href="https://facebook.github.io/react/docs/jsx-spread.html">
JSX syntax</a> for spreading
the props in the the <code>rest</code> object into individual props.
More information:
<a href="https://github.com/sebmarkbage/ecmascript-rest-spread">
Object Rest/Spread Properties ECMAScript proposal</a>,
<a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch8.md#objects-properties-and-">
Objects Properties and ... - YDKJS: ES6 & Beyond</a>,
<a href="http://redux.js.org/docs/recipes/UsingObjectSpreadOperator.html">
Using Object Spread Operator - Redux documentation</a>, and
<a href="https://facebook.github.io/react/docs/jsx-spread.html">
JSX Spread Attributes - React documentation</a>.
</p>
<p>
The object rest/spread operator is currently at stage 3 in the approval process
so the earliest release would be ES2018. Note there is a
<a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch2.md#spreadrest">rest/spread operator for <em>arrays</em></a> in ES2015.
</p>
<pre class="htmlize">
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">ModalName</span><span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span> onClick, ...rest <span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span> <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span><span class="web-mode-block-delimiter">...</span>rest<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Button</span> <span class="web-mode-html-attr-name">onClick</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span>onClick<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span><span class="web-mode-html-tag-bracket">></span>Next<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Button</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-2">)</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<p>
If only <code>onClick</code>, <code>show</code>, and <code>backdrop</code> props
are passed to <code>ModalName</code>, it could be written like this. Using the rest
and spread operators are useful when the properties are variable or unknown.
</p>
<pre class="htmlize">
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">ModalName</span><span class="rainbow-delimiters-depth-1">(</span>props<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">onClick</span> = props.onClick;
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">show</span> = props.show;
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">backdrop</span> = props.backdrop;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span> <span class="web-mode-html-attr-name">show</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span>show<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span> <span class="web-mode-html-attr-name">backdrop</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span>backdrop<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Button</span> <span class="web-mode-html-attr-name">onClick</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">{</span></span>onClick<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-3">}</span></span><span class="web-mode-html-tag-bracket">></span>Next<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Button</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-2">)</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h5>Object spread example</h5>
<p>Here is an example using the object spread operator to merge 2 objects into a new object:</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">initialState</span> = <span class="rainbow-delimiters-depth-1"><span class="custom">{</span></span>
<span class="web-mode-variable-name">modalList</span>: <span class="rainbow-delimiters-depth-2">[</span><span class="web-mode-javascript-string">"/name"</span>, <span class="web-mode-javascript-string">"/phone"</span>, <span class="web-mode-javascript-string">"/done"</span><span class="rainbow-delimiters-depth-2">]</span>,
<span class="web-mode-variable-name">currIndex</span>: <span class="web-mode-constant">null</span>,
<span class="web-mode-variable-name">formData</span>: <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">name</span>: <span class="web-mode-javascript-string">"Servur"</span>,
<span class="web-mode-variable-name">phone</span>: <span class="web-mode-constant">null</span>,
<span class="rainbow-delimiters-depth-2">}</span>,
<span class="rainbow-delimiters-depth-1"><span class="custom">}</span></span>;
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">handleRouteChange</span><span class="rainbow-delimiters-depth-1">(</span>state = initialState, pathname<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">index</span> = state.modalList.<span class="web-mode-function-call">findIndex</span><span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-function-name">path</span> => path === pathname<span class="rainbow-delimiters-depth-2">)</span>;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-2">{</span> ...state, <span class="web-mode-variable-name">currIndex</span>: index <span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<p>Here is how it could be done using <code>Object.assign</code> (ES2015):</p>
<pre class="htmlize">
<span class="web-mode-keyword">function</span> <span class="web-mode-function-name">handleRouteChange</span><span class="rainbow-delimiters-depth-1">(</span>state = initialState, pathname<span class="rainbow-delimiters-depth-1">)</span> <span class="rainbow-delimiters-depth-1">{</span>
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">index</span> = state.modalList.<span class="web-mode-function-call">findIndex</span><span class="rainbow-delimiters-depth-2">(</span><span class="web-mode-function-name">path</span> => path === pathname<span class="rainbow-delimiters-depth-2">)</span>;
<span class="web-mode-keyword">return</span> Object.<span class="web-mode-function-call">assign</span><span class="rainbow-delimiters-depth-2">(</span><span class="rainbow-delimiters-depth-3">{}</span>, <span class="web-mode-variable-name">state</span>, <span class="rainbow-delimiters-depth-3">{</span> <span class="web-mode-variable-name">currIndex</span>: <span class="web-mode-variable-name">index</span> <span class="rainbow-delimiters-depth-3">}</span><span class="rainbow-delimiters-depth-2">)</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<p>Doing it using ES5 is <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill">much harder</a>.</p>
<h4 id="concise-properties">Concise properties <small>(ES2015)</small></h4>
<p>
See <a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20%26%20beyond/ch2.md#concise-properties">Concise Properties - YDKJS: ES6 & Beyond</a>
</p>
<p>Here is an example using ES2105 concise properties:</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">a</span> = 1;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">b</span> = 2;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">c</span> = <span class="rainbow-delimiters-depth-1">{</span> a, b <span class="rainbow-delimiters-depth-1">}</span>;
</pre>
<p>It could be written in ES5 without concise properties as:</p>
<pre class="htmlize">
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">a</span> = 1;
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">b</span> = 2;
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">c</span> = <span class="rainbow-delimiters-depth-1">{</span> <span class="web-mode-variable-name">a</span>: a, <span class="web-mode-variable-name">b</span>: b <span class="rainbow-delimiters-depth-1">}</span>;
</pre>
<h4 id="array-includes"><code>Array#includes</code> <small>(ES2016)</small></h4>
<p>
See <a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20%26%20beyond/ch8.md#arrayincludes">Array#includes - YDKJS: ES6 & Beyond</a>
</p>
<p>Here is an example testing whether a value is included in an array using ES2016 <code>Array#includes</code>:</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">selectedRows</span> = <span class="rainbow-delimiters-depth-1">[</span>1, 2, 3<span class="rainbow-delimiters-depth-1">]</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">isSelected</span> = selectedRows.<span class="web-mode-function-call">includes</span><span class="rainbow-delimiters-depth-1">(</span>rowId<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<p>Here is how it could be written using ES5:</p>
<pre class="htmlize">
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">selectedRows</span> = <span class="rainbow-delimiters-depth-1">[</span>1, 2, 3<span class="rainbow-delimiters-depth-1">]</span>;
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">isSelected</span> = selectedRows.<span class="web-mode-function-call">indexOf</span><span class="rainbow-delimiters-depth-1">(</span>rowId<span class="rainbow-delimiters-depth-1">)</span> >= 0;
</pre>
<h4 id="template-literals">Template literals <small>(ES2015)</small></h4>
<p>Template literals provide support for string interpolation.
Variables and arbitrary expressions may be substituted into the string.
More information:
<a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20%26%20beyond/ch2.md#template-literals">Template Literals - YDKJS: ES6 & Beyond</a>
</p>
<p>
Here is an example using template literals:
</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">host</span> = <span class="web-mode-javascript-string">"api.github.com"</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">url</span> = <span class="web-mode-javascript-string">`</span><span class="web-mode-javascript-string"><span class="link">https://</span></span><span class="web-mode-variable-name"><span class="link">$</span></span><span class="web-mode-variable-name">{host}</span><span class="web-mode-javascript-string">/search/code`</span>;
</pre>
<p>Here is how it could be written without template literals in ES5:</p>
<pre class="htmlize">
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">host</span> = <span class="web-mode-javascript-string">"api.github.com"</span>;
<span class="web-mode-keyword">var</span> <span class="web-mode-variable-name">url</span> = <span class="web-mode-javascript-string">"https://"</span> + host + <span class="web-mode-javascript-string">"/search/code"</span>;
</pre>
<h4 id="see-also">See also</h4>
<ul>
<li><a href="https://babeljs.io/repl/">Babel REPL</a></li>
<li><a href="http://kangax.github.io/compat-table/es6/">ES2015 compatibility table</a></li>
<li><a href="http://kangax.github.io/compat-table/es2016plus/">ES2016+ compatibility table</a></li>
<li><a href="http://kangax.github.io/compat-table/esnext/">ESNext compatibility table</a></li>
</ul>
A chained modals example w/ React Router (part 2)
2016-03-02T13:07:51-08:00https://www.saltycrane.com/blog/2016/03/chained-modals-example-react-router-part-2/<p>
This is an example using
<a href="https://facebook.github.io/react/">React</a> and
<a href="https://github.com/reactjs/react-router"> React Router</a>
to create a sequence of chained modals.
In <a href="/blog/2016/02/chained-modals-example-react-bootstrap/">part 1</a>,
I made a basic example where clicking a "Next" button
advanced to the next modal in a list.
</p>
<p>
This example supports going back and forward using browser navigation
and linking to a specific modal's URL.
Additionally, each modal has a form input. On clicking the
"Next" button, an AJAX request is made to save the data. On failure
an error is displayed. On success the next modal is shown.
During the request a spinner is shown.
The full
<a href="https://github.com/saltycrane/react-chained-modals-comparison/tree/master/L2.methodB.react.router/src">code is here</a> and a
<a href="http://saltycrane.github.io/react-chained-modals-comparison/L2.methodB.react.router/">
demo is here</a>.
</p>
<h5 id="App"><code>App.js</code></h5>
<p>
<code>RoutedApp</code> is the top level component that configures the routes for the app.
Each route has an associated component.
Like the previous example, the modals are children of <code>ChainedModals</code> which
is a child of <code>App</code>. However now each modal is explicitly declared in the element tree
and React Router decides which modal to render based on the route.
For example, navigating to the <code>/name</code> route renders <code>ModalName</code> as a child
of <code>ChainedModals</code> as a child of <code>App</code>. In the <code>App</code>
component, <code>children</code> is set
to the <code>ChainedModals</code> element. In the <code>ChainedModals</code>
component, <code>children</code> is set to either the <code>ModalName</code> or
<code>ModalPhone</code> element depending on the route.
</p>
<p>
Instead of passing modal components to <code>ChainedModals</code>, a list of routes
to the modals is passed in.
The utility function <code>partial</code> allows me to create a component that is
equivalent to <code>ChainedModals</code> with the <code>modalList</code> prop preset.
</p>
<p>
This uses ES'15
<a href="/blog/2016/03/es6-features-used-react-development/#arrow-functions">
arrow functions</a>,
<a href="/blog/2016/03/es6-features-used-react-development/#destructuring-function-args">
argument destructuring</a> and
<a href="/blog/2016/03/es6-features-used-react-development/#rest-spread">
JSX spread</a>.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L2.methodB.react.router/src/containers/App.js">App.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">RoutedApp</span> = <span class="rainbow-delimiters-depth-1">()</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Router</span> <span class="web-mode-html-attr-name">history</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>hashHistory<span class="rainbow-delimiters-depth-2">}</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>App<span class="rainbow-delimiters-depth-2">}</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/"</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-function-call">partial</span><span class="rainbow-delimiters-depth-3">(</span>ChainedModals, <span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-variable-name">modalList</span>: <span class="rainbow-delimiters-depth-5">[</span><span class="web-mode-javascript-string">'/name'</span>, <span class="web-mode-javascript-string">'/phone'</span>, <span class="web-mode-javascript-string">'/done'</span><span class="rainbow-delimiters-depth-5">]</span><span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span><span class="rainbow-delimiters-depth-2">}</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/name"</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>ModalName<span class="rainbow-delimiters-depth-2">}</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/phone"</span> <span class="web-mode-html-attr-name">component</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>ModalPhone<span class="rainbow-delimiters-depth-2">}</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">IndexRedirect</span> <span class="web-mode-html-attr-name">to</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/name"</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Route</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Route</span> <span class="web-mode-html-attr-name">path</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"/done"</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Route</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Router</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">App</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span> children <span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">PageBehindModals</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="rainbow-delimiters-depth-2">{</span>children<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">partial</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="web-mode-variable-name">Comp</span>, <span class="web-mode-variable-name">props</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">(</span><span class="web-mode-variable-name">fprops</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Comp</span> <span class="rainbow-delimiters-depth-1">{</span><span class="web-mode-block-delimiter">...</span>props<span class="rainbow-delimiters-depth-1">}</span> <span class="rainbow-delimiters-depth-1">{</span><span class="web-mode-block-delimiter">...</span>fprops<span class="rainbow-delimiters-depth-1">}</span> <span class="web-mode-html-tag-bracket">/></span>;
</pre>
<h5 id="ChainedModals">ChainedModals.js</h5>
<p>
<code>ChainedModals</code> is a component that manages its child modal components.
Unlike the previous example, the modal to be shown is determined by the current route.
Before the component is rendered, the index of the current modal is determined by
finding the current route in the modal list. This index is stored in the state.
When the modal's "Next" button is clicked, <code>_gotoNext</code> determines the next
route to be displayed and changes the route using <code>hashHistory.push()</code>.
<a href="https://facebook.github.io/react/docs/top-level-api.html#react.cloneelement">
<code>React.cloneElement</code></a> is used to pass props to the
child modal as shown in the
<a href="https://github.com/reactjs/react-router/blob/v2.0.0/examples/passing-props-to-children/app.js">React Router examples</a>.
This uses ES'15
<a href="/blog/2016/03/es6-features-used-react-development/#nested-destructuring">
nested destructuring</a> and classes, and
ES'17 class properties.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L2.methodB.react.router/src/containers/ChainedModals.js">ChainedModals.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">ChainedModals</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">Component</span> <span class="rainbow-delimiters-depth-1"><span class="ATTRLIST">{</span></span>
<span class="web-mode-function-call">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2"><span class="ATTRLIST-1">{</span></span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> children <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-javascript-comment">// Clone the child view element so we can pass props to it.</span>
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">modalElement</span> = children && React.<span class="web-mode-function-call">cloneElement</span><span class="rainbow-delimiters-depth-3">(</span>children, <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-variable-name">step</span>: currIndex + 1,
<span class="web-mode-variable-name">gotoNext</span>: <span class="web-mode-constant">this</span>._gotoNext,
<span class="web-mode-variable-name">backdrop</span>: <span class="web-mode-constant">false</span>,
<span class="web-mode-variable-name">show</span>: <span class="web-mode-constant">true</span>,
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">ModalBackdrop</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="rainbow-delimiters-depth-4">{</span>modalElement<span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2"><span class="ATTRLIST-1">}</span></span>
<span class="web-mode-function-call">componentWillMount</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">_setIndexFromRoute</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-constant">this</span>.props<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-call">componentWillReceiveProps</span><span class="rainbow-delimiters-depth-2">(</span>nextProps<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">_setIndexFromRoute</span><span class="rainbow-delimiters-depth-3">(</span>nextProps<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-call">_setIndexFromRoute</span><span class="rainbow-delimiters-depth-2">(</span>props<span class="rainbow-delimiters-depth-2">)</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> modalList, <span class="web-mode-variable-name">location</span>: <span class="rainbow-delimiters-depth-4">{</span> pathname <span class="rainbow-delimiters-depth-4">}</span> <span class="rainbow-delimiters-depth-3">}</span> = props;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">index</span> = modalList.<span class="web-mode-function-call">findIndex</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-function-name">path</span> => path === pathname<span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-variable-name">currIndex</span>: index<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">_gotoNext</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> modalList <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">nextRoute</span> = modalList<span class="rainbow-delimiters-depth-3">[</span>currIndex + 1<span class="rainbow-delimiters-depth-3">]</span>;
hashHistory.<span class="web-mode-function-call">push</span><span class="rainbow-delimiters-depth-3">(</span>nextRoute<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1"><span class="ATTRLIST">}</span></span>
</pre>
<h5 id="ModalName">ModalName.js</h5>
<p>
<code>ModalName</code> is one of the modal components in the list. The
<code>_handleClickNext</code> method makes a fake AJAX request using
the <a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L2.methodB.react.router/src/request-simulator.js"><code>request</code> simulator</a>
function. <code>request</code> returns
a Promise. On success, it calls <code>gotoNext</code> (which is passed in
as a prop) to go to the next modal. The component state is used to
show a spinner during the AJAX request (<code>isRequesting</code>)
and to show validation errors (<code>errorMsg</code>).
This uses ES'15
<a href="/blog/2016/03/es6-features-used-react-development/#destructuring">
destructuring</a>, classes, and promises, and ES'17
<a href="/blog/2016/03/es6-features-used-react-development/#rest-spread">
rest/spread</a> and class properties.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L2.methodB.react.router/src/components/ModalName.js">ModalName.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">ModalName</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">Component</span> <span class="rainbow-delimiters-depth-1"><span class="ATTRLIST-1">{</span></span>
state = <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">false</span>,
<span class="web-mode-variable-name">errorMsg</span>: <span class="web-mode-constant">null</span>
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="web-mode-function-call">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> step, ...props <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> isRequesting, errorMsg <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span> <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-block-delimiter">...</span>props<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Header <span class="web-mode-html-attr-name">closeButton</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Title<span class="web-mode-html-tag-bracket">></span>Step <span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>step<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span> - Name<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Title<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Header<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Body<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>isRequesting && <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">><</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></span>Making fake ajax request...<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">></span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span>errorMsg && <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">><</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-5">{</span></span>errorMsg<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-5">}</span></span><span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">em</span><span class="web-mode-html-tag-bracket">></</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">></span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Input</span>
<span class="web-mode-html-attr-name">label</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"Enter your name"</span>
<span class="web-mode-html-attr-name">type</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"text"</span>
<span class="web-mode-html-attr-name">bsSize</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"large"</span>
<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-block-delimiter">...</span><span class="rainbow-delimiters-depth-5">(</span>errorMsg ? <span class="rainbow-delimiters-depth-6">{</span><span class="web-mode-variable-name">bsStyle</span>: <span class="web-mode-javascript-string">'error'</span><span class="rainbow-delimiters-depth-6">}</span> : <span class="rainbow-delimiters-depth-6">{}</span><span class="rainbow-delimiters-depth-5">)</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-attr-name">ref</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="rainbow-delimiters-depth-5">(</span>c<span class="rainbow-delimiters-depth-5">)</span> => <span class="web-mode-constant">this</span>._input = c<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span>
<span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Body<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Footer<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Button</span> <span class="web-mode-html-attr-name">bsStyle</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"primary"</span> <span class="web-mode-html-attr-name">onClick</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">{</span></span><span class="web-mode-constant">this</span>._handleClickNext<span class="web-mode-block-delimiter"><span class="rainbow-delimiters-depth-4">}</span></span><span class="web-mode-html-tag-bracket">></span>Next<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Button</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Footer<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">_handleClickNext</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2"><span class="ATTRLIST">{</span></span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> gotoNext <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">name</span> = <span class="web-mode-constant">this</span>._input.<span class="web-mode-function-call">getValue</span><span class="rainbow-delimiters-depth-3">()</span>;
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">true</span>, <span class="web-mode-variable-name">errorMsg</span>: <span class="web-mode-constant">null</span><span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="web-mode-function-call">request</span><span class="rainbow-delimiters-depth-3">(</span><span class="web-mode-javascript-string">'/api/name'</span>, name<span class="rainbow-delimiters-depth-3">)</span>
.<span class="web-mode-function-call">then</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">()</span> => <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-function-call">gotoNext</span><span class="rainbow-delimiters-depth-5">()</span>;
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>
.<span class="web-mode-keyword">catch</span><span class="rainbow-delimiters-depth-3">(</span><span class="rainbow-delimiters-depth-4">(</span>error<span class="rainbow-delimiters-depth-4">)</span> => <span class="rainbow-delimiters-depth-4">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-5">(</span><span class="rainbow-delimiters-depth-6">{</span><span class="web-mode-variable-name">isRequesting</span>: <span class="web-mode-constant">false</span>, <span class="web-mode-variable-name">errorMsg</span>: error<span class="rainbow-delimiters-depth-6">}</span><span class="rainbow-delimiters-depth-5">)</span>;
<span class="rainbow-delimiters-depth-4">}</span><span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2"><span class="ATTRLIST">}</span></span>;
<span class="rainbow-delimiters-depth-1"><span class="ATTRLIST-1">}</span></span>
</pre>
A chained modals example w/ React Bootstrap
2016-02-23T23:21:44-08:00https://www.saltycrane.com/blog/2016/02/chained-modals-example-react-bootstrap/<p>
This is an example using
<a href="https://facebook.github.io/react/">React</a>
to create a sequence of
chained modals. Each modal has a "Next" button that, when clicked, advances
to the next modal. The list of modals is configurable at page load.
I used
<a href="https://react-bootstrap.github.io/">React-Bootstrap</a>
for the modals which provide animation
when switching modals.
The full <a href="https://github.com/saltycrane/react-chained-modals-comparison/tree/master/L1.methodA.react/src">code is here</a> and a <a href="http://saltycrane.github.io/react-chained-modals-comparison/L1.methodA.react/">demo is here</a>.
</p>
<h5 id="App"><code>App.js</code></h5>
<p>
The top level app is a function that returns an element tree consisting of
two elements: <code>PageBehindModals</code> and <code>ChainedModals</code>.
<code>ChainedModals</code> is a parent of the modals which are passed
in via the <code>modalList</code> prop.
This uses
<a href="/blog/2016/03/es6-features-used-react-development/#arrow-functions">
ES'15 arrow functions</a>.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L1.methodA.react/src/containers/App.js">App.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">App</span> = <span class="rainbow-delimiters-depth-1">()</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">PageBehindModals</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">ChainedModals</span> <span class="web-mode-html-attr-name">modalList</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span><span class="rainbow-delimiters-depth-3">[</span>ModalName, ModalPhone<span class="rainbow-delimiters-depth-3">]</span><span class="rainbow-delimiters-depth-2">}</span> <span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<h5 id="ModalName"><code>ModalName.js</code></h5>
<p>
<code>ModalName</code> is one of the modal components.
It is built using React-Bootstrap's <code>Modal</code> and <code>Button</code>
components.
The step number and onClick handler
are passed in as props from the parent component.
The rest of the props (<code>show</code> and <code>backdrop</code>)
are passed along to React-Bootstrap's Modal component.
This uses
<a href="/blog/2016/03/es6-features-used-react-development/#destructuring-function-args">
ES'15 argument destructuring</a> and
<a href="/blog/2016/03/es6-features-used-react-development/#rest-spread">
ES'17 rest/spread</a>.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L1.methodA.react/src/components/ModalName.js">ModalName.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">ModalName</span> = <span class="rainbow-delimiters-depth-1">(</span><span class="rainbow-delimiters-depth-2">{</span> <span class="web-mode-variable-name">onClickNext</span>, <span class="web-mode-variable-name">step</span>, ...rest <span class="rainbow-delimiters-depth-2">}</span><span class="rainbow-delimiters-depth-1">)</span> => <span class="rainbow-delimiters-depth-1">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span> <span class="rainbow-delimiters-depth-2">{</span><span class="web-mode-block-delimiter">...</span>rest<span class="rainbow-delimiters-depth-2">}</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Header <span class="web-mode-html-attr-name">closeButton</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Title<span class="web-mode-html-tag-bracket">></span>Step <span class="rainbow-delimiters-depth-2">{</span>step<span class="rainbow-delimiters-depth-2">}</span> - Name<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Title<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Header<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Body<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">></span>Enter your name<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">p</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Body<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Modal</span>.Footer<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">Button</span> <span class="web-mode-html-attr-name">bsStyle</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"primary"</span> <span class="web-mode-html-attr-name">onClick</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-2">{</span>onClickNext<span class="rainbow-delimiters-depth-2">}</span><span class="web-mode-html-tag-bracket">></span>Next<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Button</span><span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span>.Footer<span class="web-mode-html-tag-bracket">></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">Modal</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-1">)</span>;
</pre>
<h5 id="ChainedModals"><code>ChainedModals.js</code></h5>
<p><code>ChainedModals</code> keeps track of the current modal displayed
in its component state. It defines <code>_handleClickNext</code>
that is passed to the child modal components. When the child modal's
"Next" button is clicked, this method is run. The method updates <code>currIndex</code>
in the state, which causes a re-render.
On re-render, <code>ChainedModals</code> finds a new modal component
and renders it instead of the previous one. When the end of the modal
list is reached, the modal is hidden, and the underlying page is shown.
This uses
<a href="/blog/2016/03/es6-features-used-react-development/#destructuring">
ES'15 destructuring</a>, ES'15 classes, and
ES'17 class properties.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L1.methodA.react/src/containers/ChainedModals.js">ChainedModals.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">class</span> <span class="web-mode-type">ChainedModals</span> <span class="web-mode-keyword">extends</span> <span class="web-mode-type">Component</span> <span class="rainbow-delimiters-depth-1">{</span>
state = <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-variable-name">currIndex</span>: 0,
<span class="web-mode-variable-name">showModal</span>: <span class="web-mode-constant">true</span>
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="web-mode-function-call">render</span><span class="rainbow-delimiters-depth-2">()</span> <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> modalList <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex, showModal <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">ModalComponent</span> = modalList<span class="rainbow-delimiters-depth-3">[</span>currIndex<span class="rainbow-delimiters-depth-3">]</span>;
<span class="web-mode-keyword">return</span> <span class="rainbow-delimiters-depth-3">(</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-4">{</span>showModal && <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">ModalBackdrop</span> <span class="web-mode-html-tag-bracket">/></span><span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">ModalComponent</span>
<span class="web-mode-html-attr-name">step</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-4">{</span>currIndex + 1<span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-attr-name">onClickNext</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-constant">this</span>._handleClickNext<span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-attr-name">backdrop</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-4">{</span><span class="web-mode-constant">false</span><span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-attr-name">show</span><span class="web-mode-html-attr-equal">=</span><span class="rainbow-delimiters-depth-4">{</span>showModal<span class="rainbow-delimiters-depth-4">}</span>
<span class="web-mode-html-tag-bracket">/></span>
<span class="web-mode-html-tag-bracket"></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>
<span class="rainbow-delimiters-depth-3">)</span>;
<span class="rainbow-delimiters-depth-2">}</span>
<span class="web-mode-function-name">_handleClickNext</span> = <span class="rainbow-delimiters-depth-2">()</span> => <span class="rainbow-delimiters-depth-2">{</span>
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> modalList <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.props;
<span class="web-mode-keyword">const</span> <span class="rainbow-delimiters-depth-3">{</span> currIndex <span class="rainbow-delimiters-depth-3">}</span> = <span class="web-mode-constant">this</span>.state;
<span class="web-mode-keyword">if</span> <span class="rainbow-delimiters-depth-3">(</span>currIndex < modalList.length - 1<span class="rainbow-delimiters-depth-3">)</span> <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-4">(</span><span class="rainbow-delimiters-depth-5">{</span><span class="web-mode-variable-name">currIndex</span>: currIndex + 1<span class="rainbow-delimiters-depth-5">}</span><span class="rainbow-delimiters-depth-4">)</span>;
<span class="rainbow-delimiters-depth-3">}</span> <span class="web-mode-keyword">else</span> <span class="rainbow-delimiters-depth-3">{</span>
<span class="web-mode-constant">this</span>.<span class="web-mode-function-call">setState</span><span class="rainbow-delimiters-depth-4">(</span><span class="rainbow-delimiters-depth-5">{</span><span class="web-mode-variable-name">showModal</span>: <span class="web-mode-constant">false</span><span class="rainbow-delimiters-depth-5">}</span><span class="rainbow-delimiters-depth-4">)</span>;
<span class="rainbow-delimiters-depth-3">}</span>
<span class="rainbow-delimiters-depth-2">}</span>;
<span class="rainbow-delimiters-depth-1">}</span>
</pre>
<h5 id="ModalBackdrop"><code>ModalBackdrop.js</code></h5>
<p>A separate <code>ModalBackdrop</code> component is used so that the default modal's
backdrop doesn't
flash in and out when showing and hiding the modals. Since there is a separate modal
backdrop, the <code>backdrop</code> prop is set to false for each modal.
<code class="github-link">[<a href="https://github.com/saltycrane/react-chained-modals-comparison/blob/master/L1.methodA.react/src/components/ModalBackdrop.js">ModalBackdrop.js on github</a>]</code>
</p>
<pre class="htmlize">
<span class="web-mode-keyword">const</span> <span class="web-mode-variable-name">ModalBackdrop</span> = <span class="rainbow-delimiters-depth-1">()</span> => <span class="web-mode-html-tag-bracket"><</span><span class="web-mode-html-tag">div</span> <span class="web-mode-html-attr-name">className</span><span class="web-mode-html-attr-equal">=</span><span class="web-mode-html-attr-value">"modal-backdrop in"</span><span class="web-mode-html-tag-bracket">></</span><span class="web-mode-html-tag">div</span><span class="web-mode-html-tag-bracket">></span>;
</pre>
How to set up Babel 6 with React on Mac OS X (command line only)
2016-01-07T00:06:17-08:00https://www.saltycrane.com/blog/2016/01/how-set-up-babel-6-with-react-mac-os-x-command-line-only/<p>
<a href="https://babeljs.io/">Babel</a> is a tool used to compile
JavaScript from ES6 to ES5. It is also the official way to compile
React's <a href="https://facebook.github.io/react/docs/jsx-in-depth.html">JSX</a>.
Here is how to install and set up Babel 6 on OS X to run on the command line
to compile React's JSX.
</p>
<h4>Directory structure</h4>
<pre>my-project
├── .babelrc
├── hello.babelized.js
├── hello.js
├── index.html
├── node_modules
└── package.json</pre>
<h4>Install Node.js</h4>
<p>Installing Node.js provides <a href="https://www.npmjs.com/">npm</a>,
the Node package manager. npm is used to
install Babel.</p>
<pre class="console">$ brew install node </pre>
<pre class="console">$ node --version
v5.5.0 </pre>
<h4>Create a <code>package.json</code> file</h4>
<p>
<a href="https://docs.npmjs.com/files/package.json">package.json</a>
is the configuration file for npm. The package.json file below
specifies 3 packages to install. <code>babel-cli</code> is the Babel command line tool.
<code>babel-preset-es2015</code>
and <code>babel-preset-react</code>
are 2 packages that provide the plugins for Babel to transform ES6 and JSX
respectively. The <a href="https://docs.npmjs.com/misc/scripts">"scripts"</a>
section specifies a command to compile the
<code>hello.js</code> file with Babel.
</p>
<pre class="json">{
"devDependencies": {
"babel-cli": "6.5.1",
"babel-preset-es2015": "6.5.0",
"babel-preset-react": "6.5.0"
},
"scripts": {
"build": "babel hello.js -o hello.babelized.js"
}
}</pre>
<h4>Install Babel</h4>
<p>
<a href="https://docs.npmjs.com/cli/install"><code>npm install</code></a>
installs all the packages listed in package.json
into a <code>node_modules</code> directory in the current directory.
Packages can also be installed globally using the "-g"
flag with <code>npm install</code>. Since this is a local install,
the <code>babel</code> command is availiable as <code>./node_modules/.bin/babel</code>.
</p>
<pre class="console">$ npm install </pre>
<pre class="console">$ ./node_modules/.bin/babel --version
6.5.1 (babel-core 6.5.1) </pre>
<h4>Create a <code>.babelrc</code> file</h4>
<p>
The <a href="https://babeljs.io/docs/usage/babelrc/"><code>.babelrc</code></a>
file is used to configure Babel.</p>
<pre class="json">{
"presets": ["react", "es2015"]
}</pre>
<h4>Create a <code>index.html</code> file</h4>
<p>This uses the React libraries from Facebook's CCN and the compiled
<code>hello.babelized.js</code> file. The React app lives in the "container" div.
</p>
<pre class="html">
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Hello World</title>
</head>
<body>
<div id="container"></div>
<script src="https://fb.me/react-0.14.7.min.js"></script>
<script src="https://fb.me/react-dom-0.14.7.min.js"></script>
<script src="hello.babelized.js"></script>
</body>
</html>
</pre>
<h4>Create a <code>hello.js</code> file</h4>
<p>Note: this <code>hello.js</code> does not use ES6.</p>
<pre class="javascript">var Hello = React.createClass({
render: function() {
return (
<h1>Hello World</h1>
);
}
});
ReactDOM.render(
<Hello />,
document.getElementById('container')
);</pre>
<h4>Run Babel</h4>
<pre class="console">$ npm run build </pre>
<p>This creates the <code>hello.babelized.js</code> file:</p>
<pre class="javascript">var Hello = React.createClass({
displayName: 'Hello',
render: function () {
return React.createElement(
'h1',
null,
'Hello World'
);
}
});
ReactDOM.render(React.createElement(Hello, null), document.getElementById('container'));</pre>
<h4>View in browser</h4>
<pre class="console">$ open index.html </pre>
<h4>See also</h4>
<ul>
<li><a href="https://babeljs.io/blog/2015/10/31/setting-up-babel-6">
Setting up Babel 6</a> on the Babel blog</li>
<li><a href="http://www.2ality.com/2015/11/configuring-babel6.html">
Configuring Babel 6</a> on the ②ality blog</li>
<li><a href="https://babeljs.io/docs/usage/cli/">
Babel CLI documentation</a></li>
</ul>
Modules and import in ES6 for Python developers
2015-12-03T23:02:28-08:00https://www.saltycrane.com/blog/2015/12/modules-and-import-es6-python-developers/<style>
td.col2 {
width: 500px;
}
</style>
<p>
Here's a comparison of Python and JavaScript (ES6) imports.
There are a few differences between the two:
<ol>
<li><a href="http://exploringjs.com/es6/ch_modules.html#static-module-structure">
JavaScript imports are static</a>; Python's are dynamic.
</li>
<li>JavaScript items must be explicitly exported.
In Python, all items are available for import.
</li>
<li>JavaScript has a concept of a default export. Python does not.</li>
</ol>
</p>
<table class="table table-sc">
<thead>
<tr>
<th><strong>Python</strong></th>
<th><strong>ES6 (ES 2015)</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<pre class="python">import mymodule
mymodule.myfunc()</pre>
mymodule.py:
<pre class="python">def myfunc(): pass</pre>
<hr>
<pre class="python">import mymodule as myalias
myalias.myfunc()</pre>
mymodule.py:
<pre class="python">def myfunc(): pass</pre>
</td>
<td class="col2">
<em>Namespaced imports</em>
<pre class="javascript">import * as myalias from "./mymodule";
myalias.myfunc();</pre>
mymodule.js:
<pre class="javascript">export function myfunc() {}</pre>
Note: this form covers both Python's <code>import mymodule</code>
and <code>import mymodule as myalias</code> forms.
</td>
</tr>
<tr>
<td>
<pre class="python">from mymodule import myvar, myfunc
print myvar
myfunc()</pre>
mymodule.py:
<pre class="python">myvar = 42
def myfunc(): pass</pre>
</td>
<td class="col2">
<em>Named imports</em>
<pre class="javascript">import { myvar, myfunc } from "./mymodule";
console.log(myvar);
myfunc();</pre>
mymodule.js:
<pre class="javascript">export var myvar = 42;
// no semicolon for inline exports
// of functions and classes
export function myfunc() {}</pre>
Note: curly braces are required even if only importing a single item.
This is not <a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch2.md#destructuring">destructuring</a>. It is
<a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch3.md#importing-api-members">syntax specific to modules</a>.
Destructuring on import is
<a href="http://exploringjs.com/es6/ch_modules.html#_can-i-use-destructuring-in-an-import-statement">
not supported</a> in ES6.
</td>
</tr>
<tr>
<td>
<em>No equivalent</em>
</td>
<td class="col2">
<em>Default imports (preferred form)</em>
<pre class="javascript">import myalias from "./mymodule";
myalias();</pre>
mymodule.js:
<pre class="javascript">export default function myfunc() {}</pre>
<p>
Note: this import syntax has <strong>no curly braces</strong> because
<code>export <strong>default</strong></code> is used instead of just
<code>export</code> in mymodule.js. There can be only one default
export per module. Using this syntax is the
<a href="http://exploringjs.com/es6/ch_modules.html#_default-exports-are-favored">
preferred form</a>. Unlike the form with curly braces, you will always
supply your own name for the imported item. It may or may not be the same
as the original name of the exported item. You may also combine the default
import syntax with the non-default syntax.
</p>
<pre class="javascript">import mydefault, { myother } from "./mymodule";
mydefault();
myother();</pre>
mymodule.js:
<pre class="javascript">export function myother() {}
export default function myfunc() {}</pre>
</td>
</tr>
<tr>
<td>
<pre class="python">from mymodule import myfunc as myalias
myalias()</pre>
mymodule.py:
<pre class="python">def myfunc(): pass</pre>
</td>
<td class="col2">
<em>Renaming an import</em>
<pre class="javascript">import { myfunc as myalias } from "./mymodule";
myalias();</pre>
mymodule.js:
<pre class="javascript">export function myfunc() {}</pre>
</td>
</tr>
<tr>
<td>
<pre class="python">from mymodule import *
print myvar
myfunc()</pre>
mymodule.py:
<pre class="python">myvar = 42
def myfunc(): pass</pre>
Note: this form is <a href="https://www.python.org/dev/peps/pep-0008/#imports">not recommended</a>
</td>
<td class="col2">
<em>No equivalent</em>
</td>
</tr>
<tr>
<td>
<pre class="python">from mydir.mymodule import myfunc
myfunc()</pre>
mydir/mymodule.py:
<pre class="python">def myfunc(): pass</pre>
Note: mydir contains a <code>__init__.py</code> file and is a module (package)
</td>
<td class="col2">
<em>Importing from a subdirectory</em>
<pre class="javascript">import { myfunc } from "mydir/mymodule";
myfunc();</pre>
mydir/mymodule.js:
<pre class="javascript">export function myfunc() {}</pre>
</td>
</tr>
</tbody>
</table>
<h4 id="names-paths">Names vs. paths</h4>
<p>Modules can be referenced
<a href="http://exploringjs.com/es6/ch_modules.html#_modules-in-javascript">
by name or by path</a>. Names are often used with external libraries.
For example, below "react" is the name.
</p>
<pre class="javascript">import React from "react";</pre>
<p>Paths are often used with your project modules. Here the module
is referenced by the path "./MyComponent". (The <code>.js</code> extension is implicit.)
</p>
<pre class="javascript">import MyComponent from "./MyComponent";</pre>
<h4 id="curly-braces">When are curly braces needed?</h4>
<p>
Curly braces are needed when importing non-default exports from a module.
If the item is exported <strong>with <code>default</code></strong>,
use the import syntax <strong>without curly braces</strong>.
</p>
<pre class="javascript">// mymodule.js
export default Something;</pre>
<pre class="javascript">import Something from "mymodule";</pre>
<p>
If the item is exported <strong>without <code>default</code></strong>,
you must import the item <strong>with curly braces</strong>.
</p>
<pre class="javascript">// mymodule.js
export Something;</pre>
<pre class="javascript">import { Something } from "mymodule";</pre>
<h4 id="references">References</h4>
<p>
<a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch3.md#modules">
You Don't Know JS: ES6 & Beyond</a>
</p>
<p>
<a href="http://exploringjs.com/es6/ch_modules.html">
Exploring ES6</a>
</p>
Switching to OS X and front end development
2015-09-10T08:26:36-07:00https://www.saltycrane.com/blog/2015/09/switching-osx-and-front-end-development/<p>After 7 years, I've yielded to the Dark Side and switched from Ubuntu to OS X on my work laptop.
I've also switched from Python and back end web development to JavaScript and front end development.
The former is mostly to support the latter.
</p>
<p>Linux is rare<sup>1</sup>, especially among front end developers,
and I want to make it easy to collaborate as I learn new things.
I've had problems working with Photoshop files in GIMP and I couldn't run
the iOS simulator. <a href="https://news.ycombinator.com/item?id=10812214">
Issues with Linux device drivers</a> don't help.
</p>
<p>I'm choosing front end development because I want to code closer to the end user.<sup>2</sup>
In small part like Ian Bicking
<a href="http://www.ianbicking.org/blog/2014/02/saying-goodbye-to-python.html">
wrote last year</a>,
I feel unexcited about back end development and really excited about
JavaScript and front end development. I'm excited about
ES 2015 and
React and
React Native and
CSS transitions.<sup>3</sup>
I'm even coming around to Node.js.
JavaScript is <a href="https://www.destroyallsoftware.com/talks/wat">uglier</a>
than Python, but it's getting
<a href="https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841">
better</a>
and there are things
<a href="https://glyph.twistedmatrix.com/2015/09/software-you-can-use.html">
Python can't do</a> that
<a href="http://steve-yegge.blogspot.com/2007/02/next-big-language.html">
JavaScript can</a>.<sup>4</sup> If only beauty mattered, maybe I'd use Scheme.<sup>5</sup>
</p>
<p>I'm sure I will hate OS X at first, but hopefully it will be good in the
long run. If anyone can recommend a tiling window manager like
<a href="http://www.qtile.org/">Qtile</a> for OS X, please let me know.
</p>
<p><em>(I will continue using Emacs because <a href="http://emacsrocks.com/">Emacs rocks!</a> <sup>6</sup>)</em>
</p>
<hr>
<small>
<ol>
<li>I think I was the last person at my company running Linux.</li>
<li>I've been trying to do front end work for years now, but
I finally got a sensible chance to switch as my company is
changing it's technology stack from Python to Ruby and
Backbone/Angular to React.</li>
<li><em>Update 2016-01-04:</em> Here are even <a href="http://www.2ality.com/2016/01/web-technologies-2015.html">
more exciting web technologies</a>: Electron, progressive web apps, and WebAssembly.</li>
<li><em>Update 2016-01-01:</em> I found James Hague had <a href="http://prog21.dadgum.com/203.html">
similar thoughts</a> on Python and JavaScript.</li>
<li>Speaking of functional languages and JavaScript, <a href="http://elm-lang.org/">Elm</a> sounds pretty cool.</li>
<li><em>Update 2016-01-26:</em> <a href="/blog/2015/12/switching-emacs-vim-actually-spacemacs/">Or will I?</a></li>
</ol>
</small>
An example using reduce() in Underscore.js
2015-08-31T23:14:55-07:00https://www.saltycrane.com/blog/2015/08/example-using-reduce-underscore-js/<p>
I never learned how to use reduce in Python since our BDFL
<a href="http://www.artima.com/weblogs/viewpost.jsp?thread=98196">recommended against it</a>.
But since JavaScript doesn't have list comprehensions, I'm learning some
functional constructs in Underscore.js.
</p>
<p>
Underscore's <a href="http://underscorejs.org/#reduce"><code>reduce()</code></a> function
can be used to
<a href="https://en.wikipedia.org/wiki/Fold_%28higher-order_function%29">reduce</a>
an array of things into a single thing.
A common use is computing the sum of an array of numbers.
Like <code>map</code> and <code>filter</code>, it provides an alternative to a for loop. Reduce
is not limited to returning a single thing and can be used to
<a href="http://elijahmanor.com/reducing-filter-and-map-down-to-reduce/">combine the functionality of map and filter</a>
like a Python list comprehension. Learning Underscore's <code>reduce</code>
also helped me understand
<a href="https://en.wikipedia.org/wiki/MapReduce">MapReduce</a> and
<a href="https://github.com/rackt/redux/blob/master/docs/Glossary.md#reducer">reducers in Redux</a>.
</p>
<p>Here is an example that uses <code>reduce</code> to create a single JavaScript object
from an array of objects.
An empty object, <code>{}</code>, is passed in to <code>_.reduce()</code> as the initial state.
It is then extended with each item in the array and finally returned.
I also did the same example using <code>_.each()</code> for comparison,
(<em>update</em>) and using <code>Array.prototype.reduce</code> and ES6 w/o Underscore.
</p>
<h4 id="underscore-reduce">Example using Underscore's <code>reduce</code></h4>
<pre class="javascript">var myArr = [
{ rating: 5 },
{ price: 200 },
{ distance: 10 }
];
var myObj = _.reduce( myArr, function( memo, item ) {
return _.extend( memo, item ); }, {} );
console.log( myObj );</pre>
<p>Here is the output:</p>
<pre>{ rating: 5, price: 200, distance: 10 }</pre>
<h4 id="underscore-each">Example using Underscore's <code>each</code></h4>
<p>Here is the same example using <code>_.each</code>:</p>
<pre class="javascript">var myArr = [
{ rating: 5 },
{ price: 200 },
{ distance: 10 }
];
var myObj = {};
_.each( myArr, function( item ) {
myObj = _.extend( myObj, item ); });
console.log( myObj );</pre>
<p>The output is the same:</p>
<pre>{ rating: 5, price: 200, distance: 10 }</pre>
<h4 id="javascript-reduce">Example using ES6 and <code>Array.prototype.reduce</code></h4>
<p>
<em>Update 2015-12-11</em>: I have been writing ES6 with React thanks to
<a href="https://babeljs.io/">Babel</a> so here is an ES6 version without Underscore.js.
It looks very similar to the Underscore version.
<code>reduce()</code> is now a method of the array and <code>Object.assign()</code>
takes the place of <code>_.extend()</code>.
<em>(
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce">
<code>Array.prototype.reduce</code></a> is actually ES5, but
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign">
<code>Object.assign</code></a> and
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const">
<code>const</code></a> are ES6.
)</em>
</p>
<pre class="javascript">const myArr = [
{ rating: 5 },
{ price: 200 },
{ distance: 10 }
];
const myObj = myArr.reduce( function( memo, item ) {
return Object.assign( {}, memo, item ); }, {} );
console.log( myObj );</pre>
<p>The output is the same:</p>
<pre>{ rating: 5, price: 200, distance: 10 }</pre>
Calling JavaScript from Python to de-CloudFlare scraped content
2015-07-15T21:10:48-07:00https://www.saltycrane.com/blog/2015/07/calling-javascript-python-de-cloudflare-scraped-content/<p>
<a href="https://twitter.com/saltycrane/status/621133702706278400">Yesterday</a>
I wrote a script to scrape my own web page because I screwed up the
CSV export feature and Product needed the data. One problem was that the
<a href="https://en.wikipedia.org/wiki/CloudFlare">CloudFlare</a> CDN
<a href="https://support.cloudflare.com/hc/en-us/articles/200170016-What-is-Email-Address-Obfuscation-">obfuscated</a>
the email addresses on the page. My <del>solution</del><ins>crazy hack</ins>:
running a Node.js script to de-obfuscate the email from my Python scraping script.
</p>
<p>Example obfuscated email stuff from CloudFlare:</p>
<pre class="html">
<a href="/cdn-cgi/l/email-protection#d4b7b5a6b194a4b1a0e7e2e4fab7bbb9">
<span class="__cf_email__" data-cfemail="0162607364417164753237312f626e6c">[email protected]</span>
<script cf-hash='f9e31' type="text/javascript">
/* <![CDATA[ */!function(){try{var t="currentScript"in document?document.currentScript:function(){for(var t=document.getElementsByTagName("script"),e=t.length;e--;)if(t[e].getAttribute("cf-hash"))return t[e]}();if(t&&t.previousSibling){var e,r,n,i,c=t.previousSibling,a=c.getAttribute("data-cfemail");if(a){for(e="",r=parseInt(a.substr(0,2),16),n=2;a.length-n;n+=2)i=parseInt(a.substr(n,2),16)^r,e+=String.fromCharCode(i);e=document.createTextNode(e),c.parentNode.replaceChild(e,c)}}}catch(u){}}();/* ]]> */
</script>
</a>
</pre>
<p>Using <a href="http://jsbeautifier.org/">jsbeautifier.org</a>, I adapted the JavaScript from above into this Node.js script, <code>decloudflare.js</code>.</p>
<pre class="javascript">var e, r, n, i, a = process.argv[2];
for (e = "", r = parseInt(a.substr(0, 2), 16), n = 2; a.length - n; n += 2) i = parseInt(a.substr(n, 2), 16) ^ r, e += String.fromCharCode(i);
console.log(e);</pre>
<p>Example usage:</p>
<pre>$ node decloudflare.js 0162607364417164753237312f626e6c
care@pet360.com</pre>
<p>I used the
<a href="http://docs.naked-py.com/en/latest/">Naked</a> library
(thanks to <a href="http://sweetme.at/2014/02/17/a-simple-approach-to-execute-a-node.js-script-from-python/">Sweetmeat</a>)
to call the Node.js script. (Though probably I could've just used the
<a href="https://docs.python.org/2/library/subprocess.html">subprocess</a> module.)
<pre>$ pip install Naked </pre>
<pre class="python">from Naked.toolshed.shell import muterun_js
def decloudflare_email(cfemail):
resp = muterun_js('decloudflare.js', cfemail)
return resp.stdout.rstrip()
cfemail = '0162607364417164753237312f626e6c'
print 'cfemail from python: ' + cfemail
email = decloudflare_email(cfemail)
print 'email from python: ' + email</pre>
<pre>cfemail from python: 0162607364417164753237312f626e6c
email from python: care@pet360.com</pre>
How to install gulp.js on Ubuntu 14.10
2015-03-22T18:55:49-07:00https://www.saltycrane.com/blog/2015/03/how-install-gulpjs-ubuntu-1410/<p>
<a href="http://gulpjs.com/">gulp.js</a> is a JavaScript build tool
similar to <a href="http://gruntjs.com">Grunt</a>.
It depends on the Node.js package manager,
<a href="https://www.npmjs.org/">npm</a>.
</p>
<p>If you use the standard Ubuntu Utopic Unicorn repository to install nodejs/npm, you
might get this error when running gulp:
<pre>/usr/bin/env: node: No such file or directory</pre>
To fix this, you can install the <code>nodejs-legacy</code> package in the standard repository or use the
<a href="http://www.ubuntuupdates.org/ppa/chris_lea_nodejs">chris-lea nodejs repository</a>.
</p>
<h4 id="install-nodejs">Install nodejs, npm, and gulp</h4>
<pre class="console">$ sudo add-apt-repository ppa:chris-lea/node.js
$ sudo apt-get update
$ sudo apt-get install nodejs
$ sudo npm install -g gulp </pre>
<h4 id="install-gulp">Install gulp in your project directory</h4>
<p>This creates an empty package.json file. If you already have a package.json
file, skip the <code>echo "{}" > package.json</code> step.
</p>
<pre class="console">$ cd ~/myproject
$ echo "{}" > package.json
$ npm install --save-dev gulp </pre>
<h4 id="verify">Verify gulp is installed</h4>
<pre class="console">$ nodejs --version
v0.10.33
$ npm --version
1.4.28
$ gulp --version
[18:49:34] CLI version 3.8.11
[18:49:34] Local version 3.8.11</pre>
<h4 id="run-task">Run a simple gulp task</h4>
<p>This task will minify and rename a JS file using
<a hre="https://github.com/terinjokes/gulp-uglify">gulp-uglify</a>
and <a href="https://www.npmjs.com/package/gulp-rename">gulp-rename</a>.
</p>
<ol>
<li><pre class="console">$ cd ~/myproject</pre></li>
<li>Create a package.json file. For more information, see the
<a href="https://docs.npmjs.com/files/package.json">npm package.json documentation</a>.
<pre class="json">{
"name": "my-project-name",
"version": "0.1.0",
"devDependencies": {
"gulp": "3.8.11",
"gulp-rename": "1.2.0",
"gulp-uglify": "1.1.0"
}
}</pre>
</li>
<li>Install gulp-uglify and gulp-rename
<pre class="console">$ npm install
npm WARN package.json @ No description
npm WARN package.json @ No repository field.
npm WARN package.json @ No README data
gulp-rename@1.2.0 node_modules/gulp-rename
gulp-uglify@1.1.0 node_modules/gulp-uglify
├── deepmerge@0.2.7
├── vinyl-sourcemaps-apply@0.1.4 (source-map@0.1.43)
├── through2@0.6.3 (xtend@4.0.0, readable-stream@1.0.33)
├── uglify-js@2.4.16 (uglify-to-browserify@1.0.2, async@0.2.10, optimist@0.3.7, source-map@0.1.34)
└── gulp-util@3.0.4 (array-differ@1.0.0, object-assign@2.0.0, beeper@1.0.0, array-uniq@1.0.2, lodash._reinterpolate@3.0.0, lodash._reescape@3.0.0, lodash._reevaluate@3.0.0, replace-ext@0.0.1, minimist@1.1.1, chalk@1.0.0, vinyl@0.4.6, lodash.template@3.3.2, multipipe@0.1.2, dateformat@1.0.11)
</pre>
</li>
<li>Get an example unminified JS file:
<pre class="console">$ wget http://code.jquery.com/jquery-2.1.1.js
--2015-03-22 22:20:44-- http://code.jquery.com/jquery-2.1.1.js
Resolving code.jquery.com (code.jquery.com)... 94.31.29.230, 94.31.29.53
Connecting to code.jquery.com (code.jquery.com)|94.31.29.230|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 247351 (242K) [application/x-javascript]
Saving to: ‘jquery-2.1.1.js’
100%[==============================================================================================================>] 247,351 --.-K/s in 0.1s
2015-03-22 22:20:44 (1.94 MB/s) - ‘jquery-2.1.1.js’ saved [247351/247351]
</pre>
</li>
<li>Create a gulpfile.js file:
<pre class="javascript">var gulp = require('gulp');
var rename = require('gulp-rename');
var uglify = require('gulp-uglify');
gulp.task('default', function() {
return gulp.src('jquery-2.1.1.js')
.pipe(rename({suffix: '.min'}))
.pipe(uglify())
.pipe(gulp.dest('dist'));
});</pre>
</li>
<li>Run the gulp task:
<pre class="console">$ gulp
[22:48:10] Using gulpfile ~/myproject/gulpfile.js
[22:48:10] Starting 'default'...
[22:48:11] Finished 'default' after 1.11 s
</pre>
</li>
<li>You should now have a minified file, dist/jquery-2.1.1.min.js
<pre class="console">$ find . -iname 'jquery*' | xargs ls -gG
-rw-rw-r-- 1 84113 Mar 22 22:48 ./dist/jquery-2.1.1.min.js
-rw-rw-r-- 1 247351 Oct 23 17:16 ./jquery-2.1.1.js</pre>
</li>
</ol>
<h4 id="references">References</h4>
<ul>
<li><a href="http://markgoodyear.com/2014/01/getting-started-with-gulp/">Getting started with gulp</a></li>
<li><a href="https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md">gulp/getting-started.md</a></li>
<li><a href="https://www.npmjs.com/package/gulp-uglify">gulp-uglify</a></li>
</ul>
Customizing Bootstrap (Sass) using Grunt
2015-02-01T17:49:59-08:00https://www.saltycrane.com/blog/2015/02/customizing-bootstrap-sass-version-using-grunt-and-bower/<p><em>Update 2015-12-02: I updated the post to use npm instead of Bower
to install Bootstrap because it eliminates an extra tool and I hear
this is the preferred method.
</em></p>
<p><em>Update 2015-02-22: My co-workers informed me that
Grunt is
<a href="http://www.100percentjs.com/just-like-grunt-gulp-browserify-now/">so last year</a>
and <a href="http://gulpjs.com/">Gulp</a> is the new hotness.
You wish this post covered Gulp, but instead it covers Grunt.
</em></p>
<p>
I recently converted this blog to use Twitter's
<a href="http://getbootstrap.com/">Bootstrap</a> CSS framework to make it
<a href="http://en.wikipedia.org/wiki/Responsive_web_design">responsive</a>.
(In particular, I wanted to read it on my phone.)
I was using
<a href="http://www.blueprintcss.org/">Blueprint CSS</a>
from 2008
so it was about time for an update. Unfortunately my
design is also from 2008 but I won't update that. (Web design is hard.)
</p>
<p>
My problem was that 2014 Bootstrap's 200px gutter width didn't match
my 2008 design's 2px gutter. So I wanted to customize the gutter width.
</p>
<p>There are a few ways to customize Bootstrap. One option is
<a href="http://getbootstrap.com/customize/">creating a custom version on Bootstrap's website</a>
and downloading it for use on your site. However this doesn't allow you to
change things quickly as you develop.
Another option is overriding Bootstrap's style in your site stylesheet. However,
it's hacky to write compilcated selectors to override something in multiple
places when it is set in a single variable in Bootstrap. For example, to
change the gutter width,
<a href="https://github.com/twbs/bootstrap-sass/blob/v3.3.1/assets/stylesheets/bootstrap/_variables.scss#L325">
here is the single variable</a> that needs to be changed.
</p>
<h4 id="approach">Approach</h4>
<p>Here is the approach I took to customize Bootstrap.
I'm running Ubuntu 14.10 Utopic Unicorn 64-bit.
</p>
<ul>
<li>Install the <a href="http://getbootstrap.com/css/#sass">Sass version of Bootstrap</a>
using <a href="https://www.npmjs.com/">npm</a>.
The standard Bootstrap project uses
<a href="http://lesscss.org/">Less</a>
but my limited knowledge of frontend technology tells me
<a href="http://sass-lang.com/">Sass</a> (specifically
<a href="http://sass-lang.com/documentation/file.SCSS_FOR_SASS_USERS.html">SCSS</a>)
is a better choice. I previously used Bower to install Bootstrap, but
I learned that
npm can now be used instead of Bower in most cases.
</li>
<li>Override Bootstrap's Sass variables, remove unneeded components, and combine
Bootstrap with my site's stylesheet using
<a href="http://sass-lang.com/documentation/file.SASS_REFERENCE.html#import">
Sass <code>@imports</code></a>.
</li>
<li>Compile the Sass SCSS files to CSS using
<a href="http://gruntjs.com/">Grunt</a> and
<a href="https://github.com/gruntjs/grunt-contrib-sass">
grunt-contrib-sass</a>.
</li>
<li>Commit the compiled Sass files to git. An alternative is to compile the
files as part of the deploy process. At work,
it is a pain keeping these files in git because there are many merge conflicts and
differences in build tool versions and platforms between developers.
For my blog, I am the only committer so I won't run into this.
</ul>
<h4 id="directory-structure">Project directory structure</h4>
<p>Here is what my project directory structure looks like:</p>
<pre>
my-project
├── Gruntfile.js
├── node_modules
│ ├── bootstrap-sass
│ ├── grunt
│ ├── grunt-contrib-sass
│ └── grunt-contrib-watch
├── package.json
├── sass
│ ├── _bootstrap-variables-override.scss
│ ├── _bootstrap.scss
│ └── mystyle.scss
└── static
└── css
└── mystyle.css
</pre>
<h4 id="install-sass">Install Sass</h4>
<p><a href="http://sass-lang.com/">Sass</a> is installed using
<a href="https://rubygems.org/">RubyGems</a>, the
Ruby package manager.
</a>
<pre class="console">$ sudo apt-get install ruby
$ sudo gem install sass
$ sass --version
Sass 3.4.13 (Selective Steve)
</pre>
<h4 id="install-grunt">Install grunt-cli</h4>
<p>Grunt depends on <a href="https://www.npmjs.com/">npm</a>,
the <a href="http://nodejs.org/">Node.js</a> package manager.</p>
<pre class="console">$ sudo add-apt-repository ppa:chris-lea/node.js
$ sudo apt-get update
$ sudo apt-get install nodejs
$ npm --version
1.4.28
<pre class="console">$ sudo npm install -g grunt-cli
$ grunt --version
grunt-cli v0.1.13
</pre>
<h4 id="install-bootstrap-sass">Install bootstrap-sass with npm</h4>
<p><a href="https://github.com/twbs/bootstrap-sass">bootstrap-sass</a>
is the
<a href="http://getbootstrap.com/css/#sass">official Sass port</a>
of Bootstrap.
I created this package.json file, <code>/tmp/my-project/package.json</code>.
For more information about the package.json file,
see the <a href="https://docs.npmjs.com/files/package.json">npm documentation</a>.
</p>
<pre class="json">{
"name": "my-project",
"version": "0.1.0",
"dependencies": {
"bootstrap-sass": "3.3.1"
}
}</pre>
<p>Then I ran <code>npm install</code> to download the files. The packages are stored in the
<code>node_modules</code> directory.
</p>
<pre class="console">$ cd /tmp/my-project
$ npm install </pre>
<h4 id="customize-bootstrap">Customize Boostrap</h4>
<p>To customize Boostrap, I copied the
<code>node_modules/bootstrap-sass/assets/stylesheets/<a href="https://github.com/twbs/bootstrap-sass/blob/v3.3.1/assets/stylesheets/_bootstrap.scss">boostrap.scss</a></code>
file to my <code>sass</code> directory,
removed the components that I didn't need, and added
a line for my custom variable overrides.
Here is my final file, named
<code>/tmp/my-project/sass/_bootstrap.scss</code>:
</p>
<pre class="sass">
// Variable overrides come first (without !default). Bootstrap default variables
// come second because they use !default (they won't get set if set already) and
// some of them depend on our overrides.
@import "bootstrap-variables-override";
// Core variables and mixins
@import "bootstrap/variables";
@import "bootstrap/mixins";
// Reset and dependencies
@import "bootstrap/normalize";
@import "bootstrap/print";
@import "bootstrap/glyphicons";
// Core CSS
@import "bootstrap/scaffolding";
@import "bootstrap/type";
@import "bootstrap/code";
@import "bootstrap/grid";
@import "bootstrap/tables";
@import "bootstrap/forms";
@import "bootstrap/buttons";
// // I am not using these components so I commented them out to make the CSS file smaller
// // Components
// @import "bootstrap/component-animations";
// @import "bootstrap/dropdowns";
// @import "bootstrap/button-groups";
// @import "bootstrap/input-groups";
// @import "bootstrap/navs";
// @import "bootstrap/navbar";
// @import "bootstrap/breadcrumbs";
// @import "bootstrap/pagination";
// @import "bootstrap/pager";
// @import "bootstrap/labels";
// @import "bootstrap/badges";
// @import "bootstrap/jumbotron";
// @import "bootstrap/thumbnails";
// @import "bootstrap/alerts";
// @import "bootstrap/progress-bars";
// @import "bootstrap/media";
// @import "bootstrap/list-group";
// @import "bootstrap/panels";
// @import "bootstrap/responsive-embed";
// @import "bootstrap/wells";
// @import "bootstrap/close";
// // Components w/ JavaScript
// @import "bootstrap/modals";
// @import "bootstrap/tooltip";
// @import "bootstrap/popovers";
// @import "bootstrap/carousel";
// Utility classes
@import "bootstrap/utilities";
@import "bootstrap/responsive-utilities";
</pre>
<p>The Boostrap variables are located in
<code>node_modules/bootstrap-sass/assets/stylesheets/bootstrap/<a href="https://github.com/twbs/bootstrap-sass/blob/v3.3.1/assets/stylesheets/bootstrap/_variables.scss">_variables.scss</a></code>
I copied the ones that I was interested in into a <code>_bootstrap-variables-override.scss</code> file.
<strong>Be sure to remove the <code>!default</code> flag</strong> and import this file before
the default bootstrap variables. For more information about <code>!default</code>,
see the <a href="http://sass-lang.com/documentation/file.SASS_REFERENCE.html#variable_defaults_">Sass documentation</a>.
Here is my <code>/tmp/my-project/sass/_bootstrap-variables-override.scss</code> file:
</p>
<pre class="sass">// overrides here (do not use !default here)
$grid-gutter-width: 10px;
$font-size-base: 16px;
$headings-font-weight: 800;</pre>
<p>In my site stylesheet, I import <code>_bootstrap.scss</code>. Here is my
<code>/tmp/my-project/sass/mystyle.scss</code> file:
</p>
<pre class="sass">@import "bootstrap";
/* add site style here */
</pre>
<h4 id="install-grunt-plugins">Install grunt plugins</h4>
<p>To compile the Sass files to CSS, I am using
<a href="https://github.com/gruntjs/grunt-contrib-sass">grunt-contrib-sass</a>.
I added a <code>devDependencies</code> section to my package.json file
which contains grunt and two grunt plugins.
Here is my <code>/tmp/my-project/package.json</code>.
For more information about installing plugins,
see the <a href="http://gruntjs.com/getting-started#package.json">Grunt documentation</a>.
</p>
<pre class="json">
{
"name": "my-project",
"version": "0.1.0",
"dependencies": {
"bootstrap-sass": "3.3.1"
},
"devDependencies": {
"grunt": "0.4.5",
"grunt-contrib-sass": "0.8.1",
"grunt-contrib-watch": "0.6.1"
}
}
</pre>
<p>I ran <code>npm install</code> again to install grunt, grunt-contrib-sass,
and grunt-contrib-watch.</p>
<pre class="console">$ cd /tmp/my-project
$ npm install </pre>
<h4 id="gruntfile">Create Gruntfile.js</h4>
<p>My Gruntfile.js uses the
<a href="https://github.com/gruntjs/grunt-contrib-sass">grunt-contrib-sass</a>
plugin to compile the Sass to CSS.
My input file is <code>sass/mystyle.scss</code> and my output file is <code>static/css/mystyle.css</code>.
I set the <code>loadPath</code> so that Sass can find the Bootstrap SCSS files in the node_modules directory to import. Alternatively, I could specify the full path to import in <code>/tmp/my-project/sass/_bootstrap.scss</code>.
For example: <code>@import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/variables";</code>
I am also using the
<a href="https://github.com/gruntjs/grunt-contrib-watch">grunt-contrib-watch</a>
plugin during development to automatically run the compilation whenever a SCSS file changes.
For more information on configuring tasks, see the
<a href="http://gruntjs.com/configuring-tasks">Grunt documentation</a>.
Here is my Gruntfile, <code>/tmp/my-project/Gruntfile.js</code>:
</p>
<pre class="javascript">module.exports = function(grunt) {
grunt.initConfig({
sass: {
// this is the "dev" Sass config used with "grunt watch" command
dev: {
options: {
style: 'expanded',
// tell Sass to look in the Bootstrap stylesheets directory when compiling
loadPath: 'node_modules/bootstrap-sass/assets/stylesheets'
},
files: {
// the first path is the output and the second is the input
'static/css/mystyle.css': 'sass/mystyle.scss'
}
},
// this is the "production" Sass config used with the "grunt buildcss" command
dist: {
options: {
style: 'compressed',
loadPath: 'node_modules/bootstrap-sass/assets/stylesheets'
},
files: {
'static/css/mystyle.css': 'sass/mystyle.scss'
}
}
},
// configure the "grunt watch" task
watch: {
sass: {
files: 'sass/*.scss',
tasks: ['sass:dev']
}
}
});
grunt.loadNpmTasks('grunt-contrib-sass');
grunt.loadNpmTasks('grunt-contrib-watch');
// "grunt buildcss" is the same as running "grunt sass:dist".
// if I had other tasks, I could add them to this array.
grunt.registerTask('buildcss', ['sass:dist']);
};</pre>
<h4 id="run-grunt">Run grunt</h4>
<p>To compile the Sass to CSS for production, I run <code>grunt buildcss</code>. This creates
the output CSS file <code>/tmp/my-project/static/css/mystyle.css</code>.
<pre class="console">$ cd /tmp/my-project
$ grunt buildcss </pre>
<p>During development, I use <code>grunt watch</code> to
build whenever a SCSS (Sass) file changes.</p>
<pre class="console">$ cd /tmp/my-project
$ grunt watch </pre>
<h4 id="notes">Other Notes</h4>
<ul>
<li>I don't commit the <code>node_modules</code> directory to git. Sass uses the Bootstrap files
in node_modules to create the final CSS file.</li>
<li>To configure Bootstrap's Javascript and glyphicons in your project, you can use
<a href="https://github.com/gruntjs/grunt-contrib-concat">grunt-contrib-concat</a>
and <a href="https://github.com/gruntjs/grunt-contrib-copy">grunt-contrib-copy</a>
as spacepope nancho <a href="#comment-2396008796">described in his comment</a>.</li>
</ul>
<h4 id="see-also">See Also / References</h4>
<ul>
<li><a href="http://ryanchristiani.com/getting-started-with-grunt-and-sass/">
Getting started with Grunt and Sass - Ryan Christiani</a></li>
<li><a href="http://www.wearejh.com/development/frontend-automation-with-grunt-sass-browsersync/">
Frontend automation with Grunt, Sass + BrowserSync.</a></li>
<li><a href="http://culttt.com/2013/11/18/setting-sass-grunt/">
Setting up Sass with Grunt | Culttt</a></li>
<li><a href="http://benfrain.com/lightning-fast-sass-compiling-with-libsass-node-sass-and-grunt-sass/">
Lightning fast Sass compiling with libsass, Node-sass and Grunt-sass</a></li>
</ul>
How to install grunt on Ubuntu 14.04
2014-11-22T00:22:13-08:00https://www.saltycrane.com/blog/2014/11/how-install-grunt-ubuntu-1404/<p><a href="http://gruntjs.com/">Grunt</a> is a JavaScript task runner that
can be used to compile
<a href="http://sass-lang.com/">Sass</a>, run
<a href="http://www.jshint.com/">JSHint</a>, or run
<a href="http://gruntjs.com/plugins">many other plugins</a>.
It depends on the
<a href="http://nodejs.org/">Node.js</a>
package manager,
<a href="https://www.npmjs.org/">npm</a>.
</p>
<p>If you use the standard Ubuntu Trusty Tahr repository to install nodejs/npm, you
will get this error when running grunt:
<pre>/usr/bin/env: node: No such file or directory</pre>
Instead, use the
<a href="http://www.ubuntuupdates.org/ppa/chris_lea_nodejs?dist=trusty">chris-lea nodejs repository</a>.</p>
<h4 id="install-nodejs">Install nodejs, npm, and grunt-cli</h4>
<pre class="console">$ sudo add-apt-repository ppa:chris-lea/node.js
$ sudo apt-get update
$ sudo apt-get install nodejs
$ sudo npm install -g grunt-cli </pre>
<h4 id="install-grunt">Install grunt in your project directory</h4>
<pre class="console">$ cd ~/myproject
$ echo "{}" > package.json
$ npm install grunt --save-dev </pre>
<h4 id="verify">Verify grunt is installed</h4>
<pre class="console">$ nodejs --version
v0.10.33
$ npm --version
1.4.28
$ grunt --version
grunt-cli v0.1.13
grunt v0.4.5</pre>
<h4 id="run-task">Run a simple grunt task</h4>
<ol>
<li><pre class="console">$ cd ~/myproject</pre></li>
<li>Create a package.json file:
<pre class="json">{
"name": "my-project-name",
"version": "0.1.0",
"devDependencies": {
"grunt": "~0.4.5",
"grunt-contrib-uglify": "~0.5.0"
}
}</pre>
</li>
<li>Install grunt-contrib-uglify
<pre class="console">$ npm install
npm WARN package.json my-project-name@0.1.0 No description
npm WARN package.json my-project-name@0.1.0 No repository field.
npm WARN package.json my-project-name@0.1.0 No README data
grunt-contrib-uglify@0.5.1 node_modules/grunt-contrib-uglify
├── chalk@0.5.1 (ansi-styles@1.1.0, escape-string-regexp@1.0.2, supports-color@0.2.0, strip-ansi@0.3.0, has-ansi@0.1.0)
├── lodash@2.4.1
├── maxmin@0.2.2 (figures@1.3.5, pretty-bytes@0.1.2, gzip-size@0.2.0)
└── uglify-js@2.4.15 (uglify-to-browserify@1.0.2, async@0.2.10, optimist@0.3.7, source-map@0.1.34)</pre>
</li>
<li>Get an example unminified JS file:
<pre class="console">$ wget http://code.jquery.com/jquery-2.1.1.js
--2014-11-22 00:47:31-- http://code.jquery.com/jquery-2.1.1.js
Resolving code.jquery.com (code.jquery.com)... 94.31.29.53, 94.31.29.230
Connecting to code.jquery.com (code.jquery.com)|94.31.29.53|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 247351 (242K) [application/x-javascript]
Saving to: ‘jquery-2.1.1.js’
100%[================================================================================================================>] 247,351 --.-K/s in 0.1s
2014-11-22 00:47:31 (1.71 MB/s) - ‘jquery-2.1.1.js’ saved [247351/247351]
</pre>
</li>
<li>Create a Gruntfile.js file:
<pre class="javascript">module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
build: {
src: 'jquery-2.1.1.js',
dest: 'jquery-2.1.1.min.js'
}
}
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', ['uglify']);
};</pre>
</li>
<li>Run the grunt task:
<pre class="console">$ grunt
Running "uglify:build" (uglify) task
Done, without errors.</pre>
</li>
<li>You should now have a minified file, jquery-2.1.1.min.js
<pre class="console">$ ls -gG jquery*
-rw-rw-r-- 1 247351 2014 10/23 17:16 jquery-2.1.1.js
-rw-rw-r-- 1 84113 2014 11/22 00:48 jquery-2.1.1.min.js</pre>
</li>
</ol>
<h4>References</h4>
<ul>
<li><a href="https://github.com/joyent/node/issues/3911">https://github.com/joyent/node/issues/3911</a></li>
<li><a href="http://gruntjs.com/getting-started">http://gruntjs.com/getting-started</a></li>
</ul>
A bank style session timeout example using jQuery, Bootstrap, and Flask
2014-09-17T18:38:10-07:00https://www.saltycrane.com/blog/2014/09/bank-style-session-timeout-example-using-jquery-bootstrap-and-flask/<p>
This is an example that uses JavaScript to display a session timeout
warning modal 10 minutes before session expiration. It also resets the
session expiration whenever the user clicks the mouse. It uses
JavaScript,
<a href="https://jquery.com/">jQuery</a>, and
<a href="http://getbootstrap.com/">Bootstrap</a>
on the frontend and Python,
<a href="http://flask.pocoo.org/">Flask</a>,
<a href="https://flask-login.readthedocs.org/en/latest/">Flask-Login</a>, and
<a href="https://wtforms.readthedocs.org/en/latest/">WTForms</a>
on the backend.
</p>
<ul>
<li>Mouse clicks anywhere on the page ping the server at a maximum
frequecy of once per minute and reset the session expiration.</li>
<li>10 minutes before the session expiration, a warning modal is
displayed with two buttons: "Log out" and "Stay Logged In".</li>
<li>If the user clicks "Stay Logged In" the session expiration is reset.</li>
<li>If the user clicks "Log out", the user is logged out.</li>
<li>If the user does nothing for 10 minutes, the user is logged out and
displayed a message that the session timed out.</li>
</ul>
<p>Here is the JavaScript (session-monitor.js):</p>
<pre class="javascript">
sessionMonitor = function(options) {
"use strict";
var defaults = {
// Session lifetime (milliseconds)
sessionLifetime: 60 * 60 * 1000,
// Amount of time before session expiration when the warning is shown (milliseconds)
timeBeforeWarning: 10 * 60 * 1000,
// Minimum time between pings to the server (milliseconds)
minPingInterval: 1 * 60 * 1000,
// Space-separated list of events passed to $(document).on() that indicate a user is active
activityEvents: 'mouseup',
// URL to ping the server using HTTP POST to extend the session
pingUrl: '/ping',
// URL used to log out when the user clicks a "Log out" button
logoutUrl: '/logout',
// URL used to log out when the session times out
timeoutUrl: '/logout?timeout=1',
ping: function() {
// Ping the server to extend the session expiration using a POST request.
$.ajax({
type: 'POST',
url: self.pingUrl
});
},
logout: function() {
// Go to the logout page.
window.location.href = self.logoutUrl;
},
onwarning: function() {
// Below is example code to demonstrate basic functionality. Use this to warn
// the user that the session will expire and allow the user to take action.
// Override this method to customize the warning.
var warningMinutes = Math.round(self.timeBeforeWarning / 60 / 1000),
$alert = $('<div id="jqsm-warning">Your session will expire in ' + warningMinutes + ' minutes. ' +
'<button id="jqsm-stay-logged-in">Stay Logged In</button>' +
'<button id="jqsm-log-out">Log Out</button>' +
'</div>');
if (!$('body').children('div#jqsm-warning').length) {
$('body').prepend($alert);
}
$('div#jqsm-warning').show();
$('button#jqsm-stay-logged-in').on('click', self.extendsess)
.on('click', function() { $alert.hide(); });
$('button#jqsm-log-out').on('click', self.logout);
},
onbeforetimeout: function() {
// By default this does nothing. Override this method to perform actions
// (such as saving draft data) before the user is automatically logged out.
// This may optionally return a jQuery Deferred object, in which case
// ontimeout will be executed when the deferred is resolved or rejected.
},
ontimeout: function() {
// Go to the timeout page.
window.location.href = self.timeoutUrl;
}
},
self = {},
_warningTimeoutID,
_expirationTimeoutID,
// The time of the last ping to the server.
_lastPingTime = 0;
function extendsess() {
// Extend the session expiration. Ping the server and reset the timers if
// the minimum interval has passed since the last ping.
var now = $.now(),
timeSinceLastPing = now - _lastPingTime;
if (timeSinceLastPing > self.minPingInterval) {
_lastPingTime = now;
_resetTimers();
self.ping();
}
}
function _resetTimers() {
// Reset the session warning and session expiration timers.
var warningTimeout = self.sessionLifetime - self.timeBeforeWarning;
window.clearTimeout(_warningTimeoutID);
window.clearTimeout(_expirationTimeoutID);
_warningTimeoutID = window.setTimeout(self.onwarning, warningTimeout);
_expirationTimeoutID = window.setTimeout(_onTimeout, self.sessionLifetime);
}
function _onTimeout() {
// A wrapper that calls onbeforetimeout and ontimeout and supports asynchronous code.
$.when(self.onbeforetimeout()).always(self.ontimeout);
}
// Add default variables and methods, user specified options, and non-overridable
// public methods to the session monitor instance.
$.extend(self, defaults, options, {
extendsess: extendsess
});
// Set an event handler to extend the session upon user activity (e.g. mouseup).
$(document).on(self.activityEvents, extendsess);
// Start the timers and ping the server to ensure they are in sync with the backend session expiration.
extendsess();
return self;
};
</pre>
<p>Here is the important HTML:</p>
<pre class="html">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Login</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
</head>
<body>
<div id="session-warning-modal" class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="sessWarnLabel">Your session is about to expire</h4>
</div>
<div class="modal-body">
Your session will expire in <span id="remaining-time"></span> minutes due to inactivity.
</div>
<div class="modal-footer">
<button id="log-out" class="btn btn-default" type="button" data-dismiss="modal">Log Out</button>
<button id="stay-logged-in" class="btn btn-warning" type="button" data-dismiss="modal">Stay Logged In</button>
</div>
</div>
</div>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="{{ url_for('static', filename='session-monitor.js')}}"></script>
<script type="text/javascript">
// Configure and start the session timeout monitor
sessMon = sessionMonitor({
// Subtract 1 minute to ensure the backend doesn't expire the session first
sessionLifetime: {{ PERMANENT_SESSION_LIFETIME_MS }} - (1 * 60 * 1000),
timeBeforeWarning: 10 * 60 * 1000, // 10 minutes
minPingInterval: 1 * 60 * 1000, // 1 minute
pingUrl: '/ping',
logoutUrl: '/logout',
timeoutUrl: '/logged-out?timeout=1&next=' + encodeURIComponent(
window.location.pathname + window.location.search + window.location.hash),
// The "mouseup" event was used instead of "click" because some of the
// inner elements on some pages have click event handlers that stop propagation.
activityEvents: 'mouseup',
onwarning: function() {
$("#session-warning-modal").modal("show");
}
});
$(document).ready( function() {
// Configure the session timeout warning modal
$("#session-warning-modal")
.modal({
"backdrop": "static",
"keyboard": false,
"show": false
})
.on("click", "#stay-logged-in", sessMon.extendsess)
.on("click", "#log-out", sessMon.logout)
.find("#remaining-time").text(Math.round(sessMon.timeBeforeWarning / 60 / 1000));
});
window.sessMon = sessMon;
</script>
</body>
</html>
</pre>
<p>Here is the Python Flask app (myapp.py):</p>
<pre class="python">import collections
import datetime
from flask import Flask, request, render_template, redirect, url_for, session
from flask.ext.login import (
LoginManager, login_user, logout_user, UserMixin, login_required)
from wtforms.fields import PasswordField, StringField
from wtforms.form import Form
UserRow = collections.namedtuple('UserRow', ['id', 'password'])
TOY_USER_DATABASE = {
'george': UserRow(id=1, password='george'),
}
# settings ###############################################################
# Set a secret key to sign the session (Flask config value)
SECRET_KEY = 'insert secret key here'
# The amount of time after which the user's session expires
# (this is a Flask setting and is also used by the JavaScript)
PERMANENT_SESSION_LIFETIME = datetime.timedelta(minutes=60)
# init ###############################################################
app = Flask(__name__)
app.config.from_object(__name__)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = '.login'
@login_manager.user_loader
def load_user(userid):
return User(userid)
@app.context_processor
def add_session_config():
"""Add current_app.permanent_session_lifetime converted to milliseconds
to context. The config variable PERMANENT_SESSION_LIFETIME is not
used because it could be either a timedelta object or an integer
representing seconds.
"""
return {
'PERMANENT_SESSION_LIFETIME_MS': (
app.permanent_session_lifetime.seconds * 1000),
}
# models ###############################################################
class User(UserMixin):
def __init__(self, id):
self.id = id
# forms ###############################################################
class LoginForm(Form):
username = StringField()
password = PasswordField()
# views ###############################################################
@app.route("/login", methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
message = ''
if request.method == 'POST' and form.validate():
db_user = TOY_USER_DATABASE.get(form.username.data)
if form.password.data == db_user.password:
user = User(db_user.id)
login_user(user)
return redirect(url_for('.home'))
else:
message = 'Login failed.'
context = {
'form': form,
'message': message,
}
return render_template('login.html', **context)
@app.route("/")
@login_required
def home():
return render_template('home.html')
@app.route("/another-page")
@login_required
def another_page():
return render_template('another_page.html')
@app.route("/logout")
@login_required
def logout():
logout_user()
return redirect(url_for('.logged_out') + '?' + request.query_string)
@app.route("/logged-out")
def logged_out():
timed_out = request.args.get('timeout')
return render_template('logged_out.html', timed_out=timed_out)
@app.route("/ping", methods=['POST'])
def ping():
session.modified = True
return 'OK'
if __name__ == "__main__":
app.run(debug=True)
</pre>
<p>The full example is also on github at:
<a href="https://github.com/saltycrane/session-timeout-example">https://github.com/saltycrane/session-timeout-example</a>.
</p>
<p>Initial ideas were taken from
<a href="http://www.itworld.com/development/335546/how-create-session-timeout-warning-your-web-application-using-jquery">
http://www.itworld.com/development/335546/how-create-session-timeout-warning-your-web-application-using-jquery</a>
but, from what I could tell, it pinged the server whether the user was active or not.
</p>
jQuery flot stacked bar chart example
2010-03-11T11:22:58-08:00https://www.saltycrane.com/blog/2010/03/jquery-flot-stacked-bar-chart-example/<p>
<a href="http://code.google.com/p/flot/">Flot</a> is a JavaScript plotting library
for <a href="http://jquery.com/">jQuery</a>. Here are my steps to create a
simplified version of the
<a href="http://people.iola.dk/olau/flot/examples/stacking.html">stacked bar chart
example</a> from the flot <a href="http://people.iola.dk/olau/flot/examples/">examples
page</a>. For more information, see the
<a href="http://code.google.com/p/flot/source/browse/trunk/API.txt">API
documentation</a>.
</p>
<ul>
<li>Download and unpack:
<pre>cd ~/src/jquery/flot_examples
curl http://flot.googlecode.com/files/flot-0.6.tar.gz | tar xzf -</pre>
</li>
<li><code>~/src/jquery/flot_examples/stacked_bar_ex.html</code>:
<pre class="html"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--[if IE]><script language="javascript" type="text/javascript" src="flot/excanvas.min.js"></script><![endif]-->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="flot/jquery.flot.js"></script>
<script type="text/javascript" src="flot/jquery.flot.stack.js"></script>
<title>Flot stacked bar example</title>
</head>
<body>
<div id="placeholder" style="width:550px;height:200px;"></div>
<script type="text/javascript" src="stacked_bar_ex.js"></script>
</body>
</html></pre>
</li>
<li><code>~/src/jquery/flot_examples/stacked_bar_ex.js</code>:
<pre class="javascript">$(function () {
var css_id = "#placeholder";
var data = [
{label: 'foo', data: [[1,300], [2,300], [3,300], [4,300], [5,300]]},
{label: 'bar', data: [[1,800], [2,600], [3,400], [4,200], [5,0]]},
{label: 'baz', data: [[1,100], [2,200], [3,300], [4,400], [5,500]]},
];
var options = {
series: {stack: 0,
lines: {show: false, steps: false },
bars: {show: true, barWidth: 0.9, align: 'center',},},
xaxis: {ticks: [[1,'One'], [2,'Two'], [3,'Three'], [4,'Four'], [5,'Five']]},
};
$.plot($(css_id), data, options);
});</pre>
</li>
<li>View <code>~/src/jquery/flot_examples/stacked_bar_ex.html</code> in the browser:
<a href="http://picasaweb.google.com/lh/photo/VYWmzEk0TqA5FXFBiMJkRg?feat=embedwebsite"><img src="http://lh3.ggpht.com/_WnP2PKiLI14/S869cZAbYkI/AAAAAAAAAHo/sNiLx_2VPfo/s800/flot_stacked_chart_screenshot3.png"></a>
</li>
</ul>
Emacs espresso-mode for jQuery
2010-03-10T19:25:47-08:00https://www.saltycrane.com/blog/2010/03/emacs-espresso-mode-jquery/<p>Because <a href="http://code.google.com/p/js2-mode/">js2-mode</a> (20090723b)
indents <a href="http://jquery.com/">jQuery</a> like this:</p>
<pre class="js">$(document).ready(function() {
$("a").click(function() {
alert("Hello World");
});
});</pre>
<p>instead of this:</p>
<pre class="js">$(document).ready(function() {
$("a").click(function() {
alert("Hello World");
});
});</pre>
<p>I've switched to <a href="http://www.nongnu.org/espresso/">espresso-mode</a>.
Here's my install notes:</p>
<ul>
<li>Download
<pre>$ cd ~/.emacs.d/site-lisp
$ wget http://download.savannah.gnu.org/releases-noredirect/espresso/espresso.el</pre>
</li>
<li>Edit your <code>.emacs</code>:
<pre>(add-to-list 'load-path "~/.emacs.d/site-lisp")
(autoload #'espresso-mode "espresso" "Start espresso-mode" t)
(add-to-list 'auto-mode-alist '("\\.js$" . espresso-mode))
(add-to-list 'auto-mode-alist '("\\.json$" . espresso-mode))</pre>
</li>
<li>Start emacs and byte-compile espresso.el:
<pre>M-x byte-compile-file RET ~/.emacs.d/site-lisp/espresso.el</pre>
</li>
</ul>
<p><em>Side note:</em> I just realized it is "espresso-mode" and not "expresso-mode".</p>
Google jQuery hosting (very short version)
2010-03-10T18:15:55-08:00https://www.saltycrane.com/blog/2010/03/google-jquery-hosting-very-short-version/<p>Via <a href="http://encosia.com/2008/12/10/3-reasons-why-you-should-let-google-host-jquery-for-you/">this
article</a>, use the Google-hosted jQuery instead of serving it yourself:</p>
<pre><script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script></pre>
<p>Here's the <a href="http://code.google.com/apis/ajaxlibs/documentation/#jquery">info
from Google</a>.</p>