Skip to content

Commit 81cea2b

Browse files
committed
Update README.md
1 parent 975f88a commit 81cea2b

File tree

1 file changed

+168
-43
lines changed

1 file changed

+168
-43
lines changed

README.md

Lines changed: 168 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,89 +19,164 @@ A live-editing time travel environment for [Redux](https://github.com/rackt/redu
1919
* If the reducers throw, you will see during which action this happened, and what the error was
2020
* With `persistState()` store enhancer, you can persist debug sessions across page reloads
2121

22+
### Overview
23+
24+
Redux DevTools is a development time package that provides power-ups for your Redux development workflow. Be careful to strip its code in production! To use Redux DevTools, you need to choose a “monitor”—a React component that will serve as a UI for the DevTools. Different tasks and workflows require different UIs, so Redux DevTools is built to be flexible in this regard. We recommend using [`LogMonitor`](https://github.com/gaearon/redux-devtools-log-monitor) for inspecting the state and time travel, and wrap it in a [`DockMonitor`](https://github.com/gaearon/redux-devtools-dock-monitor) to quckly move it across the screen. That said, when you’re comfortable rolling up your own setup, feel free to do this, and share it with us.
25+
2226
### Installation
2327

2428
```
2529
npm install --save-dev redux-devtools
2630
```
2731

28-
DevTools is a [store enhancer](http://rackt.github.io/redux/docs/Glossary.html#store-enhancer), which should be added to your middleware stack *after* [`applyMiddleware`](http://rackt.github.io/redux/docs/api/applyMiddleware.html) as `applyMiddleware` is potentially asynchronous. Otherwise, DevTools won’t see the raw actions emitted by asynchronous middleware such as [redux-promise](https://github.com/acdlite/redux-promise) or [redux-thunk](https://github.com/gaearon/redux-thunk).
32+
You’ll also likely want to install some monitors:
33+
34+
```
35+
npm install --save-dev redux-devtools-log-monitor
36+
npm install --save-dev redux-devtools-dock-monitor
37+
```
38+
39+
### Usage
40+
41+
#### Create a `DevTools` Component
2942

30-
To use, first create a `DevTools` component by passing a `monitor` component to `createDevTools`. In the following example our `monitor` consists of [`redux-devtools-log-monitor`](https://github.com/gaearon/redux-devtools-log-monitor) docked within [`redux-devtools-dock-monitor`](https://github.com/gaearon/redux-devtools-dock-monitor):
43+
Somewhere in your project, create a `DevTools` component by passing a `monitor` element to `createDevTools`. In the following example our `monitor` consists of [`LogMonitor`](https://github.com/gaearon/redux-devtools-log-monitor) docked within [`DockMonitor`](https://github.com/gaearon/redux-devtools-dock-monitor):
3144

32-
####containers/DevTools.js
45+
##### `containers/DevTools.js`
3346

3447
```js
3548
import React from 'react';
3649

37-
// createDevTools takes a monitor and produces a DevTools component
50+
// Exported from redux-devtools
3851
import { createDevTools } from 'redux-devtools';
3952

40-
// Monitor component for Redux DevTools
53+
// Monitors are separate packages, and you can make a custom one
4154
import LogMonitor from 'redux-devtools-log-monitor';
42-
43-
// Dock component to contain a Redux DevTools monitor
4455
import DockMonitor from 'redux-devtools-dock-monitor';
4556

46-
export default createDevTools(
47-
<DockMonitor toggleVisibilityKey='H'
48-
changePositionKey='Q'>
49-
<LogMonitor />
57+
// createDevTools takes a monitor and produces a DevTools component
58+
const DevTools = createDevTools(
59+
// Monitors are individually adjustable with props
60+
// Consult their repositories to learn about those props
61+
<DockMonitor toggleVisibilityKey='ctrl-h'
62+
changePositionKey='ctrl-q'>
63+
<LogMonitor theme='tomorrow' />
5064
</DockMonitor>
5165
);
66+
67+
export default DevTools;
68+
```
69+
70+
Note that you can use `LogMonitor` directly without wrapping it in `DockMonitor` if you’d like to display the DevTools UI somewhere right inside your application:
71+
72+
```js
73+
// If you'd rather not use docking UI, use <LogMonitor> directly
74+
const DevTools = createDevTools(
75+
<LogMonitor theme='solarized' />
76+
);
5277
```
5378

54-
Note that it is not essential to put [`redux-devtools-log-monitor`](https://github.com/gaearon/redux-devtools-log-monitor) inside the dock component, it can be placed wherever you like in the component tree.
79+
#### Use `DevTools.instrument()` Store Enhancer
80+
81+
The `DevTools` component you created with `createDevTools()` has a special static method called `instrument()`. It returns a [store enhancer](http://rackt.github.io/redux/docs/Glossary.html#store-enhancer) that you need to use in development.
82+
83+
A store enhancer is a function that takes `createStore` and returns an enhanced version of it that you will use instead. You probably already used another store enhancer—[`applyMiddleware()`](http://redux.js.org/docs/api/applyMiddleware.html). Unlike `applyMiddleware()`, you will need to be careful to only use `DevTools.instrument()` in development environment, and never in production.
84+
85+
The easiest way to apply several store enhancers in a row is to use the [`compose()`](http://redux.js.org/docs/api/compose.html) utility function that ships with Redux. It is the same `compose()` that you can find in Underscore and Lodash. In our case, we would use it to compose several store enhancers into one: `compose(applyMiddleware(m1, m2, m3), DevTools.instrument())`.
86+
87+
It’s important that you should add `DevTools.instrument()` *after* `applyMiddleware` in your `compose()` function arguments. This is because `applyMiddleware` is potentially asynchronous, but `DevTools.instrument()` expects all actions to be plain objects rather than actions interpreted by asynchronous middleware such as [redux-promise](https://github.com/acdlite/redux-promise) or [redux-thunk](https://github.com/gaearon/redux-thunk). So make sure `applyMiddleware()` goes first in the `compose()` call, and `DevTools.instrument()` goes after it.
5588

56-
Next add `instrument()` and (optionally) `persistState()` to your store enhancers, and create your store:
89+
If you’d like, you may add another store enhancer called `persistState()`. It ships with this package, and it lets you serialize whole sessions (including all dispatched actions and the state of the monitors) by a URL key. So if you visit `http://localhost:3000/?debug_session=reproducing_weird_bug`, do something in the app, then open `http://localhost:3000/?debug_session=some_other_feature`, and then go back to `http://localhost:3000/?debug_session=reproducing_weird_bug`, the state will be restored. The implementation of `persistState()` is fairly naïve but you can take it as an inspiration and build a proper UI for it if you feel like it!
90+
91+
#### Exclude DevTools from Production Builds
92+
93+
Finally, to make sure we’re not pulling any DevTools-related code in the production builds, we will envify our code. With Webpack, you can use `DefinePlugin` (Browserify equivalent is called [`envify`](https://github.com/zertosh/loose-envify)) to turn magic constants like `process.env.NODE_ENV` into `'production'` or `'development'` strings depending on the environment, and import and render `redux-devtools` conditionally when `process.env.NODE_ENV` is not `'production'`. Then, if you have an Uglify step before production, Uglify will eliminate dead `if (false)` branches with `redux-devtools` imports.
94+
95+
If you are using ES6 modules with Webpack 1.x and Babel, you might try putting your `import` statement inside an `if (process.env.NODE_ENV !== 'production)` to exclude the DevTools package from your production bundle. However this ES6 specification forbids it, so this won’t compile. Instead, you can use a conditional CommonJS `require`. Babel will let it compile, and Uglify will eliminate the dead branches before Webpack creates a bundle. This is why we recommend creating a `configureStore.js` file that either directs you to `configureStore.dev.js` or `configureStore.prod.js` depending on the configuration. While it is a little bit more maintenance, the upside is that you can be sure you won’t pull any development dependencies into the production builds, and that you can easily enable different middleware (e.g. crash reporting, logging) in the production environment.
96+
97+
##### `store/configureStore.js`
98+
99+
```js
100+
// Use ProvidePlugin (Webpack) or loose-envify (Browserify)
101+
// together with Uglify to strip the dev branch in prod build.
102+
if (process.env.NODE_ENV === 'production') {
103+
module.exports = require('./configureStore.prod');
104+
} else {
105+
module.exports = require('./configureStore.dev');
106+
}
107+
```
108+
109+
##### `store/configureStore.prod.js`
57110

58111
```js
112+
import { createStore, applyMiddleware, compose } from 'redux';
113+
import rootReducer from '../reducers';
114+
115+
const finalCreateStore = compose(
116+
// Middleware you want to use in production:
117+
applyMiddleware(p1, p2, p3),
118+
// Other store enhancers if you use any
119+
)(createStore);
59120

60-
import { createStore, compose } from 'redux';
121+
export default function configureStore(initialState) {
122+
return finalCreateStore(rootReducer, initialState);
123+
};
124+
```
125+
126+
##### `store/configureStore.dev.js`
127+
128+
```js
129+
import { createStore, applyMiddleware, compose } from 'redux';
61130
import { persistState } from 'redux-devtools';
62131
import rootReducer from '../reducers';
63132
import DevTools from '../containers/DevTools';
64133

65134
const finalCreateStore = compose(
66-
// Enables your middleware:
67-
applyMiddleware(m1, m2, m3), // any Redux middleware, e.g. redux-thunk
68-
69-
// Provide support for DevTools
135+
// Middleware you want to use in development:
136+
applyMiddleware(d1, d2, d3),
137+
// Required! Enable Redux DevTools with the monitors you chose
70138
DevTools.instrument(),
71-
72-
// Lets you write ?debug_session=<name> in address bar to persist debug sessions
73-
persistState(
74-
window.location.href.match(
75-
/[?&]debug_session=([^&]+)\b/
76-
)
77-
)
139+
// Optional. Lets you write ?debug_session=<key> in address bar to persist debug sessions
140+
persistState(getDebugSessionKey())
78141
)(createStore);
79142

143+
function getDebugSessionKey() {
144+
// You can write custom logic here!
145+
// By default we try to read the key from ?debug_session=<key> in the address bar
146+
const matches = window.location.href.match(/[?&]debug_session=([^&]+)\b/);
147+
return (matches && matches.length > 0)? matches[1] : null;
148+
}
149+
80150
export default function configureStore(initialState) {
81151
const store = finalCreateStore(rootReducer, initialState);
82152

83-
// enable hot reloading for the store
153+
// Hot reload reducers (requires Webpack or Browserify HMR to be enabled)
84154
if (module.hot) {
85155
module.hot.accept('../reducers', () =>
86-
store.replaceReducer(require('../reducers'))
156+
store.replaceReducer(require('../reducers')/*.default if you use Babel 6+ */)
87157
);
88158
}
89159

90160
return store;
91161
}
92162
```
93163

94-
Finally, include the DevTools component in your page:
164+
#### Render `<DevTools>` in Your App
165+
166+
Finally, include the `DevTools` component in your page.
167+
A naïve way to do this would be to render it right in your `index.js`:
95168

96-
####index.js
169+
##### `index.js`
97170

98171
```js
99172
import React from 'react';
100173
import { render } from 'react-dom';
101174
import { Provider } from 'react-redux';
102175
import configureStore from './store/configureStore';
103-
import TodoApp from './TodoApp';
104-
import DevTools from './DevTools';
176+
import TodoApp from './containers/TodoApp';
177+
178+
// Don't do this! You’re bringing DevTools into the production bundle.
179+
import DevTools from './containers/DevTools';
105180

106181
const store = configureStore();
107182

@@ -116,13 +191,9 @@ render(
116191
);
117192
```
118193

119-
**Make sure to only use DevTools in development!** In production it will be terribly slow because currently actions just accumulate forever.
120-
121-
In Webpack, you can use `DefinePlugin` to turn magic constants like `__DEV__` into `true` or `false` depending on the environment, and import and render `redux-devtools` conditionally behind `if (__DEV__)`. Then, if you have an Uglify step before production, Uglify will eliminate dead `if (false)` branches with `redux-devtools` imports.
122-
123-
If you are using ES6 modules with Webpack 1.x, you might try putting your `import` statement inside an `if (__DEV__)` to exclude the DevTools package from your production bundle. This will not work. However, you can work around this by creating separate `dev` and `prod` Root components that are dynamically imported using commonJS `require`:
194+
We recommend a different approach. Create a `Root.js` component that renders the root of your application (usually some component surrounded by a `<Provider>`). Then use the same trick with conditional `require` statements to have two versions of it, one for development, and one for production:
124195

125-
####containers/Root.js
196+
##### `containers/Root.js`
126197

127198
```js
128199
if (process.env.NODE_ENV === 'production') {
@@ -132,7 +203,7 @@ if (process.env.NODE_ENV === 'production') {
132203
}
133204
```
134205

135-
####Root.dev.js
206+
##### `containers/Root.dev.js`
136207

137208
```js
138209
import React, { Component } from 'react';
@@ -155,7 +226,7 @@ export default class Root extends Component {
155226
}
156227
```
157228

158-
####Root.prod.js
229+
##### `containers/Root.prod.js`
159230

160231
```js
161232
import React, { Component } from 'react';
@@ -174,6 +245,62 @@ export default class Root extends Component {
174245
}
175246
```
176247

248+
#### Adjusting the Appearance
249+
250+
When you use [`DockMonitor`](https://github.com/gaearon/redux-devtools-dock-monitor), you usually want to render `<DevTools>` at the root of your app. It will appear in a docked container above it. However, you can also render it anywhere else in your React component tree. In this case, you’d create a development and a production version of some other component that would either include or exclude `<DevTools>`.
251+
252+
For example (you don’t have to do that!), you may prefer to display the DevTools in a separate window instead of rendering it inside the page. In this case, you can remove `DockMonitor` from `DevTools.js` and just use the `LogMonitor`, and have some code like this:
253+
254+
##### `index.js`
255+
256+
```js
257+
import React from 'react';
258+
import { Provider } from 'react-redux';
259+
import { render } from 'react-dom';
260+
import configureStore from './store/configureStore';
261+
import App from './containers/App';
262+
263+
const store = configureStore();
264+
265+
render(
266+
<Provider store={store}>
267+
<App />
268+
</Provider>,
269+
document.getElementById('root')
270+
);
271+
272+
if (process.env.NODE_ENV !== 'production') {
273+
const showDevTools = require('./showDevTools');
274+
showDevTools(store);
275+
}
276+
```
277+
278+
##### `showDevTools.js`
279+
280+
```js
281+
import React from 'react';
282+
import { render } from 'react-dom';
283+
import DevTools from './containers/DevTools';
284+
285+
export default function showDevTools(store) {
286+
const popup = window.open(null, 'Redux DevTools', 'menubar=no,location=no,resizable=yes,scrollbars=no,status=no');
287+
// Reload in case it already exists
288+
popup.location.reload();
289+
290+
setTimeout(() => {
291+
popup.document.write('<div id="react-devtools-root"></div>');
292+
render(
293+
<DevTools store={store} />,
294+
popup.document.getElementById('react-devtools-root')
295+
);
296+
}, 10);
297+
}
298+
```
299+
300+
Personal preferences vary, and whether to put the DevTools in a separate window, in a dock, or right inside you app’s user interface, is up to you. Make sure to check the documentation for the monitors you use and learn about the different props they support for customizing the appearance and the behavior.
301+
302+
Note that there are no useful props you can pass to the `DevTools` component other than the `store`. The `store` prop is needed if you don’t wrap `<DevTools>` in a `<Provider>`—just like with any connected component. To adjust the monitors, you need to pass props to them inside `DevTools.js` itself inside the `createDevTools()` call when they are used.
303+
177304
### Running Examples
178305

179306
You can do this:
@@ -183,7 +310,7 @@ git clone https://github.com/gaearon/redux-devtools.git
183310
cd redux-devtools
184311
npm install
185312
186-
cd examples/counter
313+
cd examples/counter # or examples/todomvc
187314
npm install
188315
npm start
189316
open http://localhost:3000
@@ -192,8 +319,6 @@ open http://localhost:3000
192319
Try clicking on actions in the log, or changing some code inside `examples/counter/reducers/counter`.
193320
For fun, you can also open `http://localhost:3000/?debug_session=123`, click around, and then refresh.
194321

195-
Oh, and you can do the same with the TodoMVC example as well.
196-
197322
### Custom Monitors
198323

199324
**DevTools accepts monitor components so you can build a completely custom UI.** [`redux-devtools-log-monitor`](https://github.com/gaearon/redux-devtools-log-monitor) and [`redux-devtools-dock-monitor`](https://github.com/gaearon/redux-devtools-dock-monitor) are just examples of what is possible.
@@ -205,7 +330,7 @@ Some crazy ideas for custom monitors:
205330
* A slider that lets you jump between computed states just by dragging it
206331
* An in-app layer that shows the last N states right in the app (e.g. for animation)
207332
* A time machine like interface where the last N states of your app reside on different Z layers
208-
* Feel free to come up with and implement your own! Check [`redux-devtools-log-monitor`](https://github.com/gaearon/redux-devtools-log-monitor) propTypes to see what you can do.
333+
* Feel free to come up with and implement your own! Check [`LogMonitor`](https://github.com/gaearon/redux-devtools-log-monitor) propTypes to see what you can do.
209334

210335
In fact some of these are implemented already:
211336

0 commit comments

Comments
 (0)