* Update Chrome manifest.json
* Remove use of window in background
* Test devpanel
* Inject pageScript using new API
* Keep connection from devpanel to background alive
* Keep connection from content script to background alive
* Replace page action with action
* Cleanup syncOptions
* Update options to not rely on background page access
* Start work on updating popup
* Updates
* Remove window
* Get opening in a separate window working
* Remove pageScriptWrap
* Add socket to panelStore
* Fix tests
* Try to use MV3 for Firefox
* Fix path
* Fix Chrome E2E tests
* Revert unintentional change
* Skip Electron tests for now
Looks like they're still working through stuff in https://github.com/electron/electron/issues/41613
* Better image centering
The Firefox popup did not like the old CSS. This is still not perfect, but it's better than it was.
* Create shaggy-taxis-cross.md
* Use lodash-es instead of lodash in extension
Produces (slightly) smaller bundles
* Use lodash-es instead of lodash in instrument
* Use lodash-es instead of lodash in utils
* Use lodash-es instead of lodash in redux-devtools
* Remove lodash from instrument
* Remove lodash from redux-devtools
* Remove lodash from utils
* Remove unnecessary mapValues from extension
This change splits out the main logic from the Redux Devtools App into a new
core package but keeps the socket connection management in @redux-devtools/app.
The aim is to allow for easier reuse of the rest of the app in other envioronments
with their own transport methods, such as React Native or Electron.
* Remove unnecessary exported functions from instrument
This avoids importing CombinedState so that the types are compatible with Redux 5
* Create bright-pumpkins-move.md
* Use default Node version in CI
GitHub's default Node version doesn't require additional downloads or installations. Currently the LTS and default version are similar, but I think it would be best to stay consistent with the other repositories.
* Replace deprecated action
* Revert "Use default Node version in CI"
This reverts commit b6a1ffd4b5.
* window.bundle.js seems to work
* minify
* Use API instead of CLI
* Add remote bundle
* Perform code-splitting
* Add background and stop code-splitting
* Add other entrypoints
* Flesh out some more
* Keep going
* Copy to browser directories
* Ignore import type error
* Strip out webpack stuff
* Remove todos
* Remove pug imports
* Fix
* Adjusts the location open uses
Currently when using open, the cli will ignore the host and protocol and always open `http://localhost`.
This pr adjusts the open script to use the options protocol and host and default back to localhost if not provided.
* adds changeset
* fixes grammar
* adjusts electron path to respect protocol and host as well
Co-authored-by: Nathan Bierema <nbierema@gmail.com>
* Update Troubleshooting.md
Added documentation for another possible cause of "### It shows only the `@@INIT` action or moving back and forth doesn't update the state".
* Added missing link label
Co-authored-by: Nathan Bierema <nbierema@gmail.com>
* Add `extends object` constraint to `T` in `assign` helper.
* Add `JSX.IntrinsictAttributes` constraint to `P` in `Tabs`.
* Format
Co-authored-by: Nathan Bierema <nbierema@gmail.com>
* fix: broken rtk-query-monitor demo not working #1126
Description:
downgrades framer-motion in order to remove the runtime error "_framerMotion.motion.custom is not a function"
See: https://stackoverflow.com/questions/66703410/next-js-framermotion-motion-custom-is-not-a-function
* feat(rtk-query): add Data tab #1126
* fix: bump min popup width to 700px #1126
Description:
improve UI of rtk-query right side tabs
* fix: bump min popup window width again to 760px #1126
* chore: add changeset
* feat(rtk-query): improve a11y of rtk-query-monitor tab panel #1126
* chore(rtk-query): add few integration tests to rtk-query-monitor #1126
* Fix merge
* Deduplicate msw
Co-authored-by: Nathan Bierema <nbierema@gmail.com>
* Use rollup for d3tooltip
* Use rollup for map2tree
* Set moduleResolution
* Use rollup for d3-state-visualizer
* Use rollup for react-base16-styling
* Use rollup for react-dock
* Use rollup for react-json-tree
* Use rollup for redux-devtools
* Use rollup for redux-devtools-intrument
* Use rollup for redux-devtools-chart-monitor
* Update export
* Use rollup for redux-devtools-dock-monitor
* Use rollup for redux-devtools-inspector-monitor
* Fix inspector demo
* Fix invalid eslint config
* Use rollup for inspector-monitor-test-tab
* Use rollup for inspector-monitor-trace-tab
* Use rollup for redux-devtools-log-monitor
* Use rollup for redux-devtools-remote
* Use rollup in redux-devtools-rtk-query-monitor
* Use rollup for redux-devtools-serialize
* Fix redux-devtools examples
* Use rollup for redux-devtools-slider-monitor
* Fix slider examples
* Use rollup for redux-devtools-ui
* Use rollup for redux-devtools-utils
* Use rollup for redux-devtools-extension
* Use rollup for redux-devtools-app
* Fix Webpack app build
* Fix extension build
* Turn on minimization
* Update CLI
* Use jumpToAction instead of jumpToState
* Jumping to action is time traveling
* Fix bad null check
* Fix types
* Fix JUMP_TO_ACTION handling
* Use indexOf
* fix(deps): update all non-major dependencies
* Changes
* Downgrade msw for now
* Move into own group
* Ignore no-unused-vars
* Disable both
* Just the one
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Nathan Bierema <nbierema@gmail.com>
* feat(redux-devtools-app): use `prefers-color-scheme` to set `theme.light` if user has not set a preferred theme
* chore(@redux-devtools/app): fix lint error
Error message:
0:0 error Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: test/__mocks__/styleMock.ts.
The file must be included in at least one of the projects provided
* chore: run prettier
* feat(app): add theme color dropdown
* refactor(rename): system preference option from default to auto
* Build all
* Lint all
* Test (almost) all
* Test CLI
* Format
* Make styled-components a peer dependency
* Add missing peer dependencies
* Fix electron fixture
* chore(deps): update jest monorepo
* Changes for jest 27
* Update types as well
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Nathan Bierema <nbierema@gmail.com>
* Start work
* More work
* stash
* stash
* stash
* Eliminate relay
* Fix
* Define page script to content script messages
* Define ContentScriptToPageScriptMessage
* Not required
* Fill out more types
* More work on types
* More type fixes
* Add type
* More improvements to types
* More work on types
* Fix more type errors
* More changes
* More work
* Work
* Fix build
* Fix lint
* Fix more lint
* Fix bug
* Fix tests
* Add packages
* misc changes
* Work
* Fix missing package
* Remove lint for now
* Test differently
* Try no-sandbox
* Test
* Try that
* Try that
* Test
* Not headless?
* Test electron
* Try Windows
* lerna run test
* Update
* chore(extension): test in CI
* On Windows
* Use working-directory
* Increase timeout
* Try waiting 2 seconds
* Or 5
* Keep other parts of CI
* Add more to gitattributes
* Expand
* No yaml
* Long
* fix(devui): Always run updateTabs on UNSAFE_componentWillReceiveProps. Otherwise, the changes in property tabs might not be reflected on the UI.
* chore(*): Update devui version. Fix the container growing out of viewport when too many actions.
* fix(devui): Update snapshots
* Update Tabs.tsx
* Update package.json
* Update package.json
Co-authored-by: Nathan Bierema <nbierema@gmail.com>
* Add failing test case for stringifyJSON
* Mutate clone of value in stringifyJSON instead of original
Co-authored-by: Nathan Bierema <nbierema@gmail.com>
* Touch events
Enable to resize the dock on touch devices by handling touch events in addition to mouse events
* Prettifying
`yarn run prettify` command prettifying.
Co-authored-by: Nathan Bierema <nbierema@gmail.com>
This allows users to upgrade to the API-compatible v5 of react-redux without encumbering their `node_modules` with a nested copy of react-redux (v4) under `redux-devtools/node_modules`.
It also moves redux and react-redux into `devDependencies`, which is the proper location for packages that are also specified in `peerDependencies`. Otherwise, peers specified as an alternating range (`||`) won't resolve properly. This brings it in line with what [react-redux does](https://github.com/reactjs/react-redux/blob/master/package.json#L94-L107).
The following warnings are fixed.
1) warning.js:44 Warning: Failed prop type: Passing "props" prop to
<AppContainer /> is deprecated. Replace <AppContainer component={App}
props={{ myProp: myValue }} /> with <AppContainer><App myProp={myValue}
/></AppContainer>.
in AppContainer
2) Warning: Failed prop type: Passing "component" prop to <AppContainer
/> is deprecated. Replace <AppContainer component={App} /> with
<AppContainer><App /></AppContainer>.
in AppContainer
The following warnings are fixed.
1) warning.js:44 Warning: Failed prop type: Passing "props" prop to
<AppContainer /> is deprecated. Replace <AppContainer component={App}
props={{ myProp: myValue }} /> with <AppContainer><App myProp={myValue}
/></AppContainer>.
in AppContainer
2) Warning: Failed prop type: Passing "component" prop to <AppContainer
/> is deprecated. Replace <AppContainer component={App} /> with
<AppContainer><App /></AppContainer>.
in AppContainer
A couple of places mention the wrong webpack plugin required for
excluding DevTools from production builds. I fixed the typo, and
expanded on the explanation a bit, since this was a bit of a sticking
point for me =)
Very confusing error messages were produced if the
container component returned by createDevTools()
was constructed without a store being injected or
if the store had not been instrumented with
DevTools.instrument()
Add warnings for both of these cases and avoid console error
spam by short circuiting rendering of the devtools if they
have not been setup correctly.
Fixes#179
@dzannotti helped increase the performance of `react-json-tree` - it doesn't use deep equality check anymore, so it should be much faster. I put it on 0.1.9. The details are on this commit:
b550c73770
@ -11,4 +11,3 @@ Project maintainers have the right and responsibility to remove, edit, or reject
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
Developer Tools to power-up [Redux](https://redux.js.org/) development workflow or any other architecture which handles the state change (see [integrations](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Integrations.md)).
In the meantime, you can do this:
It can be used as a browser extension (for [Chrome](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd), [Edge](https://microsoftedge.microsoft.com/addons/detail/redux-devtools/nnkgneoiohoecpdiaponcejilbhhikei) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/)), as [a standalone app](https://github.com/reduxjs/redux-devtools/tree/main/packages/redux-devtools-app) or as [a React component](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools) integrated in the client app.
Oh, and you can do this with the TodoMVC example as well.
## Development
### It's Ugly!
This is a monorepo powered by [pnpm](https://pnpm.io/). [Install pnpm](https://pnpm.io/installation) and run `pnpm install` to get started. Each package's dependencies need to be built before the package itself can be built. You can either build all the packages (i.e., `pnpm run build:all`) or use pnpm workspace commands to build only the packages necessary for the packages you're working on (i.e., `pnpm --filter "remotedev-redux-devtools-extension" build`).
The design or usability is not the point. (Although we'll have better design in the future :-)
## Backers
You can build a completely custom UI for it because `<DevTools>` accepts a `monitor` React component prop. You can build any UI you want for it. The included `LogMonitor` is just an example.
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/redux-devtools-extension#backer)]
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/redux-devtools-extension#sponsor)]
By installing [`@redux-devtools/cli`](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-cli#usage), starting the server server and launching the Redux DevTools app (`redux-devtools --open`), you can connect any remote application, even not javascript. There are some integrations for javascript like [remote-redux-devtools](https://github.com/zalmoxisus/remote-redux-devtools) and [remotedev](https://github.com/zalmoxisus/remotedev), but the plan is to deprecate them and support it out of the box from the extension without a websocket server. It is more useful for non-js apps.
### WebSocket Clients
We're using [SocketCluster](http://socketcluster.io/) for realtime communication, which provides a fast and scalable webSocket layer and a minimal pub/sub system. You need to include one of [its clients](https://github.com/SocketCluster/client-drivers) in your app to communicate with RemotedevServer. Currently there are clients for [JavaScript (NodeJS)](https://github.com/SocketCluster/socketcluster-client), [Java](https://github.com/sacOO7/socketcluster-client-java), [Python](https://github.com/sacOO7/socketcluster-client-python), [C](https://github.com/sacOO7/socketcluster-client-C), [Objective-C](https://github.com/abpopov/SocketCluster-ios-client) and [.NET/C#](https://github.com/sacOO7/SocketclusterClientDotNet).
By default, the websocket server is running on `ws://localhost:8000/socketcluster/`.
### Messaging lifecycle
#### 1. Connecting to the WebSocket server
The client driver provides a way to connect to the server via websockets (see the docs for the selected client).
> Note that JavaScript client composes the url from `hostname` and `port`, adding `/socketcluster/` path automatically. For other clients, you should specify that path. For example, for `ObjectiveC` it would be `self.client.initWithHost("localhost/socketcluster/", onPort: 8000, securely: false)`.
#### 2. Disconnecting and reconnecting
SocketCluster client handles reconnecting for you, but you still might want to know when the connection is established, or when it failed to connect.
##### JavaScript
```js
socket.on('connect', (status) => {
// Here will come the next step
});
socket.on('disconnect', (code) => {
console.warn('Socket disconnected with code', code);
#### 3. Authorizing and subscribing to the channel of events
We're not providing an authorizing mechanism yet. All you have to do is to emit a `login` event, and you'll get a `channelName` you should subscribe for, and watch for messages and events. Make sure to pass the `master` event, otherwise it should be a monitor, not a client app.
You could just emit the `login` event, and omit subscribing (and point `5` bellow) if you want only to log data, not to interact with te app.
#### 4. Sending the action and state to the monitor
To send your data to the monitor use `log` or `log-noid` channel. The latter will add the socket id to the message from the server side (useful when the message was sent before the connection was established).
The message object includes the following:
- `type` - usually should be `ACTION`. If you want to indicate that we're starting a new log (clear all actions emitted before and add `@@INIT`), use `INIT`. In case you have a lifted state similar to one provided by [`redux-devtools-instrument`](https://github.com/zalmoxisus/redux-devtools-instrument), use `STATE`.
- `action` - the action object. It is recommended to lift it in another object, and add `timestamp` to show when the action was fired off: `{ timestamp: Date.now(), action: { type: 'SOME_ACTION' } }`.
- `payload` - usually the state or lifted state object.
- `name` - name of the instance to be shown in the instances selector. If not provided, it will be equal to `instanceId`.
- `instanceId` - an id to identify the instance. If not provided, it will be the same as `id`. However, it is useful when having several instances (or stores) in the same connection. Also if the user will specify a constant value, it would allow to persist the state on app reload.
- `id` - socket connection id, which should be either `socket.id` or should not provided and use `log-noid` channel.
socket.emit(socket.id if "log" else "log-noid", Message(action, state));
```
#### 5. Listening for monitor events
When a monitor action is emitted, you'll get an event on the subscribed function. The argument object includes a `type` key, which can be:
- `DISPATCH` - a monitor action dispatched on Redux DevTools monitor, like `{ type: 'DISPATCH', payload: { type: 'JUMP_TO_STATE', 'index': 2 }`. See [`redux-devtools-instrument`](https://github.com/zalmoxisus/redux-devtools-instrument/blob/master/src/instrument.js) for details. Additionally to that API, you'll get also a stringified `state` object when needed. So, for example, for time travelling (`JUMP_TO_STATE`) you can just parse and set the state (see the example). Usually implementing this type of actions would be enough.
- `ACTION` - the user requested to dispatch an action remotely like `{ type: 'ACTION', action: '{ type: \'INCREMENT_COUNTER\' }' }`. The `action` can be either a stringified javascript object which should be evalled or a function which arguments should be evalled like [here](https://github.com/zalmoxisus/remotedev-utils/blob/master/src/index.js#L62-L70).
- `START` - a monitor was opened. You could handle this event in order not to do extra tasks when the app is not monitored.
- `STOP` - a monitor was closed. You can take this as no need to send data to the monitor. I there are several monitors and one was closed, all others will send `START` event to acknowledge that we still have to send data.
See [`mobx-remotedev`](https://github.com/zalmoxisus/mobx-remotedev/blob/master/src/monitorActions.js) for an example of implementation without [`redux-devtools-instrument`](https://github.com/zalmoxisus/redux-devtools-instrument/blob/master/src/instrument.js).
##### JavaScript
```js
function handleMessages(message) {
if (message.type === 'DISPATCH' && message.payload.type === 'JUMP_TO_STATE') {
store.setState(JSON.parse(message.state));
}
}
```
##### Python
```py
def handleMessages(key, message):
if message.type === "DISPATCH" and message.payload.type === "JUMP_TO_STATE":
If you don’t want to bother with installing Redux DevTools and integrating it into your project, consider using [Redux DevTools Extension](https://github.com/reduxjs/redux-devtools/tree/master/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.
## Manual Integration
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.
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):
##### `containers/DevTools.js`
```js
import React from 'react';
// Exported from redux-devtools
import { createDevTools } from '@redux-devtools/core';
// Monitors are separate packages, and you can make a custom one
import LogMonitor from '@redux-devtools/log-monitor';
import DockMonitor from '@redux-devtools/dock-monitor';
// createDevTools takes a monitor and produces a DevTools component
const DevTools = 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.
<DockMonitor
toggleVisibilityKey="ctrl-h"
changePositionKey="ctrl-q"
defaultIsVisible={true}
>
<LogMonitortheme="tomorrow"/>
</DockMonitor>,
);
export default DevTools;
```
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:
```js
// If you'd rather not use docking UI, use <LogMonitor> directly
The `DevTools` component you created with `createDevTools()` has a special static method called `instrument()`. It returns a [store enhancer](http://redux.js.org/docs/Glossary.html#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()`](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.
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())`.
You can add additional options to it: `DevTools.instrument({ maxAge: 50, shouldCatchErrors: true })`. See [`redux-devtools-instrument`'s API](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-instrument#api) for more details.
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.
##### `store/configureStore.js`
```js
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from '../reducers';
import DevTools from '../containers/DevTools';
const enhancer = compose(
// Middleware you want to use in development:
applyMiddleware(d1, d2, d3),
// Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument(),
);
export default function configureStore(initialState) {
// Note: only Redux >= 3.1.0 supports passing enhancer as third argument.
// See https://github.com/reactjs/redux/releases/tag/v3.1.0
const store = 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+ */,
),
);
}
return store;
}
```
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!
```js
// ...
import { persistState } from '@redux-devtools/core';
const enhancer = compose(
// Middleware you want to use in development:
applyMiddleware(d1, d2, d3),
// Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument(),
// Optional. Lets you write ?debug_session=<key> in address bar to persist debug sessions
persistState(getDebugSessionKey()),
);
function getDebugSessionKey() {
// You can write custom logic here!
// By default we try to read the key from ?debug_session=<key> in the address bar
export default function configureStore(initialState) {
// ...
}
```
#### Exclude DevTools from Production Builds
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`](https://github.com/webpack/docs/wiki/list-of-plugins#defineplugin) with Webpack, or [`envify`](https://github.com/zertosh/loose-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:
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.
##### `store/configureStore.js`
```js
// Use DefinePlugin (Webpack) or loose-envify (Browserify)
// together with Uglify to strip the dev branch in prod build.
export default function configureStore(initialState) {
// Note: only Redux >= 3.1.0 supports passing enhancer as third argument.
// See https://github.com/rackt/redux/releases/tag/v3.1.0
const store = 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+ */,
),
);
}
return store;
}
```
#### Render `<DevTools>` in Your App...
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`:
##### `index.js`
```js
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import TodoApp from './containers/TodoApp';
// Don't do this! You’re bringing DevTools into the production bundle.
import DevTools from './containers/DevTools';
const store = 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:
##### `containers/Root.js`
```js
if (process.env.NODE_ENV === 'production') {
module.exports = require('./Root.prod');
} else {
module.exports = require('./Root.dev');
}
```
##### `containers/Root.dev.js`
```js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import TodoApp from './TodoApp';
import DevTools from './DevTools';
export default class Root extends Component {
render() {
const { store } = this.props;
return (
<Providerstore={store}>
<div>
<TodoApp/>
<DevTools/>
</div>
</Provider>
);
}
}
```
##### `containers/Root.prod.js`
```js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import TodoApp from './TodoApp';
export default class Root extends Component {
render() {
const { store } = this.props;
return (
<Providerstore={store}>
<TodoApp/>
</Provider>
);
}
}
```
#### ...Or Open Them in a New Window
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. 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:
##### `index.js`
```js
import React from 'react';
import { Provider } from 'react-redux';
import { render } from 'react-dom';
import configureStore from './store/configureStore';
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.
### Gotchas
- **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 conditional `require`s and use `DefinePlugin` (Webpack) or `loose-envify` (Browserify) together with Uglify to remove the dead code. Here is [an example](https://github.com/erikras/react-redux-universal-hot-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 _after_ `applyMiddleware` in the `compose`d functions, 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).
### What Next?
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](https://github.com/gaearon/redux-devtools-log-monitor) and [DockMonitor](https://github.com/gaearon/redux-devtools-dock-monitor) 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](https://github.com/romseguy/redux-devtools-chart-monitor) or a [diff](https://github.com/whetstone/redux-devtools-diff-monitor) monitor. Don’t forget to send a PR to feature your monitor at the front page!
[](https://gitter.im/zalmoxisus/redux-devtools-extension?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
- from [Chrome Web Store](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd);
- or download `extension.zip` from [last releases](https://github.com/zalmoxisus/redux-devtools-extension/releases), unzip, open `chrome://extensions` url and turn on developer mode from top left and then click; on `Load Unpacked` and select the extracted folder for use
- or build it with `npm i && npm run build:extension` and [load the extension's folder](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked) `./build/extension`;
- or run it in dev mode with `npm i && npm start` and [load the extension's folder](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked) `./dev`.
### 2. For Firefox
- from [Mozilla Add-ons](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/);
- or build it with `npm i && npm run build:firefox` and [load the extension's folder](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Temporary_Installation_in_Firefox) `./build/firefox` (just select a file from inside the dir).
### 3. For Electron
- just specify `REDUX_DEVTOOLS` in [`electron-devtools-installer`](https://github.com/GPMDP/electron-devtools-installer).
### 4. For other browsers and non-browser environment
- use [`remote-redux-devtools`](https://github.com/zalmoxisus/remote-redux-devtools).
## Usage
> Note that starting from v2.7, `window.devToolsExtension` was renamed to `window.__REDUX_DEVTOOLS_EXTENSION__` / `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__`.
## 1. With Redux
### 1.1 Basic store
For a basic [Redux store](https://redux.js.org/api/createstore#createstorereducer-preloadedstate-enhancer) simply add:
Note that [`preloadedState`](https://redux.js.org/api/createstore#createstorereducer-preloadedstate-enhancer) argument is optional in Redux's [`createStore`](https://redux.js.org/api/createstore#createstorereducer-preloadedstate-enhancer).
> For universal ("isomorphic") apps, prefix it with `typeof window !== 'undefined' &&`.
```js
const composeEnhancers =
(typeof window !== 'undefined' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
compose;
```
> For TypeScript use [`redux-devtools-extension` npm package](#13-use-redux-devtoolsextension-package-from-npm), which contains all the definitions, or just use `(window as any)` (see [Recipes](/docs/Recipes.md#using-in-a-typescript-project) for an example).
```js
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
```
In case ESLint is configured to not allow using the underscore dangle, wrap it like so:
> **Note**: Passing enhancer as last argument requires **redux@>=3.1.0**. For older versions apply it like [here](https://github.com/zalmoxisus/redux-devtools-extension/blob/v0.4.2/examples/todomvc/store/configureStore.js) or [here](https://github.com/zalmoxisus/redux-devtools-extension/blob/v0.4.2/examples/counter/store/configureStore.js#L7-L12). Don't mix the old Redux API with the new one.
> You don't need to npm install [`redux-devtools`](https://github.com/gaearon/redux-devtools) when using the extension (that's a different lib).
### 1.2 Advanced store setup
If you setup your store with [middleware and enhancers](http://redux.js.org/docs/api/applyMiddleware.html), change:
```diff
import { createStore, applyMiddleware, compose } from 'redux';
// Specify extension’s options like name, actionsDenylist, actionsCreators, serialize...
})
: compose;
const enhancer = composeEnhancers(
applyMiddleware(...middleware),
// other store enhancers if any
);
const store = createStore(reducer, enhancer);
```
> [See the post for more details](https://medium.com/@zalmoxis/improve-your-development-workflow-with-redux-devtools-extension-f0379227ff83).
### 1.3 Use `@redux-devtools/extension` package from npm
To make things easier, there's an npm package to install:
```
npm install --save @redux-devtools/extension
```
and to use like so:
```js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from '@redux-devtools/extension';
const store = createStore(
reducer,
composeWithDevTools(
applyMiddleware(...middleware),
// other store enhancers if any
),
);
```
To specify [extension’s options](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#windowdevtoolsextensionconfig):
```js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from '@redux-devtools/extension';
const composeEnhancers = composeWithDevTools({
// Specify name here, actionsDenylist, actionsCreators and other options if needed
});
const store = createStore(
reducer,
/* preloadedState, */ composeEnhancers(
applyMiddleware(...middleware),
// other store enhancers if any
),
);
```
> There are just a [few lines of code](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/index.js) added to your bundle.
In case you don't include other enhancers and middlewares, just use `devToolsEnhancer`:
```js
import { createStore } from 'redux';
import { devToolsEnhancer } from '@redux-devtools/extension';
const store = createStore(
reducer,
/* preloadedState, */ devToolsEnhancer(),
// Specify name here, actionsDenylist, actionsCreators and other options if needed
);
```
### 1.4 Using in production
It's useful to include the extension in production as well. Usually you [can use it for development](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f).
If you want to restrict it there, use `composeWithDevToolsLogOnlyInProduction` or `devToolsEnhancerLogOnlyInProduction`:
```js
import { createStore } from 'redux';
import { devToolsEnhancerLogOnlyInProduction } from '@redux-devtools/extension';
> You'll have to add `'process.env.NODE_ENV': JSON.stringify('production')` in your Webpack config for the production bundle ([to envify](https://github.com/gaearon/redux-devtools/blob/master/docs/Walkthrough.md#exclude-devtools-from-production-builds)). If you use `create-react-app`, [it already does it for you.](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/config/webpack.config.prod.js#L253-L257)
If you're already checking `process.env.NODE_ENV` when creating the store, import `composeWithDevToolsLogOnly` or `devToolsEnhancerLogOnly` for production environment.
If you don’t want to allow the extension in production, just use `composeWithDevToolsDevelopmentOnly` or `devToolsEnhancerDevelopmentOnly`.
> See [the article](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f) for more details.
### 1.5 For React Native, hybrid, desktop and server side Redux apps
For React Native we can use [`react-native-debugger`](https://github.com/jhen0409/react-native-debugger), which already included [the same API](https://github.com/jhen0409/react-native-debugger/blob/master/docs/redux-devtools-integration.md) with Redux DevTools Extension.
For most platforms, include [`Remote Redux DevTools`](https://github.com/zalmoxisus/remote-redux-devtools)'s store enhancer, and from the extension's context menu choose 'Open Remote DevTools' for remote monitoring.
## 2. Without Redux
See [integrations](docs/Integrations.md) and [the blog post](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f) for more details on how to use the extension with any architecture.
Also see [`./examples` folder](https://github.com/zalmoxisus/redux-devtools-extension/tree/master/examples).
## Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/redux-devtools-extension#backer)]
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/redux-devtools-extension#sponsor)]
The `options` object is optional, and can include any of the following.
### `name`
_string_ - the instance name to be shown on the monitor page. Default value is `document.title`. If not specified and there's no document title, it will consist of `tabId` and `instanceId`.
### `actionCreators`
_array_ or _object_ - action creators functions to be available in the Dispatcher. See [the example](https://github.com/zalmoxisus/redux-devtools-extension/commit/477e69d8649dfcdc9bf84dd45605dab7d9775c03).
### `latency`
_number (in ms)_ - if more than one action is dispatched in the indicated interval, all new actions will be collected and sent at once. It is the joint between performance and speed. When set to `0`, all actions will be sent instantly. Set it to a higher value when experiencing perf issues (also `maxAge` to a lower value). Default is `500 ms`.
### `maxAge`
_number_ (>1) - maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance. Default is `50`.
### `trace`
_boolean_ or _function_ - if set to `true`, will include stack trace for every dispatched action, so you can see it in trace tab jumping directly to that part of code ([more details](../Features/Trace.md)). You can use a function (with action object as argument) which should return `new Error().stack` string, getting the stack outside of reducers. Default to `false`.
### `traceLimit`
_number_ - maximum stack trace frames to be stored (in case `trace` option was provided as `true`). By default it's `10`. Note that, because extension's calls are excluded, the resulted frames could be 1 less. If `trace` option is a function, `traceLimit` will have no effect, as it's supposed to be handled there.
### `serialize`
_boolean_ or _object_ which contains:
- **options**`object or boolean`:
- `undefined` - will use regular `JSON.stringify` to send data (it's the fast mode).
- `false` - will handle also circular references.
- `true` - will handle also date, regex, undefined, primitives, error objects, symbols, maps, sets and functions.
- object, which contains `date`, `regex`, `undefined`, `nan`, `infinity`, `error`, `symbol`, `map`, `set` and `function` keys. For each of them you can indicate if to include (by setting as `true`). For `function` key you can also specify a custom function which handles serialization. See [`jsan`](https://github.com/kolodny/jsan) for more details. Example:
```js
const store = Redux.createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__({
serialize: {
options: {
undefined: true,
function: function (fn) {
return fn.toString();
},
},
},
}),
);
```
- **replacer**`function(key, value)` - [JSON `replacer` function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) used for both actions and states stringify.
Example of usage with [mori data structures](https://github.com/swannodette/mori):
```js
const store = Redux.createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__({
serialize: {
replacer: (key, value) =>
value && mori.isMap(value) ? mori.toJs(value) : value,
},
}),
);
```
In addition, you can specify a data type by adding a [`__serializedType__`](https://github.com/zalmoxisus/remotedev-serialize/blob/master/helpers/index.js#L4) key. So you can deserialize it back while importing or persisting data. Moreover, it will also [show a nice preview showing the provided custom type](https://cloud.githubusercontent.com/assets/7957859/21814330/a17d556a-d761-11e6-85ef-159dd12f36c5.png):
```js
const store = Redux.createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__({
serialize: {
replacer: (key, value) => {
if (Immutable.List.isList(value)) {
// use your custom data type checker
return {
data: value.toArray(), // ImmutableJS custom method to get JS data as array
__serializedType__: 'ImmutableList', // mark you custom data type to show and retrieve back
};
}
},
},
}),
);
```
- **reviver**`function(key, value)` - [JSON `reviver` function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter) used for parsing the imported actions and states. See [`remotedev-serialize`](https://github.com/zalmoxisus/remotedev-serialize/blob/master/immutable/serialize.js#L8-L41) as an example on how to serialize special data types and get them back:
```js
const store = Redux.createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__({
serialize: {
reviver: (key, value) => {
if (
typeof value === 'object' &&
value !== null &&
'__serializedType__' in value
) {
switch (value.__serializedType__) {
case 'ImmutableList':
return Immutable.List(value.data);
}
}
},
},
}),
);
```
- **immutable**`object` - automatically serialize/deserialize immutablejs via [remotedev-serialize](https://github.com/zalmoxisus/remotedev-serialize). Just pass the Immutable library like so:
```js
import Immutable from 'immutable'; // https://facebook.github.io/immutable-js/
// ...
// Like above, only showing off compose this time. Reminder you might not want this in prod.
It will support all ImmutableJS structures. You can even export them into a file and get them back. The only exception is `Record` class, for which you should pass in addition the references to your classes in `refs`.
- **refs**`array` - ImmutableJS `Record` classes used to make possible restore its instances back when importing, persisting... Example of usage:
const myRecord = new ABRecord({ b: 3 }); // used in the reducers
const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__({
serialize: {
immutable: Immutable,
refs: [ABRecord],
},
}),
);
```
Also you can specify alternative values right in the state object (in the initial state of the reducer) by adding `toJSON` function:
In the example bellow it will always send `{ component: '[React]' }`, regardless of the state's `component` value (useful when you don't want to send lots of unnecessary data):
You could also alter the value. For example when state is `{ count: 1 }`, we'll send `{ counter: 10 }` (notice we don't have an arrow function this time to use the object's `this`):
```js
function counter(
state = {
count: 0,
toJSON: function () {
return { conter: this.count * 10 };
},
},
action,
) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
default:
return state;
}
}
```
### `actionSanitizer` / `stateSanitizer`
- **actionSanitizer** (_function_) - function which takes `action` object and id number as arguments, and should return `action` object back. See the example bellow.
- **stateSanitizer** (_function_) - function which takes `state` object and index as arguments, and should return `state` object back.
_string or array of strings as regex_ - actions types to be hidden / shown in the monitors (while passed to the reducers). If `actionsAllowlist` specified, `actionsDenylist` is ignored.
Example:
```js
createStore(
reducer,
remotedev({
sendTo: 'http://localhost:8000',
actionsDenylist: 'SOME_ACTION',
// or actionsDenylist: ['SOME_ACTION', 'SOME_OTHER_ACTION']
// or just actionsDenylist: 'SOME_' to omit both
}),
);
```
### `predicate`
_function_ - called for every action before sending, takes `state` and `action` object, and returns `true` in case it allows sending the current data to the monitor. Use it as a more advanced version of `actionsDenylist`/`actionsAllowlist` parameters.
_boolean_ - if specified as `false`, it will not record the changes till clicking on `Start recording` button. Default is `true`. Available only for Redux enhancer, for others use `autoPause`.
### `pauseActionType`
_string_ - if specified, whenever clicking on `Pause recording` button and there are actions in the history log, will add this action type. If not specified, will commit when paused. Available only for Redux enhancer. Default is `@@PAUSED`.
### `autoPause`
_boolean_ - auto pauses when the extension’s window is not opened, and so has zero impact on your app when not in use. Not available for Redux enhancer (as it already does it but storing the data to be sent). Default is `false`.
### `shouldStartLocked`
_boolean_ - if specified as `true`, it will not allow any non-monitor actions to be dispatched till clicking on `Unlock changes` button. Available only for Redux enhancer. Default is `false`.
### `shouldHotReload`
_boolean_ - if set to `false`, will not recompute the states on hot reloading (or on replacing the reducers). Available only for Redux enhancer. Default to `true`.
### `shouldCatchErrors`
_boolean_ - if specified as `true`, whenever there's an exception in reducers, the monitors will show the error message, and next actions will not be dispatched.
### `features`
If you want to restrict the extension, just specify the features you allow:
```js
const composeEnhancers = composeWithDevTools({
features: {
pause: true, // start/pause recording of dispatched actions
lock: true, // lock/unlock dispatching actions and side effects
persist: true, // persist states on page reloading
export: true, // export history of actions in a file
import: 'custom', // import history of actions from a file
jump: true, // jump back and forth (time travelling)
skip: true, // skip (cancel) actions
reorder: true, // drag and drop actions in the history list
dispatch: true, // dispatch custom actions or action creators
test: true, // generate tests for the selected actions
},
// other options like actionSanitizer, stateSanitizer
});
```
If not specified, all of the features are enabled. When set as an object, only those included as `true` will be allowed.
Note that except `true`/`false`, `import` and `export` can be set as `custom` (which is by default for Redux enhancer), meaning that the importing/exporting occurs on the client side. Otherwise, you'll get/set the data right from the monitor part.
> Note this is advanced API, which you usually don't need to use with Redux enhancer.
Use the following methods of `window.__REDUX_DEVTOOLS_EXTENSION__`:
- [connect](#connect)
- [disconnect](#disconnect)
- [send](#send)
- [listen](#listen)
- [open](#open)
- [notifyErrors](#notifyerrors)
<aid="connect"></a>
### connect([options])
##### Arguments
- [`options`] _Object_ - [see the available options](Arguments.md).
##### Returns
_Object_ containing the following methods:
- `subscribe(listener)` - adds a change listener. It will be called any time an action is dispatched from the monitor. Returns a function to unsubscribe the current listener.
- `unsubscribe()` - unsubscribes all listeners.
- `send(action, state)` - sends a new action and state manually to be shown on the monitor. If action is `null` then we suppose we send `liftedState`.
- `init(state)` - sends the initial state to the monitor.
- `error(message)` - sends the error message to be shown in the extension's monitor.
See [redux enhancer's example](https://github.com/reduxjs/redux-devtools/blob/main/packages/redux-devtools-extension/src/logOnly.ts), [react example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/react-counter-messaging/components/Counter.js) and [blog post](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f) for more details.
### disconnect()
Remove extensions listener and disconnect extensions background script connection. Usually just unsubscribing the listener inside the `connect` is enough.
<aid="send"></a>
### send(action, state, [options, instanceId])
Send a new action and state manually to be shown on the monitor. It's recommended to use [`connect`](connect), unless you want to hook into an already created instance.
##### Arguments
- `action`_String_ (action type) or _Object_ with required `type` key.
- `state`_any_ - usually object to expand.
- [`options`] _Object_ - [see the available options](Arguments.md).
- [`instanceId`] _String_ - instance id for which to include the log. If not specified and not present in the `options` object, will be the first available instance.
<aid="listen"></a>
### listen(onMessage, instanceId)
Listen for messages dispatched for specific `instanceId`. For most cases it's better to use `subcribe` inside the [`connect`](connect).
##### Arguments
- `onMessage`_Function_ to call when there's an action from the monitor.
- `instanceId`_String_ - instance id for which to handle actions.
<aid="open"></a>
### open([position])
Open the extension's window. This should be conditional (usually you don't need to open extension's window automatically).
##### Arguments
- [`position`] _String_ - window position: `left`, `right`, `bottom`. Also can be `panel` to [open it in a Chrome panel](../FAQ.md#how-to-keep-devtools-window-focused-all-the-time-in-a-chrome-panel). Or `remote` to [open remote monitor](../FAQ.md#how-to-get-it-work-with-webworkers-react-native-hybrid-desktop-and-server-side-apps). By default is `left`.
<aid="notifyErrors"></a>
### notifyErrors([onError])
When called, the extension will listen for uncaught exceptions on the page, and, if any, will show native notifications. Optionally, you can provide a function to be called when an exception occurs.
##### Arguments
- [`onError`] _Function_ to call when there's an exceptions.
This document exists to keep track of how the different parts of the Redux DevTools interact, since it's easy to forget how it all works together. This is intended for internal purposes and is just a collection of notes to myself.
## Entry Points
### Window
This is the default view that is shown in the Redux DevTools popup, the Chrome DevTools tab (if direct access to the background page is available), and new popup windows that are created. It has direct access to the background page via `chrome.runtime.getBackgroundPage`.
### DevPanel
This is the view that is shown in the Chrome DevTools tab if direct access to the background page is not available.
Initially this was the view that was always used for the Chrome DevTools tab, but when support to directly access the background page from the DevTools tab was added, [the Window View became the preferred view](https://github.com/zalmoxisus/redux-devtools-extension/pull/580).
### Remote
This does not interact with the other parts of the extension at all, it just renders the `App` component from `@redux-devtools/app`.
It can be triggered by hitting the "Remote" button in any of the other views, which calls `chrome.windows.create` and creates a new window.
### DevTools
This is the script that adds the Redux panel in the Chrome DevTools using `chrome.devtools.panels.create`.
It creates a Window View if it has direct access to the background page, otherwise it creates a DevPanel View.
Note that this used to always show the DevPanel View, but [started using the Window View by default](https://github.com/zalmoxisus/redux-devtools-extension/pull/580) once direct access to the background page was added to Chrome DevTools tabs.
### Content Script
Passes messages between the injected page script and the background page.
It listens for messages from the injected page script using `window.addEventListener('message', ...)`. It knows the message is from the injected page script if `message.source` is `'@devtools-page'`. See the Chrome DevTools docs where this approach [is documented](https://developer.chrome.com/docs/extensions/how-to/devtools/extend-devtools#evaluated-scripts-to-devtools).
It creates a connection to the background page using `chrome.runtime.connect` with the name `'tab'` when it receives the first message from the injected page script.
- [Improve your development workflow with Redux DevTools Extension](https://medium.com/@zalmoxis/improve-your-development-workflow-with-redux-devtools-extension-f0379227ff83)
- [Using Redux DevTools in production](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f)
- [Redux DevTools without Redux](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f)
- [How to disable/enable it in production](#how-to-disable-it-in-production)
- [How to persist debug sessions across page reloads](#how-to-persist-debug-sessions-across-page-reloads)
- [How to open DevTools programmatically](#how-to-open-devtools-programmatically)
- [How to enable/disable errors notifying](#how-to-enabledisable-errors-notifying)
- [How to get it work with WebWorkers, React Native, hybrid, desktop and server side apps](#how-to-get-it-work-with-webworkers-react-native-hybrid-desktop-and-server-side-apps)
- [Keyboard shortcuts](#keyboard-shortcuts)
#### How to get it work
- Check the extension with [Counter](http://zalmoxisus.github.io/examples/counter/) or [TodoMVC](http://zalmoxisus.github.io/examples/todomvc/) demo.
- Reload the extension on the extensions page (`chrome://extensions/`).
- If something goes wrong, [open an issue](https://github.com/zalmoxisus/redux-devtools-extension/issues) or tweet me: [@mdiordiev](https://twitter.com/mdiordiev).
#### How to disable it in production
Usually you don't have to. See [the article for details on how to include it in production](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f).
#### How to persist debug sessions across page reloads
Just click the `Persist` button or add `?debug_session=<session_name>` to the url.
#### How to open DevTools programmatically
```js
window.__REDUX_DEVTOOLS_EXTENSION__.open();
```
Make sure to have it conditionally. Auto opening windows is a bad DX. See the [API](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Methods.md#open) for details.
#### How to enable/disable errors notifying
Just find `Redux DevTools` on the extensions page (`chrome://extensions/`) and click the `Options` link to customize everything. The errors notifying is disabled by default. If enabled, it works only when the store enhancer is called (in order not to show notifications for any sites you visit). In case you want notifications for a non-redux app, init it explicitly by calling `window.__REDUX_DEVTOOLS_EXTENSION__.notifyErrors()` (probably you'll check if `window.__REDUX_DEVTOOLS_EXTENSION__` exists before calling it).
#### How to get it work with WebWorkers, React Native, hybrid, desktop and server side apps
It is not possible to inject extension's script there and to communicate directly. To solve this, use [Remote Redux DevTools](https://github.com/zalmoxisus/remote-redux-devtools). After including it inside the app, click `Remote` button for remote monitoring.
#### Keyboard shortcuts
To set/change the keyboard shortcuts, click "Keyboard shortcuts" button on the bottom of the extensions page (`chrome://extensions/`). By default only `Cmd` (`Ctrl`) + `Shift` + `E` is available, which will open the extension popup (only when the Redux store is available in the current page).
One of the features of Redux DevTools is to select an action in the history and see the callstack that triggered it. It aims to solve the problem of finding the source of events in the event list.
By default it's disabled as, depending of the use case, generating and serializing stack traces for every action can impact the performance. To enable it, set `trace` option to `true` as in [examples](https://github.com/zalmoxisus/redux-devtools-extension/commit/64717bb9b3534ff616d9db56c2be680627c7b09d). See [the API](../API/Arguments.md#trace) for more details.
For some edge cases where stack trace cannot be obtained with just `Error().stack`, you can pass a function as `trace` with your implementation. It's useful for cases where the stack is broken, like, for example, [when calling `setTimeout`](https://github.com/zalmoxisus/redux-devtools-instrument/blob/e7c05c98e7e9654cb7db92a2f56c6b5f3ff2452b/test/instrument.spec.js#L735-L737). It takes `action` object as argument and should return `stack` string. This way it can be also used to provide stack conditionally only for certain actions.
There's also an optional `traceLimit` parameter, which is `10` by default, to prevent consuming too much memory and serializing large stacks and also allows you to get larger stacks than limited by the browser (it will overpass default limit of `10` imposed by Chrome in `Error.stackTraceLimit`). If `trace` option is a function, `traceLimit` will have no effect, that should be handled there like so: `trace: () => new Error().stack.split('\n').slice(0, limit+1).join('\n')` (`+1` is needed for Chrome where's an extra 1st frame for `Error\n`).
Apart from opening resources in Chrome DevTools, as seen in the demo above, it can open the file (and jump to the line-column) right in your editor. Pretty useful for debugging, and also as an alternative when it's not possible to use openResource (for Firefox or when using the extension from window or for remote debugging). You can click Settings button and enable that, also adding the path to your project root directory to use. It works out of the box for VSCode, Atom, Webstorm/Phpstorm/IntelliJ, Sublime, Emacs, MacVim, Textmate on Mac and Windows. For Linux you can use [`atom-url-handler`](https://github.com/eclemens/atom-url-handler).
[File an issue](https://github.com/zalmoxisus/redux-devtools-extension/issues) or [submit a PR](https://github.com/zalmoxisus/redux-devtools-extension/pulls) if you have suggestions, rate us and leave a review on [Chrome Store](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd/reviews), post feature requests and bug reports on [Product Pains](https://productpains.com/product/redux-devtools-extension), or ping me on Twitter as [@mdiordiev](https://twitter.com/mdiordiev).
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
@NgModule({
imports: [
StoreModule.forRoot(rootReducer),
// Instrumentation must be imported after importing StoreModule (config is optional)
StoreDevtoolsModule.instrument({
maxAge: 5,
}),
],
})
export class AppModule {}
```
[`Example of integration`](https://github.com/ngrx/platform/tree/master/projects/example-app/) ([live demo](https://ngrx.github.io/platform/example-app/)).
The recommended way is to use [`@redux-devtools/extension` npm package](/README.md#13-use-redux-devtools-extension-package-from-npm), which contains all typescript definitions. Or you can just use `window as any`:
```js
const store = createStore(
rootReducer,
initialState,
(window as any).__REDUX_DEVTOOLS_EXTENSION__ &&
(window as any).__REDUX_DEVTOOLS_EXTENSION__()
);
```
Note that you many need to set `no-any` to false in your `tslint.json` file.
Alternatively you can use type-guard in order to avoid
casting to any.
```typescript
import { createStore, StoreEnhancer } from 'redux';
### Export from browser console or from application
```js
store.liftedStore.getState();
```
The extension is not sharing `store` object, so you should take care of that.
### Applying multiple times with different sets of options
We're [not allowing that from instrumentation part](https://github.com/reduxjs/redux-devtools/blob/main/packages/redux-devtools-extension/src/logOnly.ts), which can be used it like so:
```js
import { createStore, compose } from 'redux';
import { devToolsEnhancerLogOnly } from '@redux-devtools/extension';
Make sure you [applied the enhancer](https://github.com/zalmoxisus/redux-devtools-extension#2-use-with-redux). Note that passing enhancer as last argument requires redux@>=3.1.0. For older versions apply it like [here](https://github.com/zalmoxisus/redux-devtools-extension/blob/v0.4.2/examples/todomvc/store/configureStore.js) or [here](https://github.com/zalmoxisus/redux-devtools-extension/blob/v0.4.2/examples/counter/store/configureStore.js#L7-L12).
Don't mix the old Redux API with the new one. Pass enhancers and applyMiddleware as last createStore argument.
### Access file url (`file:///`)
If you develop on your local filesystem, make sure to allow Redux DevTools access to `file:///` URLs in the settings of this extension:
### It shows only the `@@INIT` action or moving back and forth doesn't update the state
Most likely you mutate the state. Check it by [adding `redux-immutable-state-invariant` middleware](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/counter/store/configureStore.js#L3).
Another cause could be that you are creating multiple stores, which means that the devtools get attached to one but the application uses another. See [https://github.com/reduxjs/redux-toolkit/issues/2753](this issue).
### @@INIT or REPLACE action resets the state of the app or last actions RE-APPLIED
`@@redux/REPLACE` (or `@@INIT`) is used internally when the application is hot reloaded. When you use `store.replaceReducer` the effect will be the same as for hot-reloading, where the extension is recomputing all the history again. To avoid that set [`shouldHotReload`](/docs/API/Arguments.md#shouldhotreload) parameter to `false`.
### It doesn't work with other store enhancers
Usually the extension's store enhancer should be last in the compose. When you're using [`window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__`](/README.md#12-advanced-store-setup) or [`composeWithDevTools`](/README.md#13-use-redux-devtools-extension-package-from-npm) helper you don't have to worry about the enhancers order. However some enhancers ([like `redux-batched-subscribe`](https://github.com/zalmoxisus/redux-devtools-extension/issues/261)) also have this requirement to be the last in the compose. In this case you can use it like so:
```js
const store = createStore(
reducer,
preloadedState,
compose(
// applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__
? window.__REDUX_DEVTOOLS_EXTENSION__()
: (noop) => noop,
batchedSubscribe(/* ... */),
),
);
```
Where `batchedSubscribe` is `redux-batched-subscribe` store enhancer.
### Excessive use of memory and CPU
That is happening due to serialization of some huge objects included in the state or action. The solution is to [sanitize them](/docs/API/Arguments.md#actionsanitizer--statesanitizer).
You can do that by including/omitting data containing specific values, having specific types... In the example below we're omitting parts of action and state objects with the key `data` (in case of action only when was dispatched action `FILE_DOWNLOAD_SUCCESS`):
There's a more advanced [example on how to implement that for `ui-router`](https://github.com/zalmoxisus/redux-devtools-extension/issues/455#issuecomment-404538385).
The extension is in different process and cannot access the store object directly, unlike vanilla [`redux-devtools`](https://github.com/reduxjs/redux-devtools) which doesn't have this issue. In case sanitizing doesn't fit your use case, you might consider including it directly as a react component, so there will be no need to serialize the data, but it would add some complexity.
### It fails to serialize data when [passing synthetic events](https://github.com/zalmoxisus/redux-devtools-extension/issues/275) or [calling an action directly with `redux-actions`](https://github.com/zalmoxisus/redux-devtools-extension/issues/287)
React synthetic event cannot be reused for performance reason. So, it's not possible to serialize event objects you pass to action payloads.
1. The best solution is **not to pass the whole event object to reducers, but the data you need**:
```diff
function click(event) {
return {
type: ELEMENT_CLICKED,
- event: event
+ value: event.target.value
};
}
```
2. If you cannot pick data from the event object or, for some reason, you need the whole object, use `event.persist()` as suggested in [React Docs](https://facebook.github.io/react/docs/events.html#event-pooling), but it will consume RAM while not needed.
```diff
function increment(event) {
+ event.persist();
return {
type: ELEMENT_CLICKED,
event: event,
};
}
```
3. A workaround, to pass the whole object and at the same time not to persist it, is to override this key of the stringified payload in your action creator. Add a custom `toJSON` function right in the action object (which will be called by the extension before accessing the object):
```diff
function increment(event) {
return {
type: ELEMENT_CLICKED,
event: event,
+ toJSON: function (){
+ return { ...this, event: '[Event]' };
+ }
};
}
```
Note that it shouldn't be arrow function as we want to have access to the function's `this`.
As we don't have access to the original object, skipping and recomputing actions during hot reloading will not work in this case. We recommend to use the first solution whenever possible.
### Symbols or other unserializable data not shown
To get data which cannot be serialized by `JSON.stringify`, set [`serialize` parameter](/docs/API/Arguments.md#serialize):
```js
const store = Redux.createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__({
serialize: true,
}),
);
```
It will handle also date, regex, undefined, error objects, symbols, maps, sets and functions.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.