8 propositions that will improve your React Application

8 propositions that will improve your React Application

8 propositions that will improve your React Application

After using React for developing web and mobile applications for a series of projects, I’ve come across a lot of typical challenges and good approaches to handling them. In this article, I’ve summed up 8 propositions that might come in handy for your React project, both if you’re new to React and if you’re an intermediate React Developer.

1. Use create-react-app

Don’t spoil your good mood by wasting unnecessary time on config! Unless you have very specific requirements for your application, there is no point in spending a lot of effort on setting up your environment over and over, every time you need to start a new project. Create-react-app is an officially supported boilerplate that bootstraps your new project for you. You’ll be ready to write code in a matter of seconds.

You’ll definitely find developers that disagree with me, but I still rest my case! I use create-react-app 95% of the time.

Check out Facebook’s official page and see how to benefit from create-react-app.

2. Keep yourself updated with the React ecosystem

The React ecosystem is huge and constantly expanding. You can find a ready-to-go solution to a broad range of problems, including everything from styling, tooling, state management and prebuild components.

Check out this awesome-react list to see if it offers a solution that suits your project. Generally, I recommend asking yourself whether someone else might have already created a suitable solution to your problem before beginning solving it yourself. Google is mostly just a few clicks away.

3. Use PropTypes

As your codebase grows, it can save you a lot of time catching bugs by typechecking.

For large-scale solutions, I would recommend using JavaScript extensions such as TypeScript, though using React’s built-in typechecking goes a long way.

PropTypes is really easy to use. Let’s look at an example.

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class BlogPost extends Component {
  render() {
    return (
      <div>
        <div>{this.props.blogTitle}</div>
        <div>{this.props.blogID}</div>
      </div>
    );
  }
}

BlogPost.propTypes = {
  blogTitle: PropTypes.string.isRequired,
  blogID: PropTypes.number
}

In this example, we want to make sure that the props blogTitle and blogID is of types string and number respectively. Furthermore, we want to make sure that blogTitle is always provided by simply chaining isRequired

Any violations of the above will result in a warning message in the console.

4. Use functional components

If your component doesn’t require any internal state or lifecycle hooks such as componentDidMount, use a functional component instead of a regular class component.

// Class component
class BlogPost extends Component {
  render() {
    return (
      <div>
        <div>{this.props.blogTitle}</div>
        <div>{this.props.blogID}</div>
      </div>
    );
  }
}

// Functional component
const BlogPost = props => (
  <div>
    <div>{props.blogTitle}</div>
    <div>{props.blogID}</div>
  </div>
);

In this example, we see how we can avoid unnecessary complexity and lines of code by replacing a class component with a functional component.

In this way, you’re using less and more readable code, thus you’re avoiding this binding. You should always strive to use functional components, whenever possible. In this way, you’re also forcing yourself to reconsider whether a component should be stateful, and avoiding accidentally storing state on a component that you shouldn’t or didn’t have to.

5. Beware of setState

When using setState() to update state, beware that a call to setState() may be asynchronous. Because React will try to batch multiple setState() calls into a single update, you should not rely on the value of state being updated immediately.

setBlogPostViews = viewsAmount => {
  this.setState({ postViews: viewsAmount });

  // Wrong! We cannot rely on the state being updated
  this.addToTotalViews(this.state.postViews);
}

In this example, we might get into trouble. Because we cannot rely on the postViews state being updated before we pass the value to the addToTotalViews() function on the next line.

Here are 3 smooth ways you can get around this.

1. Pass the subsequent statements wrapped in a callback function to setState()

setBlogPostViews = viewsAmount => {
  this.setState({ postViews: viewsAmount }, () => this.addToTotalViews(this.state.postViews));
}

setState() takes a callback function as a second parameter, which is invoked as soon as the state is effectively updated. This makes the call to addToTotalViews() reliable.

2. Pass a function to setState() instead of an object literal.

setBlogPostViews = viewsAmount => {
  this.setState(() => {
    // Do stuff here
    this.addToTotalViews(viewsAmount);

    // And then return the state to be updated
    return { postViews: viewsAmount }
  });
}

