Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support client-only initialization and more #28

Merged
merged 30 commits into from
Dec 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a99da97
Create ability to specify client-only init actions
Nov 26, 2018
fa2e56b
Create environment for integration tests
Nov 28, 2018
743b098
Create separate command for integration tests
Nov 28, 2018
1eae99d
Fix linting in IsomorphicTestEnvironment
Nov 28, 2018
5cda9e0
Update jest to latest version
Nov 28, 2018
77d97fc
Fix determining if initAction should be called
Nov 28, 2018
c0b4c42
Create client-side rendering test
Nov 28, 2018
c5e32f6
Add test for changing initParams
Nov 28, 2018
cae6782
Use SimpleInitTestComponent in tests
Nov 30, 2018
6a0e1f4
Move didMount test to correct describe group
Nov 30, 2018
38bab89
Add unit tests for withInitAction clientOnly param
Nov 30, 2018
a624597
Don't call clientOnly action in MODE_PREPARE
Nov 30, 2018
a4002be
Only run prepare validation with prepared action
Nov 30, 2018
2959156
Add unit initComponent tests for clientOnly action
Nov 30, 2018
e4393ad
Use snapshot testing for assertion on actions
Nov 30, 2018
4f74ff4
Add prettier to format code
Nov 30, 2018
4373460
Move common store test state to fixtures
Nov 30, 2018
ba20883
Add test and warning for prepareComponent
Nov 30, 2018
f09f423
Remove yarn lock
Nov 30, 2018
1687fc8
Integration test for clientOnly with reinitialize
Nov 30, 2018
aab6f20
Fix clientOnly action not calling on didMount
Nov 30, 2018
342a534
Fix isInitializing prop for clientOnly actions
Nov 30, 2018
97006fa
Remove redundant render update calls
Dec 3, 2018
c23bf4b
Merge branch 'master' into feature/support-clientonly-init
Dec 3, 2018
d080ff1
Add h1 headers to each doc page
Dec 3, 2018
d459338
Merge branch 'master' into feature/support-clientonly-init
Dec 3, 2018
d13a9bc
Start writing usage guide in docs
Dec 5, 2018
57ebd34
Finalize usage guide
flut1 Dec 6, 2018
af964be
Fix typo in usage guide
flut1 Dec 7, 2018
f6b5b2d
Update API docs
flut1 Dec 12, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"transform-class-properties",
"transform-object-rest-spread",
"transform-export-extensions",
"transform-strict-mode"
"transform-strict-mode",
["babel-plugin-transform-builtin-extend", {
"globals": ["Error", "Array"]
}]
]
}
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"parser": "babel-eslint",
"extends": "airbnb",
"extends": ["airbnb", "prettier"],
"plugins": [
"import",
"react"
Expand Down
5 changes: 5 additions & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
trailingComma: 'all',
singleQuote: true,
printWidth: 100,
};
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins:
relative_links:
enabled: true
collections: true
remote_theme: pmarsceill/just-the-docs
remote_theme: flut1/just-the-docs
repository: mediamonks/react-redux-component-init
aux_links:
"view it on GitHub":
Expand Down
167 changes: 131 additions & 36 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
---
title: API
nav_order: 4
nav_order: 5
---
{::comment}


# View the rendered version of this documentation at:
https://mediamonks.github.io/react-redux-component-init



{:/comment}
# API
{: .no_toc }

