fune/devtools/client/inspector/store.js
Razvan Caliman f3e06f46f3 Bug 1623906 - [devtools] Dispatch thunks from Flexbox panel React components to highlight nodes r=gl,jdescottes,nchevobbe
### Problem
There is a lot of [prop drilling](https://kentcdodds.com/blog/prop-drilling) in React components to pass down a callback that is invoked at the bottom of a long chain of components.

[onShowBoxModelHighlighterForNode()](https://searchfox.org/mozilla-central/search?path=&q=onShowBoxModelHighlighterForNode) is one such example. It is created at the [Inspector client level](https://searchfox.org/mozilla-central/rev/25d491b7924493b5d4bedc07841a1cd92f271100/devtools/client/inspector/inspector.js#1885-1908) then passed to React components for panels, then drilled down to the consumer component which invokes it. There is also some [needless duplication](https://searchfox.org/mozilla-central/source/devtools/client/inspector/layout/layout.js#85-86) with [onShowBoxModelHighlighter](https://searchfox.org/mozilla-central/search?q=onShowBoxModelHighlighter%5CW&path=&case=false&regexp=true).

### Solution

With this patch, we leverage thunks in Redux.
In Redux, you can `dispatch()`:
- actions -> an object with an action type string which is matched by reducers
- thunks -> a function which can be async and can itself dispatch actions or other thunks

Thunks are supported by middleware already set up in the DevTools Redux [createStore()](https://searchfox.org/mozilla-central/source/devtools/client/shared/redux/create-store.js#54,104) helper. During store setup, we pass `thunkOptions` to the middleware, an object with arguments which will be available to all thunks when invoked. This is where we pass in the inspector client as a thunk option so we can invoke the highlighter.

This is the replacement for prop drilling the `onShowBoxModelHighlighterForNode()` method. The same way they dispatch actions, components can now dispatch the thunk to show/hide the highlighter when they need it without direct knowledge of the `inspector`, thus satisfying the original intent of passing down the `onShowBoxModelHighlighterForNode()` callback while cleaning up the code.

### Prior art
Thunks are not something new to DevTools. They are extensively used by the WebConsole, for example:
- passing [thunk options to the store](https://searchfox.org/mozilla-central/rev/25d491b7924493b5d4bedc07841a1cd92f271100/devtools/client/webconsole/webconsole-wrapper.js#95-104)
- making use of thunk options,  [webConsoleUI and hud](https://searchfox.org/mozilla-central/source/devtools/client/webconsole/actions/autocomplete.js#14-91) in a thunk to handle autocomplete


###Useful context:
DevTools helpers
- [thunkWithOptions](https://searchfox.org/mozilla-central/source/devtools/client/shared/redux/middleware/thunk-with-options.js)
- [shared Redux createStore helper with thunks middleware](https://searchfox.org/mozilla-central/source/devtools/client/shared/redux/create-store.js#54,104)
Redux docs
- [Async Actions in Redux](https://redux.js.org/advanced/async-actions)
- [dispatch() as default prop with redux-connect](https://react-redux.js.org/using-react-redux/connect-mapdispatch#default-dispatch-as-a-prop)

Differential Revision: https://phabricator.services.mozilla.com/D79556
2020-10-29 14:35:55 +00:00

54 lines
1.6 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const createStore = require("devtools/client/shared/redux/create-store");
const { combineReducers } = require("devtools/client/shared/vendor/redux");
// Reducers which need to be available immediately when the Inspector loads.
const reducers = {
// Provide a dummy default reducer.
// Redux throws an error when calling combineReducers() with an empty object.
default: (state = {}) => state,
};
function createReducer(laterReducers = {}) {
return combineReducers({
...reducers,
...laterReducers,
});
}
module.exports = inspector => {
const store = createStore(createReducer(), {
// Enable log middleware in tests
shouldLog: true,
// Pass the client inspector instance so thunks (dispatched functions)
// can access it from their arguments
thunkOptions: { inspector },
});
// Map of registered reducers loaded on-demand.
store.laterReducers = {};
/**
* Augment the current Redux store with a slice reducer.
* Call this method to add reducers on-demand after the initial store creation.
*
* @param {String} key
* Slice name.
* @param {Function} reducer
* Slice reducer function.
*/
store.injectReducer = (key, reducer) => {
if (store.laterReducers[key]) {
console.log(`Already loaded reducer: ${key}`);
return;
}
store.laterReducers[key] = reducer;
store.replaceReducer(createReducer(store.laterReducers));
};
return store;
};