Chapter 12. Redux beyond React

This chapter covers

  • Using Redux outside of a React web application
  • Implementing Redux in mobile and desktop applications
  • Using Redux in other web frameworks

JavaScript spaghetti code doesn’t discriminate against which frameworks it appears in. That’s one ailment applications across the framework spectrum have in common. Redux is most often billed as the cure to messy state management in React applications, but one of its major selling points is its compatibility with many JavaScript frameworks and environments. As long as bindings exist between Redux and the framework, you’re good to go.

Because of its value and flexibility, Redux bindings have appeared for many of the major JavaScript frameworks. Where Redux isn’t compatible, you’ll often find that Redux-inspired packages have been written to make the ideas and patterns accessible in that environment. Given the small size of the Redux API, writing the bindings for a new framework is a manageable workload.

To iterate an idea espoused throughout this book, it may be helpful to think of Redux as a design pattern first. The package you import from npm merely contains a few helpful methods to facilitate that design pattern in your application. You’ll be hard-pressed to find a client-side application where the design pattern can’t be applied.

Libraries such as React are a natural fit for Redux because the surface area is small; it has few opinions about how state should be managed on a macro scale. More opinionated frameworks usually require more consideration of how the Redux pattern fits into their paradigm, but as evidenced by all the binding packages, it can be done and often brings with it an improved quality of life.

In this chapter, we’ll explore using Redux in development environments outside of React web applications. React’s sibling mobile-development framework, React Native, is covered first. It’s a relatively simple transition for Redux from the web and the source of countless questions on developer forums. After that, we’ll explore Redux within desktop applications, using the Electron framework. Finally, we’ll break into other JavaScript web frameworks.

12.1. Mobile Redux: React Native

React Native is a popular mobile development framework maintained by Facebook and community contributors. The open source tool allows you to build native Android and iOS applications using JavaScript, and React, specifically. React DOM and React Native are alternative renderers that leverage the same diffing algorithm under the hood: React’s reconciliation algorithm. The role of that algorithm is to monitor changes to component state, then stingily trigger updates only to those elements that require them.

Because they share the same underlying mechanisms, React Native development feels similar to development of React for the web. Instead of HTML tags, React Native makes native mobile components available from both platforms. Conveniently, most React web development skills translate to React Native development skills, and that includes the use of Redux. What luck!

Within a React Native application, a top-level entry-point file may look similar to the analogous index.js file within a web app. Within that file, you should expect to see the Provider component, imported from react-redux and used to wrap the body of the application. Similar to a web application, the Provider component accepts a store as a parameter, so that it may make its contents available to specific children components within. The key takeaway here is that there are no specific, additional packages required to make Redux compatible with React Native applications. Just install redux, react-redux, and any middleware you require, then get to work!

12.1.1. Handling side-effects

Developing for mobile devices comes with unique concerns, but Redux doesn’t get in the way of handling those concerns. Let’s say you want to interact with the hardware of a device, such as the camera or accelerometer. These are side effects, like any other, and can be handled in a similar fashion to side effects within a React web application. Action creators, sagas, and middleware can be used to keep application logic out of your components.

Like a web application, many mobile apps interact with a remote server and local storage. Similar to when you employed the use of an API service in Parsnip, you can do the same within your React Native application. You may find it useful to have a separate service for each domain or side-effect type: remote server calls, local storage getters and setters, and hardware interactions. These abstractions are optional and subject to organizational preferences.

12.1.2. Network connectivity

When it comes to mobile apps, network connectivity cannot be guaranteed. As a result, mobile developers generally need to be more conscious of the offline user experience of their applications than web developers. Strategies for building a seamless offline experience vary based on the app, but one popular pattern is to store a minimal amount of data in the device’s local storage to reference if a network is unavailable. This way, the application can pull cached data to populate the app until a connection is restored.

Drawing on discussions from way back in chapter 2, can you recall a way to initialize a Redux store with data retrieved from local storage? Remember that the createStore method takes up to three arguments. The second argument is an optional initialState value:

const store = createStore(reducers, initialState, enhancers);

This initialState value can be retrieved from local storage on the device on startup. As the application is used, relevant data can be synced with local storage, so that it’s available next time the app is opened. If you’re developing a calendar app, for example, the user’s favorite events can be stored in local storage and viewed later, even if not connected to a network.

12.1.3. Performance

Mobile development comes with heightened concerns for performance. Although mobile devices become more powerful every week, their performance will naturally lag behind larger devices, such as laptops and desktop machines. The same way the size of the hardware plays a role in performance, so too will the wireless network connectivity, as discussed in the previous section.

