Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Best Practices With React and Redux Web Application Development, Part 2

DZone's Guide to

Best Practices With React and Redux Web Application Development, Part 2

We wrap up our discussion of React and Redux best practices, and not so best practices. Read on for some developer insights!

· Web Dev Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

Welcome back! If you missed Part 1, check it out here

Consider the Fetch API Instead of jQuery Ajax. Also, Be Aware of Failed to Fetch

The fetch API is the latest standard for making asynchronous calls in the browser. It's very nice in that it uses the Promise API and provides a much cleaner experience for the developer. See MDN for an overview. We use this GitHub repo in which we have wrapper functions to call fetch where we generically type the calls and verify authentication.

One caution is the vague error TypeError: Failed to fetch This can happen with unauthenticated calls to the backend or a variety of other issues. Search https://fetch.spec.whatwg.org/ for "TypeError" for a full list. The problem with this error is it gives very little information, so when simply passing any caught fetch errors to something like Sentry, we have little context about what URL was being called at the time or what parameters. The recommendation here is, when catching fetch errors, always include the URL and other information to your error handling. This may seem obvious, but it isn't always so. Generally, when catching an Error, let's call it e, you would simply log(e), where log logs to the console or sends to some error handling site like Sentry. If just this is done, you'll be lacking a lot of necessary information.

Avoid this:

log(e);

Instead, do this:

log(e, {url: url, params: params, ....}

Where you can have the option to handle other parameters how you choose. Note that log is a contrived function, a log may be logging to the local console or to a remote server.

When Possible Only Redux Connects Primitives

This greatly simplifies optimizing components and follows the "principle of least privilege." In other words, a component should only have access to the fields where it needs access. We followed a model of accessor functions, so if we needed a single field in an object we wrote an accessor function to map that field. While that sounds like a bit of overkill, it has a few benefits. It guarantees that if we write the function as safe, then we'll have no 'undefined' errors accessing the field, and it allows for even easier refactoring, even with Typescript. Connecting only primitives is not always possible, but if possible, should be the desirable approach.

We experienced a period of time where due to bugs and backend server issues, we would see many "x is undefined" messages. Lovely error right? These are avoidable with the proper checks.

Avoid this:

class OrderViewer extends React.Component {
    render() {
        return this.props.order.name
    }
}

const mapStateToProps = (state: IStateReduced, props: IOwnProps): IReduxProps => ({
    order: state.order,
});

export default connect(mapStateToProps)(OrderViewer);

Not only is object equality automatically broken here on componentWillReceiveProps, but also there is an unsafe field access to order. Now, this is fine if you can absolutely guarantee that order is never undefined, but can you really guarantee that? That means you'd have to make sure to always set at least {} in your reducer. Even then, that would only protect against immediate fields in the object, not any nested fields.

Instead, do this: 

class OrderViewer extends React.Component {
    render() {
        return this.props.orderName
    }
}

const mapStateToProps = (state: IStateReduced, props: IOwnProps): IReduxProps => ({
    orderName: state.order && state.order.name,
});

export default connect(mapStateToProps)(OrderViewer);

On the other hand, you could write an accessor function like:

function getOrderName(state: IStateReduced) {
    return state.order && state.order.name;
}

const mapStateToProps = (state: IStateReduced, props: IOwnProps): IReduxProps => ({
    orderName: getOrderName(state),
});

This is more code but has some benefits during refactoring.

Export both the component and the connected component.

This is the same concept as presentational and container components. This allows for much easier component testing. The container connects redux data to the presentational component.

Avoid doing this: 

export class OrderViewer extends React.Component {
    // ...
}

const mapStateToProps = (state: IStateReduced, props: IOwnProps): IReduxProps => ({
    // ...
});

export default connect(mapStateToProps)(OrderViewer);

Instead, do this: 

export class OrderViewer extends React.Component {
    ...
}

const mapStateToProps = (state: IStateReduced, props: IOwnProps): IReduxProps => ({
    ...
});

export default connect(mapStateToProps)(OrderViewer);

This allows you to do both:

import { OrderViewer } from './orderViewer'

and

import OrderViewer from './orderViewer'

This may be confusing, so if you wanted to name your default export that may make more sense:

export class OrderViewer extends React.Component {
    // ...
}

const mapStateToProps = (state: IStateReduced, props: IOwnProps): IReduxProps => ({
    // ...
});

const ConnectedOrderViewer = connect(mapStateToProps)(OrderViewer);
export default ConnectedOrderViewer;

Then you can do:

import ConnectedOrderViewer from './orderViewer';

Avoid Anonymous Inner Functions in Component Event Functions

When using a component event attribute like onClick or onChange, avoid anonymous inner functions. These consume unnecessary memory every time the function is rendered.

Instead , do this:

class SomeComponent {
    onClick = (e: React.MouseEvent) => {

    }
}

...

So the next question would be: how do we handle a situation where we need to pass data to this event handling function? More components!

For example, let's say you need to pass some id to onClick. To avoid having to do this:

 this.onClick(e, id)}>...

