Build Browser Extension with React/Redux

Allen Fang
ShopBack Tech Blog
4 min readMay 7, 2018

--

In the previous post: 10 tips about WebExtension, we gave some short WebExtension tips for you when we are developing a CashBack extension in ShopBack. However, there’s one thing that worth to mention is

How we build a WebExtension with React/Redux ecosystem.

We do believe there’re more and more browser extension built by React and Redux. Therefore, I want to share few basic knowledge about how to use Redux in the extension development and also a little bit experiences in the past few months.

How Redux work in WebExtension?

Browser extension is little different than regular web application, because there are three main scripts in extension but they are totally independent unit. So maybe you will have a first question about:

Where can I initialize the Redux store and how can I pass state or dispatch action to Redux between different Scripts?

I think there’re two ideas we can think it more:

  1. Background scripts is only and best place to contain the reducer, actionCreator and middleware. Which mean this is a good place to initialize Redux store.
  2. Popup and Content scripts are part of UI, it’s a place to dispatch actions to Redux. But how? If Redux store is created in Background Script, where can I get the store instance?

To sum up above two ideas, the difficult thing is we need to dispatch an action from Content/Popup scripts to Background scripts and also make sure the Redux state is synchronize to every subscriber. Fortunately, there’re already two libraries can help on it:

In above diagram, we have a centralize store which initialized in Background Scripts and it does make sense due to Background Scripts will be firstly running when your extension installed or bootstrapped. However, there’s only one thing you need to know is:

Both of libraries are only for Chrome/Firefox, because they are not WebExtension standard, even the author of redux-webext claim that is for WebExtension. But actually, the source code still rely on chrome namespace.

For both libraries, we think the react-chrome-redux will be best solution right now, because it have much clear and simple solution on passing actions and handling state updated . In near future, ShopBack will consider to contribute it to help the react-chrome-redux migrate to WebExtension standard or maybe we can create new one.

Selectors can be everywhere

Our extension also leverage reselect to help us to compute derived data and execute few business logic. Most of React/Redux developers use selectors when connecting to the React container. But actually, selectors can be everywhere!! In a browser extension development, you are not only have UI but also have Background Scripts.

We used selectors in actionCreators, middleware, event subscribers and React Containers and the other tiny Background Scripts which mean the selectors are spread to Content, Popup and Background Scripts so that we define a common selector modules to share all the selectors across all the extension units.

If you use reselect, please use it everywhere in your extension!!

However, if you want to use the selectors, you need the redux state right? But how and where you can get the Redux state even just in a normal scripts instead of React container? Let’s keep reading.

Share Store Object

If you used Redux in your extension development, you are supposed to keep whole the application states in the Redux store so that you can use react-redux to connect the store data to your React container. But in the Background Scripts: How you get the Store instance?

For example:

browser.webNavigation.onDOMContentLoaded.addListener(() => {
// need state here
});
browser.tabs.onActivated.addListener(() => {
// need state here
});

Anyway, we can do it more better. Let’s see the background entry point: we are supposed to create store when extension start:

Then pass though any functions which need the store:

Finally, write a factory function to receive the store then return the actual function for handling the logic:

You can use the same way to share the store object to anywhere in the Background Scripts.

Universal Store Subscriber

In a complicated extension, I do believe you have to know some states change then doing corresponding action. For example: If user switch account, extension need to know and load all the other information. It’s quite easy when you use react-redux but in the Background Script, store.subscribe is that only solution.

In our extension, we have an universal store subscribe like below:

The universal store responsible for creating singleton subscriber and return a function which will be called if state changed then pass the state data to the listener with corresponding state, like above domain example.

So each listener just care about their own state and execute correct logic if state changed.

Anyway, we only create a store subscriber in the background and register all listener. You can still create multiple subscribe, but it is seems to me that we don’t even create so much subscriber, developers are supposed to focus on the listener. In our extension, we have few tiny listeners to listen some specify state so that we can refetch/recompute the data in the background.

Conclusion

In near future, I will plan to write more articles about how we build the extension and contribute the react-chrome-redux to make it align WebExtension standard.

--

--