In cases where we want to update a state based on a current state, we should use the same technique to avoid potential race conditions.

incrementBlogPostViews = () => {
  // Wrong! We risk an unreliable update if the above function is called from different places concurrently
  this.setState({ postViews: this.state.postViews + 1 });

  // Correct. We pass a function with the previous state as a parameter to setState()
  this.setState(prevState => ({ postViews: prevState.postViews + 1 }));
}

3. Create an async wrapper function that returns a promise

Personally, I’m a huge fan of this little trick. Simply wrap setState in a function that returns a promise which resolves when the state has been effectively updated.

Here is an example of a wrapper function in two versions: ES7 using the async keyword, and ES6 by returning a new promise.

// Define an async wrapper function (ES7)
setBlogPostViewsAsync = async viewsAmount => {
  this.setState({ postViews: viewsAmount }, () => Promise.resolve(viewsAmount));
}

// Define an async wrapper function (ES6)
setBlogPostViewsAsync = viewsAmount => {
  return new Promise(resolve => {
     this.setState({ postViews: viewsAmount }, () => resolve(viewsAmount));
  });  
}

// Use the wrapper function instead of setState directly
setBlogPostViews = viewsAmount => {
  this.setBlogPostViewsAsync(viewsAmount)
    .then(updatedState => this.addToTotalViews(updatedState));
}

You can also choose to do a more generalized async wrapper function that would set any state and return a promise.

6. Avoid arrow functions and binds in render

It’s very common to run into situations where it might appear simple to use arrow functions or binds inside the render() method. The following are examples of such use

// Using arrow function in render
render() {
  return (
    <BlogPost
      postID={this.props.id}
      blogTitle={this.props.title}
      onDeletePost={() => this.deletePost(this.props.id)}
    />
  );
}

// Using bind in render        
render() {
  return (
    <div>
      <h1>Blog post</h1>
      <button onClick={this.showBlogPost.bind(this)} />
    </div>
  );
}

Both approaches are simple, clear and readable, but should still be avoided. The reason is, that in both cases a new function is allocated which leads to performance implications. It might not seem like such a big problem and in many situations, the performance issue is unlikely to be noticeable.

But imagine that you are using map() to render a big list of components, and do this multiple times for each, or that you — as your application grows, end up doing this a whole lot all over the place. This will put the garbage collector under extra pressure, thus the passing of an anonymous function to blogPost will cause an extra re-render, all causing a potentially noticeable decrease in rendering performance.

So instead of going into a whole discussion about where you draw the line, simply just consider this bad practice, and avoid using arrow functions and binds in the render() method altogether.

There are several ways this can be achieved and depends on how you’ve structured your code. An example could be

deletePost = () => {
  this.performDeleteOperation(this.props.id);
}

render() {
  return (
    <BlogPost
      postID={this.props.id}
      blogTitle={this.props.title}
      onDeletePost={this.deletePost} // <- No arrow function
    />
  );
}

7. Computed property names

One of many cool things that were introduced in ES6 is computed property names. Wrap a property name with [] and JavaScript will evaluate the property name dynamically.

In the following example, I demonstrate a typical problem that occurs when controlled components scales

class BlogPostForm extends Component {
  state = {
    blogTitle: "",
    blogAuthor: "",
    blogText: "",
    slug: "",
  }

  handleBlogTitleChange = event => {
    this.setState({ blogTitle: event.target.value }); 
  }

  handleBlogAuthorChange = event => {
    this.setState({ blogAuthor: event.target.value }); 
  }

  handleBlogTextChange = event => {
    this.setState({ blogText: event.target.value }); 
  }

  handleSlugChange = event => {
    this.setState({ slug: event.target.value }); 
  }

  handleCreateBlogPost = () => {
    this.performCreatePostOperation();  
  }