You could create a new component:

interface IProps {
    id: string;
    onClick: (e: React.MouseEvent) => void;
}

export class ClickObject extends React.Component {

    onClick = (e: React.MouseEvent) => {
        this.props.onClick(this.props.id);
    }

    render() {
        return (
... ) } }

Breaking object equality also happens with passing inline objects as properties.

Instead, pass the object reference.

Be Aware of Functional Components and When You May Not Want to Use Them

Functional components are clean and concise ways to render into the DOM; however, they have no lifecycle methods, and while performance optimizations have been promised for a while, those have yet to be fully realized. So while they may be quicker by default, a full React component with a proper shouldComponentUpdate will be faster and give you more flexibility.

We do leverage functional components in quite a few places, my thoughts on them are not bad, I simply prefer full components as it's less to rewrite when you do actually need to optimize further. Also, for consistency reasons, switching between functional stateless components and full (stateful) components is a stylistic change. And while that is fine, I've found consistency in style to be important in a team environment. For example, do we want to mix sass and less? Not if we can avoid it, stick with one or the other. Again, this is not always possible, but consistency is a good thing.

Don't Settle for an Inefficient IDE

Historically, for the last several years, I've used JetBrains products and specifically Webstorm for web application development. Then we started using TypeScript and the performance in Webstorm was challenging. Several of the other members of the team were using VSCode; after switching, it's hard to imagine going back. VSCode is nearly always instant in its type checking and code completion and takes much less memory. The one thing I miss from JetBrains products is their stellar git merge conflicts GUI, it is second to none.

Avoid using any specific IDE in your development that causes you to lose time because of the IDE itself. There are simply too many options out there to lose valuable development time fighting your IDE.

Instead, find what works best for your specific application. For example, Webstorm worked great for us pre-TypeScript. After moving to TypeScript, it made more sense to move to an IDE that was designed specifically for TypeScript.

Insist on a Coding Standard and Enforce it With TSLint

Consistency. Consistency of style and code can avoid a whole host of problems. For example, indentation with spaces vs. tabs, and even the number of spaces in function definitions. Having a source of truth for the code style is very important and avoids both needing to correct one another, and unnecessary code changes. Find a tslint config you can agree on and use it. I may recommend AirBnB's comprehensive tool.

Avoid having no plan or using different tslint configs or styles.

Instead, agree upon common code styling amongst your team. I would even go so far to say you should agree upon common paradigms. For example, should you always avoid functional stateless components or will you use them in certain circumstances? If you have no agreed-upon style, you may write a simple functional component, which, then, another team member needs to rewrite to a full component, if the requirements change where lifecycle methods are required.

Use CI, and Have Functional Tests in CI or Executable by Development

The closer you can get the functional tests to the developer, the fewer bugs the developer will push or the quicker they will be able to test them. The goal is for development to find the bugs before QA. This is not possible unless there is a more comprehensive testing, like functional testing done before the code hits QA.

The subject of unit testing is a very loaded topic, one that has been addressed from many aspects at length, and frequently. My personal view is that unit testing is great as long as it doesn't consume a significant portion of the development, and as long as it can be proven valuable. If your unit tests are not driving down your bugs, change how you are writing your unit tests, or why are you writing them, to begin with. What I'm most interested in are tests that expose runtime bugs and incorrect behavior.

We use Jest for testing, where you render components and expect parts or the whole output to match what you indicate. While Jest is considered unit testing, I consider it somewhat of a hybrid approach to unit testing and functional testing as Jest renders DOM, simulated clicks can happen, and output can be checked. This is exposing behavior, not just checking properties. For the sake of argument though, we can still call this unit testing, if not much more elaborate unit testing, or we can call it component unit testing. We do still have functional tests written by our QA, which we are working to move to the CI layer.

Avoid functional and/or integration tests that are only run by QA. This creates a huge lag time in identifying runtime behavior bugs.

Instead, move your functional tests as close to development as possible, preferably allow development to be able to execute some level of functional or even integration testing before merging PRs. Consider Jest snapshot testing as well which is very fast. The goal is to allow near instant feedback on newly written code. The longer it takes to get that feedback, the longer it will take to identify and fix bugs.

Conclusion

The above recommendations represent things we've found to make our team more productive and to help manage risk. Each recommendation may not be the best practice for you or your product, but we hope they give you some insights to ponder. The higher level take away is to pursue efficiency and productivity during your development process. Even a small improvement in something like your dev side build speed can translate to many hours saved in the end. Take some time to consider the above recommendations, and search for other articles on best practices with React, there is a lot of great content out there to learn from.

There are many GUI tools to connect to MongoDB databases and browse, download this cheat sheet to get to the command line to get the command line you need.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
web dev ,react ,redux

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}