forked from mirrors/gecko-dev
### 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®exp=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
54 lines
1.6 KiB
JavaScript
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;
|
|
};
|