The priority on performance makes following React and Redux best practices even more crucial. Chapter 10 of this book is dedicated to those best practices. If you’re working on a React Native app, pay special attention to opportunities where you can limit the number of unnecessary computations. As you might imagine, React Native applications are also subject to some performance concerns that are unique to the framework. You can read more about performance in the official documentation at https://facebook.github.io/react-native/docs/performance.html.

12.2. Desktop Redux: Electron

Adding to the idea of JavaScript taking over the world, Electron is a framework that enables you to write native desktop applications in JavaScript. The team at GitHub created the framework to facilitate the writing of their text editor, Atom (https://atom.io/). Fortunately for the community, both Atom and Electron are open source projects that anyone can learn from, fork, or contribute to.

12.2.1. Why native desktop?

Before you dive into implementation details, a quick aside: Why bother writing a native desktop application? Chances are a web application will be sufficient for your needs. With exceptions, you can expect to write the application once and have it work on any browser, operating system, or device. Beyond that, there’s a huge pool of web development talent to pull from for building and maintaining the system.

You have several excellent reasons to write desktop applications, however. Native apps are closer to the user. Once opened, they occupy prime real estate in the operating system’s menu, dock, or tray. They may always be visible or a keystroke away. Access to the native notification system allows you to alert users of important events and keep them engaged with your application. If your app requires frequent or frictionless use to provide value, this quality of a native application can’t be understated. Messaging, timers, to-do, and calendar applications are all typical examples, well-suited for a native environment, but a creative use of the same tools could set your product apart from competitors.

12.2.2. How Electron works

In the first chapter of Electron in Action, author Steve Kinney gives an excellent high-level introduction to the framework (https://livebook.manning.com#!/book/electron-in-action/chapter-1/v-11/point-452-75-83-0). From the app developer’s perspective, Electron starts with the main process: a Node.js application. Complete with a package.json file, you’re free to import whatever modules your application will need. Within that main process, you can create, hide, and show the application’s windows as well as the tray or menu icon.

For security reasons, each window you render is something of a sandbox environment. Naturally, you don’t want to give the user any permissions to interact with API keys or other sensitive variables that the main process needs to track. As a result, a separate client-side application is typically maintained in its own directory within the Electron app. You could, for example, move the contents of the Parsnip application into such a directory and expect it to function within an Electron window with minimal modifications.

Under the hood, Electron leverages Chromium, Google’s open source web browser, to render the contents of those windows. This is a massive boon for the developer experience. The Chrome Developer Tools are available within each window, and, consequently, so are the Redux DevTools. Even hot-loading with webpack is possible.

The main process and each renderer process need to have a way to communicate if the application is going to provide any real value. Electron’s system for communication across main and renderer processes is fittingly named IPC, or inter-process communication. IPC methods allow you to emit events from one process, while listening and responding to those events in another process.

Let’s say, for example, that a user wanted to upload a video in an Electron application. When the user selected their video file and clicked the submit button, an IPC message with the path of the video would be emitted. The code would look something like the following:

ipcRenderer.send('upload-video', '/path/to/video');

On the other side, the main process would watch for this event type and respond accordingly:

ipcMain.on('upload-video', (event, path) => {
   // handle the upload and return a success or failure response
}

Figure 12.1 illustrates this communication between processes.

Figure 12.1. IPC communication between Electron processes

This is as deep as we’ll take the discussion of Electron internals in this book. For full details, we recommend digging into a sister title in the Manning library: Electron in Action.

12.2.3. Introducing Redux to Electron

The client side of an Electron application can be written in any framework. The code running in each of the windows, or renderer process, can leverage any view library you want. React happens to be a popular option due to the architecture of Electron apps. Given that React handles the view layer, it fits well within a renderer process. Again, the role of the renderer process is to communicate with the main process, where the bulk of the business logic resides. The separation of concerns fits the React paradigm well.

Within the renderer process, the Redux store plays the same role it does in a web application: it decouples application logic from the view layer and organizes interactions with side effects. In terms of implementation, there’s no difference from the setup required in a web application. You may have differences in the types of side effects managed, but they’re still handled in the same locations you’re used to. Instead of interacting with local storage in an action creator, you may instead use IPC methods to kick off computation in the main process.

One detail worth revisiting is that the Redux store for a renderer process is isolated to that context. Multiple windows could have a separate Redux store, each operating completely independently of one another. While you’re at it, the main process can use a separate Redux store, too. On their own, each store has some value, but you have to imagine that it would be more valuable to keep each store in sync. Good news! A couple of open source packages provide that functionality.

Packages electron-redux and electron-redux-store offer two similar implementations for keeping multiple sandboxed stores in sync. Under the hood, both leverage IPC to communicate between the processes. Each provides a way for dispatched actions in one process to feed through the reducers of another process.

The electron-redux package declares the Redux store in the main process as the source of truth. Other process stores act as proxies. When creating renderer stores, they’ll need to get their initial state from the main store using a getInitialStateRenderer function. When using this package, all actions are required to be Flux Standard Action (FSA)-compliant.

We covered FSAs in an earlier chapter, but as a quick reminder, they require all actions to use a standard set of keys: payload, error, metadata, and type. electron-redux uses the meta key to determine if an action should be kept within a local renderer’s store or dispatched to the main process as well. For more implementation details, see the official documentation at https://github.com/hardchor/electron-redux.

We’d argue that the electron-redux-store is the simpler of the two packages to use. Installation requires fewer steps and dispatched actions need not be Flux Standard Actions. When updates are made to the main Redux store, you can subscribe renderer processes to subsets of store value changes using filters. For more details, reference the official documentation at https://github.com/samiskin/redux-electron-store.

12.3. Other Redux bindings

As you’ve learned, Redux bindings come in many flavors. Up to this point, this book has introduced only the react-redux package, providing the means to connect React components with the Redux store. In this section, we’ll take a quick tour of several of the most popular alternatives. If you’re interested in a more complete list, see Mark Erikson’s list at https://github.com/markerikson/redux-ecosystem-links/blob/master/library-integration.md.

12.3.1. Angular

Angular is one of the most popular JavaScript frameworks on the block. With its backing by Google and level of maturity as a project, it’s a regular choice for applications in the enterprise space. More recently, Angular overcame a great deal of churn when it released a rewrite of the framework for version 2.0.0. It’s possible to use Redux in an Angular v1 application and, if that’s the boat you find yourself in, you can view the corresponding Redux binding library here: https://github.com/angular-redux/ng-redux. We won’t cover the implementation details in this section, given that it’s unlikely a new project will choose Angular v1.

Angular 2 introduced components that more closely resemble React components. Between these new Angular components and other application-logic decoupling, Angular has become a more welcoming environment for introducing Redux. Welcoming is a relative term, however.

If you’re coming to Angular with only React experience, you’ll experience a mildly intimidating learning curve. Angular applications are written in TypeScript (https://www.typescriptlang.org/), a superset of JavaScript that adds optional static typing. The framework also leverages observables with RxJS and implements its own change detector, analogous to React’s reconciliation algorithm.

Angular was also written with the intention to cover more ground than React. Instead of only the view layer, Angular accounts for an application’s models and controllers in the MVC framework terminology. For these architecture-related reasons, the Redux bindings for Angular look and operate differently than react-redux.

Usage of Redux within an Angular app requires installation of the redux package, but additionally you’ll need to install @angular-redux/store. Similarly to React, you’ll need to make one of your top-level components aware of the Redux store. From there, you can inject NgRedux into the components you want connected with Redux functionality. The dispatch method is available on the object you inject, typically called ngRedux:

this.ngRedux.dispatch({ type: 'FETCH_TASKS' });

As for displaying Redux store data, the bindings are implemented using a method or a decorator, called select. select leverages Observables to make changes to the DOM when the store updates. As described in the RxJS documentation, “Observables are lazy Push collections of multiple values.” André Staltz, creator of Cycle.js, encourages you to think of them as “asynchronous immutable array[s].”

Angular makes regular use of Observables to handle asynchronous events. If this is foreign territory and you’d like more context on Observables, we recommend the RxJS documentation at http://reactivex.io/rxjs/manual/overview.html#observable. For detailed implementation instructions, reference the angular-redux bindings codebase here: https://github.com/angular-redux/store.

12.3.2. Ember

Ember is another popular JavaScript framework, regularly compared to and contrasted with Angular and React in blog posts and conference talks. Ember is similar in scope to Angular, offering models, views, and controllers. Drawing inspiration from Ruby’s Rails framework, Ember offers an opinionated take on a client-side application structure, baking in many common configuration choices, so you don’t have to.

The Redux bindings for Ember are used in a similar fashion to the bindings for React. You’ll need to install the ember-redux add-on. Installation of this add-on also installs redux, redux-thunk, and other dependencies. In Ember, components consist of two files, a Handlebars template for the HTML and a JavaScript file for deriving the data that gets rendered in the template. The goal in introducing Redux is to simplify the logic required in those component JavaScript files.

The method for connecting a component will look very familiar. Import connect from ember-redux and pass it the slices of state required by your component. You’ll find that stateToComputed is an Ember-friendly function name, but it’s implemented identically to mapStateToProps:

connect(stateToComputed, dispatchToActions)(Component);

Many of the other Redux elements will also look roughly the same as their react-redux counterparts. Once you’ve injected the Redux service into a route, it can be used to return an action following a successful API call:

redux.dispatch({ type: 'FETCH_TASKS', payload });

From there, reducers work like what you’re accustomed to. Reselect selector functions are available with a shim. The combineReducers package is used from the redux package, too. One method you won’t find in an Ember app, however, is createStore. Under the hood, ember-redux is taking care of it.

Ready to dive in? The codebase (https://github.com/ember-redux/ember-redux) and official documentation (http://www.ember-redux.com/) are a wealth of information. The documentation site contains a video tutorial and several example applications with advanced features. Check out the Redux Saga demo for an example middleware configuration. The use of sagas is identical to a React context, but because ember-redux hides the store creation from you, middleware initialization has a new home.

12.4. Redux without a framework

Though a bit unorthodox, there’s nothing stopping you from introducing Redux to a plain old JavaScript application. As you know, Redux bindings make it easy to use Redux within popular frameworks, but aren’t a requirement for its use. If you have a hand-rolled JavaScript codebase, Redux can, at a minimum, handle all updates in one predictable location, improving your ability to debug and test the application.

Within JavaScript applications suffering from closely coupled modules, breaking down your business logic into smaller, organized units (for example, action creators and reducers), can introduce a great deal of clarity. As you learned in chapter 9, there are clear paths for unit testing these components of the application.

Another argument for introducing Redux into this type of application is the potential insight offered by the Redux DevTools. Dispatching and monitoring actions are low-hanging fruit for an application that’s difficult to debug. Using the DevTools as a log aggregator can prove more timely and convenient than existing options.

Because this isn’t a conventional or popular option, you won’t find many playbooks online in the form of tutorials or official documentation. One example use case is within the main process of an Electron app, but again, documentation is hard to come by. We should also note that adding Redux to an application doesn’t guarantee that your state management woes will vaporize. When applied thoughtfully, though, Redux can help untangle “spaghetti” code without the use of bindings.

12.5. Exercise and solutions

As an exercise to expand your understanding of how Redux fits into other technology stacks, look at one or more of the starter kits described in this section. Your goal should be to read through the existing code to identify how and where the Redux stores are configured. Next, compare the usage patterns you see with those covered in this book while building Parsnip, the React web application. You should answer the following questions:

  • In this environment, does the creation of a Redux store differ from that of React web applications? If so, how?
  • What data is kept in the store?
  • Where are side effects handled and by what means?
  • What file organization patterns are used?

If you’re stumped while tracking down the Redux store initialization in any of the mentioned repositories, try using the global search functionality within GitHub to look for the createStore method.

If you’re interested in exploring Redux within a React Native application, look at one of the following starter kits. Both are robust options for getting a jump on a production-quality application and include many features beyond Redux. Do your best to focus exclusively on the Redux implementations the first time through the codebases. Your options are the starter kit by Matt Mcnmee, https://github.com/mcnamee/react-native-starter-app, and the Snowflake application by Barton Hammond, https://github.com/bartonhammond/snowflake.

Is Electron more your style? Try the starter kit created by C. T. Lin at https://github.com/chentsulin/electron-react-boilerplate. Again, you’ll find many features beyond Redux within this repository, so do your best to ignore those while you attempt to answer the previous questions.

Those looking for Angular 2+ implementations of Redux will be happy to find an officially supported example repository at https://github.com/angular-redux/example-app. This is one case where you won’t find the createStore method. Instead, search for store.configureStore to find one entry point into the Redux workflow.

Looking for Ember instead? Try this TodoMVC implementation at https://ember-twiddle.com/4bb9c326a7e54c739b1f5a5023ccc805. Again, you won’t find the createStore method here, as ember-redux takes care of store initialization under the hood.

You’ll notice there are few limits to where Redux can be applied within JavaScript applications. Many times, it’s the perfect tool for the job of taming a messy application state. You’ll find joy in breaking down state management into a predictable, testable, unidirectional data flow. A gentle reminder, though, for the road ahead: if Redux is a hammer, not every application is a nail.

If you’d like to dig deeper into the available Redux bindings, or countless other rabbit holes, check out the official ecosystem documentation page at http://redux.js.org/docs/introduction/Ecosystem.html.

Summary

  • Redux is flexible and generic enough for use in all kinds of applications.
  • React Native applications have unique constraints but can leverage Redux without any additional dependencies.
  • Electron applications can use Redux like a web application in the renderer processes, but each store is sandboxed. Open source options exist for synchronizing several Redux stores.
  • In addition to React, Redux bindings exist for many of the major web frameworks.
  • It’s possible to enjoy the benefits of Redux without bindings at all.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.113.67