Expand All @@ -12,37 +20,66 @@ nav_order: 4
## withInitAction `([initProps], initAction, [options])(Component)`
{: #withInitAction }

Higher-order component that adds initialization configuration to an existing component.
- `initProps` `{Array<string>}` _(optional)_ An array of names of `props` that are relevant for initialization.
- Only the values of these props are available in the `initAction` function
- On component mount, a value is required for each of these props
- The values that these props will have on mount need to be provided to `prepareComponent()`
- Component preparation using `withPrepare()` only executes once for each combination of these props. Duplicate calls (with the same `Component` and the same values for `initProps`) will be ignored.
- By default, if these props change value on the client, the component will "re-initialize". See `options` below
- Dot notation can be used to define a subset of an object prop. For example, when using `['foo.bar', 'foo.foobar']` the `initAction` will only get the properties `bar` and `foobar` on the `foo` prop.
- `initAction` `{(props, dispatch, getState) => Promise}` This is the actual initialization function. This function **must return a Promise** that resolves when initialization is complete. It receives the following arguments:
- `props` `{object}` An object containing values of the props defined in `initProps`. If `initProps` is not defined, this is an empty object.
- `dispatch` `{function}` The Redux dispatch function. This can be used to dispatch initialization actions or dispatch the `withPrepare()` action for child components
- `getState` `{function}` The Redux getState function.
- `options` `{object}` _(optional)_ An object containing additional options:
- `allowLazy` If `true`, no error will be thrown when the component is mounted without being prepared using `prepareComponent()` first. Instead, the `initAction` will be performed on `componentDidMount` on the client, as if it wasn't mounted on first render. This can be used to do non-critical initialization, like loading data for components that display below the fold. _Defaults to `false`_
- `reinitialize` If `true`, will call `initAction` again if any of the props defined in `initProps` change after mount. This change is checked with strict equality (===) _Defaults to `true`_
- `initSelf` A string that indicates the behavior for initialization on the client (`initMode == MODE_INIT_SELF`). Possible values:
- `"ASYNC"` _(default)_ the component will render immediately, even if `initAction` is still pending. It is recommended to use this option and render a loading indicator or placeholder content until `initAction` is resolved. This will give the user immediate feedback that something is being loaded. While the `initAction` is pending, an `isInitializing` prop will be passed to the component.
- `"BLOCKING"` this will cause this higher-order component not tot mount the target component until the first initialization has completed. The component will remain mounted during further re-initialization.
- `"UNMOUNT"` same as `"BLOCKING"` but it will also unmount the component during re-initialization.
- `"NEVER"` will only initialize on the server (`initMode == MODE_PREPARE`). Initialization will be skipped on the client.
- `onError` Error handler for errors in `initAction`. If given, errors will be swallowed.
- `getPrepareKey` A function that generates a "prepare key" that will be used to uniquely identify a component and its props. It has the following signature:
```({string} componentId, {Array} propsArray) => {string}```
This defaults to a function that concatenates the `componentId` and the stringified `propsArray`. In most cases, this will ensure that a component instance on the server is matched to the corresponding instance on the client. However, if the props are somehow always different between server and client, you may use this function to generate a key that omits that difference.
- `getInitState` A function that takes the Redux state and returns the init state of the reducer from this module. By default, it is assumed the state is under the `init` property. If the reducer is included elsewhere, this function can be set to retrieve the state.
Higher-order component that adds an _init action_ to an existing component.

### Arguments
{: .no_toc }

##### `initProps: Array<string>`{: .fs-4 } **optional**{: .label .label-green}
{: .no_toc }

An array of names of React props that your init action depends on
- On component mount, a value is required for each of these props
- The values for these props need to be provided to
`prepareComponent()` (see [basic usage](./usage/basic-usage))
- By default the component will "re-initialize" if these props change value on the client.
See `reinitialize` in `options` below
- Dot notation can be used to define a subset of an object prop. For example, when using
`['foo.bar', 'foo.foobar']`{: style="white-space: nowrap"} the `initAction` will only get the properties `bar` and `foobar`
on the `foo` prop. (see [using init props](./usage/using-init-props))

##### `initAction: Function | { [clientOnly]: Function, [prepared]: Function }`{: .fs-4 }
{: .no_toc }

This is the actual initialization function with signature
`(props, dispatch, getState) => Promise`{: style="white-space: nowrap"}

- [basic usage](./usage/basic-usage) For regular init actions you can pass a single function here.
- [client-only](./usage/client-only) If some part of you init action needs to be executed on
client-side only, you can pass an object with the client-side initialization function on the `clientOnly`
property, and (optionally) the server prepared initialization on the `prepared` property.

The function(s) **must return a Promise** that resolves when initialization is complete. The following
arguments are passed:
- `props: Object` An object containing values of the props defined in `initProps`. If `initProps`
is not defined, this is an empty object.
- `dispatch: Function` The Redux dispatch function. This can be used to dispatch your redux
actions or dispatch the `prepareComponent()` action for child components
- `getState: Function` The Redux getState function.

##### `options: Object`{: .fs-4 } **optional**{: .label .label-green}
{: .no_toc }

An object with additional options.
- `allowLazy` If `true`, no error will be thrown when the component is mounted without being prepared using `prepareComponent()` first. Instead, the `initAction` will be performed on `componentDidMount` on the client, as if it wasn't mounted on first render. This can be used to do non-critical initialization, like loading data for components that display below the fold. _Defaults to `false`_
- `reinitialize` If `true`, will call `initAction` again if any of the props defined in `initProps` change after mount. This change is checked with strict equality (===) _Defaults to `true`_
- `initSelf` A string that indicates the behavior for initialization on the client (`initMode == MODE_INIT_SELF`). Possible values:
- `"ASYNC"`{: style="color: #6A8759" } **default**{: .label .label-yellow} the component will render immediately, even if `initAction` is still pending. It is recommended to use this option and render a loading indicator or placeholder content until `initAction` is resolved. This will give the user immediate feedback that something is being loaded. While the `initAction` is pending, an `isInitializing` prop will be passed to the component.
- `"BLOCKING"`{: style="color: #6A8759" } this will cause this higher-order component not tot mount the target component until the first initialization has completed. The component will remain mounted during further re-initialization.
- `"UNMOUNT"`{: style="color: #6A8759" } same as `"BLOCKING"`{: style="color: #6A8759" } but it will also unmount the component during re-initialization.
- `"NEVER"`{: style="color: #6A8759" } will only initialize on the server (`initMode == MODE_PREPARE`). Initialization will be skipped on the client.
- `onError` Error handler for errors in `initAction`. If given, errors will be swallowed.
- `getPrepareKey: (componentId: string, propsArray: Array) => string` A function that generates a
"prepare key" that will be used to uniquely identify a component and its props. It has the
following signature:
This defaults to a function that concatenates the `componentId` and the stringified `propsArray`. In most cases, this will ensure that a component instance on the server is matched to the corresponding instance on the client. However, if the props are somehow always different between server and client, you may use this function to generate a key that omits that difference.
- `getInitState` A function that takes the Redux state and returns the init state of the reducer from this module. By default, it is assumed the state is under the `init` property. If the reducer is included elsewhere, this function can be set to retrieve the state.

### example
{: .no_toc }

``` jsx
// PostComponent.js
// Post.js
class Post extends React.Component {
// ...
}
Expand All @@ -54,7 +91,7 @@ export default withInitAction(
)(Post);

// PostPage.js
import Post from './components/PostComponent';
import Post from './components/Post';
// ...
class PostPage extends React.Component {
// ...
Expand All @@ -75,19 +112,77 @@ export default withInitAction(
## prepareComponent `(Component, props)`
{: #prepareComponent }

Action creator to prepare a component for rendering on the server side (`initMode == MODE_PREPARE`). Should be passed to the Redux dispatch function. Returns a Promise that resolves when preparation is complete
- `Component` `{react.Component}` The component that should be prepared. This should be a component returned by the `withInitAction` higher-order component. If no `withInitAction` wrapper is around the Component, dispatching this action will have no effect.
- `props` `{object}` The props to prepare the component with. These should be the same props as you expect to pass when you eventually render component. It should at least include the props configured in the `initProps` array of `withInitAction`.
### Arguments
{: .no_toc }

Action to prepare a component for rendering on the server side (`initMode == MODE_PREPARE`). Should
be passed to the Redux dispatch function. Returns a Promise that resolves when preparation is
complete

##### `Component: react.Component`{: .fs-4 }
{: .no_toc }
The component that should be prepared. This should be a component returned by the `withInitAction`
higher-order component. If `Component` has no `withInitAction` wrapper or only has a `clientOnly`
action, dispatching this action will have no effect.


##### `props: object`{: .fs-4 }
{: .no_toc }
The props to prepare the component with. These should be the same props as you expect to pass when
you eventually render component. It should at least include the props configured in the `initProps`
array of `withInitAction`.
Duplicate calls to `prepareComponent()` with the same `Component` and `props` will be ignored.

### Example
{: .no_toc }
```javascript
dispatch(prepareComponent(MyComponent, { id: 45 }))
```

## prepareComponents `(components, props)`
{: #prepareComponents }

A shorthand action creator for multiple `prepareComponent` calls with the same `props`. Returns a Promise that resolves when preparation for all components is complete
- `components` `{Array<react.Component>}` An array of components to prepare
- `props` `{object}` The props to prepare with
A shorthand action creator for multiple `prepareComponent()` calls with the same `props`. Returns a
Promise that resolves when preparation for all components is complete

### Arguments
{: .no_toc }

##### `Components: Array<react.Component>`{: .fs-4 }
{: .no_toc }

An array of components to prepare


##### `props: object`{: .fs-4 }
{: .no_toc }

The props to prepare with. See [prepareComponent()](#prepareComponent)

### Example
{: .no_toc }
```javascript
dispatch(prepareComponents([SomeComponent, OtherComponent], { id: 45 }))
```

## setInitMode `(initMode)`
{: #setInitMode }

An action creator to switch the `initMode` of the application. Should be called with `MODE_INIT_SELF` after the initial render on the client.
- `initMode` `{string}` Either of the modes `MODE_PREPARE` or `MODE_INIT_SELF` as defined in the `initMode` export of this module
An action to switch the `initMode` of the application. Should be called with `MODE_INIT_SELF` after
the initial render on the client.

### Arguments
{: .no_toc }

##### `initMode: string`{: .fs-4 }
{: .no_toc }
Either of the modes `MODE_PREPARE` or `MODE_INIT_SELF` as defined in the `initMode` export of
this module

### Example
{: .no_toc }
```javascript
import { initMode, setInitMode } from 'react-redux-component-init';
// ...
dispatch(setInitMode(initMode.MODE_INIT_SELF));
```
23 changes: 15 additions & 8 deletions docs/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ nav_order: 2

# Core concepts

Below is a technical explanation of the implementation for this library. For quick
setup instructions, [skip to setup](./setup.md)
Below is an in-depth explanation of the implementation for this library. For quick
setup instructions, see [setup](./setup.md). If you just want to know how to use this library
once it has been setup, follow the [usage guide](./usage)
{: .bg-yellow-000.p-3 }

## Initialization lifecycle
Expand All @@ -27,14 +28,19 @@ We don't start rendering until all components have been initialized.
all components configured with `withInitAction()` will throw an error if mounted without preparing
it first.

[^1]: Note: components configured with `allowLazy` may skip this step. For more info see the [withInitAction() API docs](./api.html#withInitAction)
[^1]: Note: components configured with `allowLazy` may skip this step. For more info see
the [withInitAction() API docs](./api.html#withInitAction)

#### Client side
We don't want to redo initialization that has already been done on the server. When new components mount (for example, on client-side navigation), they should be initialized as well.
We don't want to redo initialization that has already been done on the server. When new components
mount (for example, on client-side navigation), they should be initialized as well.

1. **First render** this is essentially the same as step 4 on the server side. All component preparation has already been done on the server.
2. **Set initMode** we dispatch `setInitMode(MODE_INIT_SELF)` to indicate that new components should initialize themselves as soon as they mount.
3. **Next render(s)** If a new component wrapped in `withInitAction()` mounts, it will automatically initialize. Additionally, a component can also be configured to re-initialize if its `props` update.
1. **First render** this is essentially the same as step 4 on the server side. All component
preparation has already been done on the server.
2. **Set initMode** we dispatch `setInitMode(MODE_INIT_SELF)` to indicate that new components
should initialize themselves as soon as they mount.
3. **Next render(s)** If a new component wrapped in `withInitAction()` mounts, it will automatically
initialize. Additionally, a component can also be configured to re-initialize if its `props` update.

Note: By default, the component will start rendering even if the `initAction` has not completed yet.
For more info see the [withInitAction() API docs](./api.html#withInitAction)
Expand Down Expand Up @@ -67,7 +73,8 @@ We use `withInitAction()` to add the following initialization to our components:

![Example prepare tree](./assets/example-prepare-tree.png)

Note: In this example, the list of posts are loaded separately from the post detail data. In another application this might be a single call
Note: In this example, the list of posts are loaded separately from the post detail data. In another
application this might be a single call
{: .bg-grey-lt-100.p-3 }

---
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ nav_order: 1
{::comment}


View the rendered version of this documentation at:
# View the rendered version of this documentation at:
https://mediamonks.github.io/react-redux-component-init


Expand Down
7 changes: 3 additions & 4 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
title: Setup
nav_order: 3
---

# Setup

Make sure you have an existing setup with the prerequisites listed [here](./index.html#prerequisites)
Expand Down Expand Up @@ -30,8 +29,8 @@ the [withInitAction() HOC](./api.html#withInitAction)
{: .bg-grey-lt-100.p-3 }

## Server side page rendering
In the function that renders your page on the server, call `prepareComponent` with the page
components you will render before you render your page. The example below is
In the function that renders your page on the server, call `prepareComponent` with the top-level
page component(s) and the props that will be passed to them. The example below is
using [express](https://expressjs.com/) and
[react-router](https://github.com/ReactTraining/react-router) 3, but these are not required.

Expand Down Expand Up @@ -71,4 +70,4 @@ store.dispatch(setInitMode(MODE_INIT_SELF));

---

[Continue reading: API](./api.md){: .btn .btn-purple }
[Continue reading: Usage guide](./usage){: .btn .btn-purple }
Loading