  render() {
    return (
      <div>
        <input 
          value={this.state.blogTitle} 
          onChange={this.handleBlogTitleChange} 
         />
        <input 
          value={this.state.blogAuthor} 
          onChange={this.handleBlogAuthorChange} 
         />
        <textarea onChange={this.handleBlogTextChange}>
          {this.state.blogText}
        </textarea>
        <input 
          value={this.state.slug} 
          onChange={this.handleSlugChange} 
        />
        <button value="Post now" onClick={this.handleCreateBlogPost} />
      </div>
    );
  }
}

For every input we want to include in a controlled component like this, we introduce a function to handle its change. These functions are almost identical and they end up taking a lot of space.

In a situation like this, we can reduce a lot of lines of code, by replacing all these functions with one generalized function to handle the inputs change using a computed property name. All we then need to do is to give the input elements a name attribute with a value of the state it should update.

class BlogPostForm extends Component {
  state = {
    blogTitle: "",
    blogAuthor: "",
    blogText: "",
    slug: "",
  }

  // Uses a computed property name
  handleInputChange = event => {
    this.setState({ [event.target.name]: event.target.value });
  }

  handleCreateBlogPost = () => {
    this.performCreatePostOperation();  
  }

  render() {
    return (
      <div>
        <input 
          name="blogTitle"
          value={this.state.blogTitle} 
          onChange={this.handleInputChange} 
         />
        <input
          name="blogAuthor"
          value={this.state.blogAuthor} 
          onChange={this.handleInputChange} 
         />
        <textarea 
          name="blogText"
          onChange={this.handleInputChange}
         >
          {this.state.blogText}
        </textarea>
        <input 
          name="slug"
          value={this.state.slug} 
          onChange={this.handleInputChange} 
        />
        <button value="Post now" onClick={this.handleCreateBlogPost} />
      </div>
    );
  }
}

8. Using HOC’s

A HOC, or a Higher-Order Component, is among the more advanced techniques in React. However, when you’ve been using HOC’s for a bit, it becomes really clear how powerful the approach is.

If you’re into functional programming you might be familiar with the concept of higher-order functions, that, given a function, returns a new function.

In the same way, a HOC is a function that takes a component and returns a new component.

This idea is very useful when we want to reuse functionality on different types of components. We can achieve this by wrapping our presentational components in a container component that contains the functionality we wish to reuse.

Let’s say that we have a list of blogPosts objects, we have a function that will filter these blogPosts based on the author, and we have different components where we wish to have this filtered list displayed.

Instead of copying over this filtering functionality, we can define a HOC that takes a presentational component, the blogPosts and authorName, and applies the filtering logic to it

const FilteredAuthorHoc = (WrappedComponent, blogPosts, authorName) => {
  return class extends Component {
    state = {
      // Maybe we would have some state...
    }

    componentDidMount() {
      // Maybe we would do something on mount... 
    }

    filteredBlogPosts = () => {
      const filteredPosts = blogPosts.filter(post => post.author === authorName);
      return filteredPosts.map(post => <div>{post.blogPostName}</div>);
    }

    render() {
      return (
        <WrappedComponent {...this.props}>
          {this.filteredBlogPosts()}
        </WrappedComponent>
      );
    }
  }
}

The HOC now serves as a prototype component which we can use to compose varies components with the same shared internal logic.

Note how the class that we return does not have a name. It’s an anonymous class. Note also that we pass any additional props on to the wrapped component since we’re not concerned about what other props it’s taking.

Now we can use our HOC like this

// We get a list of blogPost objects and an authorName from somewhere
const blogPosts = this.retrieveSomeBlogPosts();
const authorName = this.getNameOfSomeAuthor();

// We have a presentational component
const PostItems = props => (
  <div>
    <h3>{props.title}</h3>
    <div>
      {props.children}
    </div>
  </div>
);

const BlogPostItems = FilteredAuthorHoc(PostItems, blogPosts, authorName);

return (
  <BlogPostItems
    title="Interesting posts"  
  />
);

That’s it! If you have any questions or feedback, please feel free to comment below. If you liked this article, give the clap 👏 button a couple of hits!