Skip to content

Commit

Permalink
Upgrades to react-tree-walker v4.
Browse files Browse the repository at this point in the history
Adds the ability to specify a React Context that will be available to
your Components within the bootstrapping process.
Deprecates "asyncBootstrap" method on your components in favour of
"bootstrap" alternative name. Your "asyncBootstrap" methods will
still work, however you will get a deprecation warning printed.
Adds a bunch more documentation.
  • Loading branch information
ctrlplusb committed Mar 21, 2018
1 parent 7704326 commit a580f32
Show file tree
Hide file tree
Showing 5 changed files with 747 additions and 390 deletions.
93 changes: 63 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# react-async-bootstrapper 👢

Provides the ability to execute async bootstrapper functions within your React element tree.
Execute a bootstrap method on your React/Preact components. Useful for data prefetching and other activities.

[![npm](https://img.shields.io/npm/v/react-async-bootstrapper.svg?style=flat-square)](http://npm.im/react-async-bootstrapper)
[![MIT License](https://img.shields.io/npm/l/react-async-bootstrapper.svg?style=flat-square)](http://opensource.org/licenses/MIT)
Expand All @@ -9,19 +9,19 @@ Provides the ability to execute async bootstrapper functions within your React e

## TOCs

- [Introduction](#introduction)
- [FAQs](#faqs)
* [Introduction](#introduction)
* [Simple Example](#simple-example)

## Introduction

This library is an abstraction of [`react-tree-walker`](https://github.com/ctrlplusb/react-tree-walker), that allows you to attach an `asyncBootstrap` method to your React "class" components.
This library is a simple implementation of [`react-tree-walker`](https://github.com/ctrlplusb/react-tree-walker), allowing you to attach a `bootstrap` method to your React/Preact "class" components. I would highly recommend you review `react-tree-walkers` documentation so as to gain more familiarity with what is being wrapped up by `react-bootstrapper`.

Within the `asyncBootstrap` you can do any asynchronous work you like (e.g. fetching data) that you like, returning a `Promise` to indicate when the asynchronous work has completed.
I have created this implementation that responds to a `bootstrap` method to allow me to have a standard implementation that would allow for interop between multiple activities requiring a bootstrapping process. For example I have create [`react-async-component`](https://github.com/ctrlplusb/react-async-component) which provides code splitting support, and [`react-jobs`](https://github.com/ctrlplusb/react-jobs) which provides a mechanism to executed data fetching. Both use this library allowing for a single bootstrapping parse to be executed for the needs of both.

## Naive Example
## Simple Example

```jsx
import asyncBootstrapper from 'react-async-bootstrapper'
import bootstrapper from 'react-async-bootstrapper'

// Our super naive global state. Don't copy this, it's just illustrative. You'd
// likely want to use something
Expand All @@ -30,29 +30,20 @@ const globalStateManager = {
}

class Product extends Component {
 // 👇
 asyncBootstrap() {
if (globalStateManager.products[this.props.productId]) {
// Already have data
return
}

// 👇
bootstrap() {
// Fetch our product and load up our state
return fetch(`/api/products/${this.props.productId}`)
.then((response) => {
// store in our global state
globalStateManager.products[this.props.productId] = response.json()
// Indicates our desire to allow for nested asyncBootstrap instances
// to be located/resolved
return true
})
return fetch(`/api/products/${this.props.productId}`).then(response => {
// store in our global state
globalStateManager.products[this.props.productId] = response.json()
})
}

render() {
const product = globalStateManager.products[this.props.productId]
return (
<div>
{product.name} - {product.price}
{product.name} - {product.price}
</div>
)
}
Expand All @@ -66,14 +57,56 @@ const app = (
)

// Now for the bootstrapping/rendering process (on a client/server)
asyncBootstrapper(app).then(() => {
// bootstrapping complete
ReactDOM.render(app, document.getElementById('app'))
})
bootstrapper(app)
.then(() => {
// Bootstrapping is complete, now when we render our application to the DOM
// the global products state will be populated and so our components
// should render immediately with the data.
ReactDOM.render(app, document.getElementById('app'))
})
.catch(err => console.log('Eek, error!', err))
```

Yep, not a particularly useful idea in the context of executing on the front end only, but when doing server side rendering of your react application this pattern can be extremely useful.

## API

The API is very simple at the moment, only exposing a single function.

### **bootstrapper**

The default export of the library. The function that performs the magic.

```javascript
const bootstrapper = require('react-async-bootstrapper')
```

_or_

```javascript
import bootstrapper from 'react-async-bootstrapper'
```

Zing. You can do anything you like. And interplay with other libaries that support `react-async-bootstrapper`, such as [`react-async-component`](https://github.com/ctrlplusb/react-async-component) which provides code splitting support.
**Paramaters**

* **app** (React/Preact application/element, _required_)

The react application you wish to walk.

e.g. `<div>Hello world</div>`

* **options** (`Object`, _optional_)

Additional options/configuration. It currently supports the following values:

* _componentWillUnmount_: Enable this to have the `componentWillUnmount` lifecycle event be executed during the bootstrapping process. Defaults to `false`. This was added as an experimental additional flag to help with applications where they have critical disposal logic being executed within the `componentWillUnmount` lifecycle event.

* **context** (`Object`, _optional_)

Any context you wish to expose to your application. This will become available to the entire application and could be useful for exposing configuration to your `bootstrap` methods.

e.g. `{ myContextItem: 'foo' }`

## FAQs
**Returns**

> Let me know if you have any questions
A `Promise` that resolves when the bootstrapping has completed.
19 changes: 9 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "react-async-bootstrapper",
"version": "1.1.2",
"version": "2.0.0",
"description":
"Provides the ability to execute async bootstrapper functions within your React element tree.",
"Execute a bootstrap method on your React/Preact components. Useful for data prefetching and other activities.",
"license": "MIT",
"main": "commonjs/index.js",
"main": "dist/react-async-bootstrapper.js",
"files": ["*.js", "*.md", "dist"],
"repository": {
"type": "git",
Expand All @@ -15,8 +15,7 @@
"keywords": ["library"],
"scripts": {
"build": "node ./tools/scripts/build.js",
"clean":
"rimraf ./commonjs && rimraf ./umd && rimraf ./coverage && rimraf ./umd",
"clean": "rimraf ./dist && rimraf ./coverage",
"lint": "eslint src",
"precommit": "lint-staged && npm run test",
"prepublish": "npm run build",
Expand Down Expand Up @@ -56,22 +55,22 @@
"gzip-size": "^4.0.0",
"husky": "^0.14.3",
"in-publish": "2.0.0",
"jest": "^21.2.1",
"lint-staged": "^4.2.3",
"jest": "^22.4.2",
"lint-staged": "^7.0.0",
"prettier": "^1.7.4",
"pretty-bytes": "4.0.2",
"ramda": "^0.25.0",
"react": "^16.0.0",
"react-addons-test-utils": "^15.6.2",
"react-dom": "^16.0.0",
"readline-sync": "1.4.7",
"readline-sync": "1.4.9",
"rimraf": "^2.6.2",
"rollup": "^0.56.5",
"rollup": "^0.57.1",
"rollup-plugin-babel": "^3.0.3",
"rollup-plugin-uglify": "^3.0.0"
},
"dependencies": {
"react-tree-walker": "^2.1.3"
"react-tree-walker": "^4.0.2"
},
"jest": {
"collectCoverageFrom": ["src/**/*.{js,jsx}"],
Expand Down
73 changes: 48 additions & 25 deletions src/__tests__/asyncBootstrapper.test.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,60 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/no-multi-comp */

import React, { Component } from 'react'
import asyncBootstrapper from '../'

describe('asyncBootstrapper()', () => {
it('works', () => {
const values = []

class Foo extends Component {
asyncBootstrap() {
values.push(this.props.id)
return true
}

render() {
return <div>{this.props.children}</div>
}
let values = []
let actualContext

class DeprecatedAPI extends Component {
asyncBootstrap() {
values.push(this.props.id)
return true
}

render() {
return <div>{this.props.children}</div>
}
}

const app = (
<Foo id={1}>
<div>
<h1>Test</h1>
</div>
<Foo id={2}>
<Foo id={4} />
</Foo>
<Foo id={3} />
class NewAPI extends Component {
bootstrap() {
values.push(this.props.id)
actualContext = this.context.isBootstrapping
return true
}

render() {
return <div>{this.props.children}</div>
}
}

const app = Foo => (
<Foo id={1}>
<div>
<h1>Test</h1>
</div>
<Foo id={2}>
<Foo id={4} />
</Foo>
)
<Foo id={3} />
</Foo>
)

return asyncBootstrapper(app).then(() =>
expect(values).toEqual([1, 2, 4, 3]),
)
beforeEach(() => {
values = []
})

it('deprecated API', () =>
asyncBootstrapper(app(DeprecatedAPI)).then(() =>
expect(values).toEqual([1, 2, 4, 3]),
))

it('new API', () =>
asyncBootstrapper(app(NewAPI), null, { isBootstrapping: true }).then(() => {
expect(values).toEqual([1, 2, 4, 3])
expect(actualContext).toBe(true)
}))
})
17 changes: 13 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import reactTreeWalker from 'react-tree-walker'

export default function asyncBootstrapper(app, options) {
const warnmsg =
'Deprecation notice: you are using the deprecated "asyncBootsrap" for "react-async-bootstrapper", please rename these to "bootstrap"'

export default function asyncBootstrapper(app, options, context = {}) {
const visitor = (element, instance) => {
if (instance && typeof instance.asyncBootstrap === 'function') {
return instance.asyncBootstrap()
if (
instance &&
(typeof instance.asyncBootstrap === 'function' ||
typeof instance.bootstrap === 'function')
) {
return typeof instance.bootstrap === 'function'
? instance.bootstrap()
: console.log(warnmsg) || instance.asyncBootstrap()
}
}

return reactTreeWalker(app, visitor, {}, options)
return reactTreeWalker(app, visitor, context, options)
}
Loading

0 comments on commit a580f32

Please sign in to comment.