Aman's Blog

Thinking in React—few tips

April 08, 2018

I have been using React for two years now. I must say the journey so far was exciting and has taught me many things. My first React learning resource was a YouTube Video and a book named React Essentials. Since then, I have worked on a few projects and observed how it can change your life as a programmer.

React is a declarative JavaScript library for building user interfaces. What I mean by declarative is that it does what you tell it to do, rather than describing control flow. Learning React can be fun and challenging at the same time. The great power lies in using the React concept the right way. React documentation has a nice article about the thought process of building apps which I recommend.

Through this article I would like to share some of the things I learned and hope you may find them helpful.

1. Looking At The Bigger Picture

There’s always a big emphasis on creating React Components which can be reusable, maintainable and easy to test. For beginners it’s always very tempting to dump all the rendering logic into one big component. I think that’s how we all started to learn React. But, as our mind power grows, we can see this is not how it’s meant to be done.

Here are few things we can do before we start writing code:

  • Divide the bigger UI into tiny pieces. There will always be a pattern of child components nested inside parent (container) components.

  • Look for UI parts which are being used in more than one place within the application.

  • See if they can live independently which means checking if they own their state or it’s being provided by their parent component.

  • If they can live independently, try to see what are the rendering and event handling requirements.

2. React PropTypes — your best friend

I personally love React’s built-in typechecking feature as it gives me confidence about getting my components to render properly. But, I have seen beginner developers abusing it to the level where its values is completely lost.

For example, let’s create a component called ComponentA which passes its state as props to ComponentB (child component) which also forwards some slice of that props to ComponentC:

const someArbitraryObj = {
  name: "object has no name",
  someArray: [
    {
      id: 1,
      color: "red",
      value: {
        nestedValue: 20
      }
    },
    {
      id: 2,
      color: "yellow",
      value: {
        nestedValue: 10
      }
    }
  ],
  someObject: {
    age: 20
  },
  objectContainingArray: {
    values: [1, 3, 4]
  }
};

// somewhere in render function of ComponentA
<ComponentB values = {someArbitraryObj} />

As we can see the structure of the props ‘values’ is complex. The temptation while creating the componentB would be to create static propTypes like this:

// inside ComponentB (wrong way of using type checking)
static propTypes = {
  values: PropTypes.object
}

As we know everything in JavaScript(JS) is an object. So, by doing the above, we lose the benefit of props type checking. To leverage the power of React’s built-in typechecking ability and to catch most of bugs (via JS console), we can use some validators to ensure valid data is provided. In our case, we can check the validity of nested properties as well. Let’s see that in action:

ComponentB:

import { object, array, shape, string, arrayOf, number } from "prop-types";

// inside ComponentB
static propTypes = {
    values: shape({
      name: string,
      someArray: arrayOf(
        shape({
          id: number.isRequired, // if passed props doens't have this id,
          // you will see console warning and thus helps you to identify
          // problem in advance.
          color: string.isRequired,
          value: shape({
            nestedValue: number
          }).isRequired
        })
      ),
      someObject: shape({
        age: number
      }),
      objectContainingArray: shape({
        values: arrayOf(number)
      })
    })
  };

render() {
 return <ComponentC list={this.props.values.someArray} />;
}

ComponentC:

import { arrayOf, shape, number } from "prop-types";
import { map } from "lodash";

const ComponentC = props => {
  const list = map(props.list, el => {
    return (
      <h3 key={el.id}>
        color: {el.color} --- value: {el.value.nestedValue}
      </h3>
    );
  });

  return <div>{list}</div>;
};

ComponentC.propTypes = {
  list: arrayOf(
    shape({
      id: number.isRequired,
      value: shape({
        nestedValue: number.isRequired
      })
    })
  )
};

ComponentC.defaultProps = {
  list: [] // empty array
};

export default ComponentC;

Now by using validators for the deeply nested properties, we can be sure that when valid object is provided, our components will render properly.

Try changing the id 1 to ‘someString’ and you can see a few JS console warnings indicating invalid prop supplied.

Bonus Tip: You can create a separate .js file to contain your custom prop types and export them where ever you want instead of writing them all over again.

3. Understanding setState a Litte further

You might have heard lots of talk about which React lifecycle hook to be used for calling setState. As this function is the core of React, it’s important to understand what happens when you call this function.

  • You can see setState as more of requesting React to set new state for the component rather than synchronously doing it.

  • React has its own algorithm of batching the requests and flushing the state update at the end of browser events.

  • Don’t rely on this.state to reflect new values immediately after calling setState. If you intend to do so, pass an updater function instead of an object if you need to compute values based on the current state.

  • When you pass updater function inside setState call, it allows you to access the current state.

import React from "react";
export default class Counter extends React.PureComponent {
  state = {
    count: 0 // initial state
  };

  handleIncrement = () => {
    // as increment count depends on previous count value,
    // we should use updater function
    this.setState(prevState => ({      count: prevState.count + 1    }));  };

  render() {
    return (
      <div>
        <h1>Counter value is: {this.state.count}</h1>
        <button onClick={this.handleIncrement}>Increment</button>
      </div>
    );
  }
}

4. One Job per Component

You can embed few lines of logic in your render function which is alright. But, I always find it easy to divide the logic and delegate that to other component if render function starts to look messy. Let’s see that in example:

// Inside parent component
  state = {
    isLoading: true,
    posts: [],
    isErrorLoading: false,
    error: null
  };

  // rendering logic handles by Parent Component (if statements)
  // but the actual rendering job is handled by nested components
  render() {

    // LoadingComponent is resuable and can be used
    // anywhere in the application
    if (this.state.isLoading) {

      return <LoadingComponent />;    }
    if (this.state.isErrorLoading) {

      return <ErrorLoadingComponent error={this.state.error} />;    }
    return (
      <div>
      {/* some component */}
      </div>
    );
  }
}

5. Using componentWillUnmount wisely

You might have encountered an error in console saying “can’t call setState on unmounted component” and have spent some time struggling to find the root cause. It’s frustrating.

Always remember to clean things before you leave

let say you invoked asynchronous setTimeout call in componentDidMount and when callback invokes, you are setting the state:

componentDidMount() {
    setTimeout(() => {
      this.setState({
        value: 20
      });
    }, 1000);
  }

Now for some reason, if the component is unmounted (manually) before the callback invokes, we are still keeping a reference to component instance via this in setTimeout. And thus garbage collector won’t remove it from the memory and this.setState would still be a valid call causing the error to pop out. Let see how we can fix this problem.

componentDidMount() {
    setTimeout(() => {
      // checking if component hasn't been unmounted and
        // thus it's safe to call setState
      if(!this.isUnMounted) {         this.setState({        value: 20      });      }    }, 1000);
  }

componentWillUnmount() {
 this.isUnMounted = true;
}

Hope this was helpful. Happy coding 😃.


Amandeep Singh

Written by Amandeep Singh. Developer @ Thinkmill Sydney. Tech enthusiast and a pragmatic programmer.