If you don’t want to bother with installing Redux DevTools and integrating it into your project, consider using Redux DevTools Extension for Chrome and Firefox. It provides access to the most popular monitors, is easy to configure to filter actions, and doesn’t require installing any packages.
If you want to have full control over where DevTools are displayed, or are developing a custom monitor, you will probably want to integrate them manually. It’s more steps, but you will have full control over monitors and their configuration.
npm install --save-dev @redux-devtools/core
You’ll also likely want to install some monitors:
npm install --save-dev @redux-devtools/log-monitor npm install --save-dev @redux-devtools/dock-monitor
Somewhere in your project, create a DevTools
component by passing a monitor
element to createDevTools
. In the following example our monitor
consists of LogMonitor
docked within DockMonitor
:
importReactfrom'react';// Exported from redux-devtoolsimport{createDevTools}from'@redux-devtools/core';// Monitors are separate packages, and you can make a custom oneimportLogMonitorfrom'@redux-devtools/log-monitor';importDockMonitorfrom'@redux-devtools/dock-monitor';// createDevTools takes a monitor and produces a DevTools componentconstDevTools=createDevTools(// Monitors are individually adjustable with props.// Consult their repositories to learn about those props.// Here, we put LogMonitor inside a DockMonitor.// Note: DockMonitor is visible by default.<DockMonitortoggleVisibilityKey="ctrl-h"changePositionKey="ctrl-q"defaultIsVisible={true}><LogMonitortheme="tomorrow"/></DockMonitor>,);exportdefaultDevTools;
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:
// If you'd rather not use docking UI, use <LogMonitor> directlyconstDevTools=createDevTools(<LogMonitortheme="solarized"/>);
The DevTools
component you created with createDevTools()
has a special static method called instrument()
. It returns a store enhancer that you need to use in development.
A store enhancer is a function that enhances the behavior of createStore()
. You can pass store enhancer as the last optional argument to createStore()
. You probably already used another store enhancer—applyMiddleware()
. Unlike applyMiddleware()
, you will need to be careful to only use DevTools.instrument()
in development environment, and never in production.
The easiest way to apply several store enhancers in a row is to use the compose()
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())
.
You can add additional options to it: DevTools.instrument({ maxAge: 50, shouldCatchErrors: true })
. See redux-devtools-instrument
's API for more details.
It’s important that you should add DevTools.instrument()
afterapplyMiddleware
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 or redux-thunk. So make sure applyMiddleware()
goes first in the compose()
call, and DevTools.instrument()
goes after it.
import{createStore,applyMiddleware,compose}from'redux';importrootReducerfrom'../reducers';importDevToolsfrom'../containers/DevTools';constenhancer=compose(// Middleware you want to use in development:applyMiddleware(d1,d2,d3),// Required! Enable Redux DevTools with the monitors you choseDevTools.instrument(),);exportdefaultfunctionconfigureStore(initialState){// Note: only Redux >= 3.1.0 supports passing enhancer as third argument.// See https://github.com/reactjs/redux/releases/tag/v3.1.0conststore=createStore(rootReducer,initialState,enhancer);// Hot reload reducers (requires Webpack or Browserify HMR to be enabled)if(module.hot){module.hot.accept('../reducers',()=>store.replaceReducer(require('../reducers')/*.default if you use Babel 6+ */,),);}returnstore;}
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!
// ...import{persistState}from'@redux-devtools/core';constenhancer=compose(// Middleware you want to use in development:applyMiddleware(d1,d2,d3),// Required! Enable Redux DevTools with the monitors you choseDevTools.instrument(),// Optional. Lets you write ?debug_session=<key> in address bar to persist debug sessionspersistState(getDebugSessionKey()),);functiongetDebugSessionKey(){// You can write custom logic here!// By default we try to read the key from ?debug_session=<key> in the address barconstmatches=window.location.href.match(/[?&]debug_session=([^&#]+)\b/);returnmatches&&matches.length>0 ? matches[1] : null;}exportdefaultfunctionconfigureStore(initialState){// ...}
Finally, to make sure we’re not pulling any DevTools-related code in the production builds, we will envify our code. You can use DefinePlugin
with Webpack, or envify
for Browserify.
The trick is to replace all occurrences of a constant like process.env.NODE_ENV
into a string depending on the environment, and import and render redux-devtools
only 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.
With Webpack, you'll need two config files, one for development and one for production. Here's a snippet from an example production config:
// ...plugins: [newwebpack.DefinePlugin({'process.env.NODE_ENV': JSON.stringify('production')})],// ...
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.
// Use DefinePlugin (Webpack) or loose-envify (Browserify)// together with Uglify to strip the dev branch in prod build.if(process.env.NODE_ENV==='production'){module.exports=require('./configureStore.prod');}else{module.exports=require('./configureStore.dev');}
import{createStore,applyMiddleware,compose}from'redux';importrootReducerfrom'../reducers';// Middleware you want to use in production:constenhancer=applyMiddleware(p1,p2,p3);exportdefaultfunctionconfigureStore(initialState){// Note: only Redux >= 3.1.0 supports passing enhancer as third argument.// See https://github.com/rackt/redux/releases/tag/v3.1.0returncreateStore(rootReducer,initialState,enhancer);}
import{createStore,applyMiddleware,compose}from'redux';import{persistState}from'@redux-devtools/core';importrootReducerfrom'../reducers';importDevToolsfrom'../containers/DevTools';constenhancer=compose(// Middleware you want to use in development:applyMiddleware(d1,d2,d3),// Required! Enable Redux DevTools with the monitors you choseDevTools.instrument(),// Optional. Lets you write ?debug_session=<key> in address bar to persist debug sessionspersistState(getDebugSessionKey()),);functiongetDebugSessionKey(){// You can write custom logic here!// By default we try to read the key from ?debug_session=<key> in the address barconstmatches=window.location.href.match(/[?&]debug_session=([^&]+)\b/);returnmatches&&matches.length>0 ? matches[1] : null;}exportdefaultfunctionconfigureStore(initialState){// Note: only Redux >= 3.1.0 supports passing enhancer as third argument.// See https://github.com/rackt/redux/releases/tag/v3.1.0conststore=createStore(rootReducer,initialState,enhancer);// Hot reload reducers (requires Webpack or Browserify HMR to be enabled)if(module.hot){module.hot.accept('../reducers',()=>store.replaceReducer(require('../reducers')/*.default if you use Babel 6+ */,),);}returnstore;}
Finally, include the DevTools
component in your page.
A naïve way to do this would be to render it right in your index.js
:
importReactfrom'react';import{render}from'react-dom';import{Provider}from'react-redux';importconfigureStorefrom'./store/configureStore';importTodoAppfrom'./containers/TodoApp';// Don't do this! You’re bringing DevTools into the production bundle.importDevToolsfrom'./containers/DevTools';conststore=configureStore();render(<Providerstore={store}><div><TodoApp/><DevTools/></div></Provider>document.getElementById('app'));
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:
if(process.env.NODE_ENV==='production'){module.exports=require('./Root.prod');}else{module.exports=require('./Root.dev');}
importReact,{Component}from'react';import{Provider}from'react-redux';importTodoAppfrom'./TodoApp';importDevToolsfrom'./DevTools';exportdefaultclassRootextendsComponent{render(){const{ store }=this.props;return(<Providerstore={store}><div><TodoApp/><DevTools/></div></Provider>);}}
importReact,{Component}from'react';import{Provider}from'react-redux';importTodoAppfrom'./TodoApp';exportdefaultclassRootextendsComponent{render(){const{ store }=this.props;return(<Providerstore={store}><TodoApp/></Provider>);}}
When you use DockMonitor
, 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. To do this, you can remove DockMonitor
and instead render <DevTools>
inside some component of your app. Don’t forget to create two versions of this component to exclude DevTools
in production!
However you don’t even have to render <DevTools>
in the same window. For example, you may prefer to display it in a popup. In this case, you can remove DockMonitor
from DevTools.js
and just use the LogMonitor
, and have some code like this:
importReactfrom'react';import{Provider}from'react-redux';import{render}from'react-dom';importconfigureStorefrom'./store/configureStore';importAppfrom'./containers/App';conststore=configureStore();render(<Providerstore={store}><App/></Provider>,document.getElementById('root'),);if(process.env.NODE_ENV!=='production'){constshowDevTools=require('./showDevTools');showDevTools(store);}
importReactfrom'react';import{render}from'react-dom';importDevToolsfrom'./containers/DevTools';exportdefaultfunctionshowDevTools(store){constpopup=window.open(null,'Redux DevTools','menubar=no,location=no,resizable=yes,scrollbars=no,status=no',);// Reload in case it already existspopup.location.reload();setTimeout(()=>{popup.document.write('<div id="react-devtools-root"></div>');render(<DevToolsstore={store}/>,popup.document.getElementById('react-devtools-root'),);},10);}
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.
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.
Your reducers have to be pure and free of side effects to work correctly with DevTools. For example, even generating a random ID in reducer makes it impure and non-deterministic. Instead, do this in action creators.
Make sure to only apply
DevTools.instrument()
and render<DevTools>
in development! In production, this will be terribly slow because actions just accumulate forever. As described above, you need to use conditionalrequire
s and useDefinePlugin
(Webpack) orloose-envify
(Browserify) together with Uglify to remove the dead code. Here is an example that adds Redux DevTools handling the production case correctly.It is important that
DevTools.instrument()
store enhancer should be added to your middleware stack afterapplyMiddleware
in thecompose
d functions, asapplyMiddleware
is potentially asynchronous. Otherwise, DevTools won’t see the raw actions emitted by asynchronous middleware such as redux-promise or redux-thunk.
Now that you see the DevTools, you might want to learn what those buttons mean and how to use them. This usually depends on the monitor. You can begin by exploring the LogMonitor and DockMonitor documentation, as they are the default monitors we suggest to use together. When you’re comfortable using them, you may want to create your own monitor for more exotic purposes, such as a chart or a diff monitor. Don’t forget to send a PR to feature your monitor at the front page!