React Handbook đź“”

React Handbook đź“”

·

18 min read

React Concepts

JSX

a syntax extension to JavaScript, which gets compiled to JavaScript by Babel. After compilation, JSX expressions become regular JS function calls and evaluate to JS objects. So JSX can be passed around like any JS object. JSX attributes can accept both strings (inside "") and objects (inside {}). JSX prevents cross-scripting attacks (XSS) because everything is converted to a string before being rendered.

Difference between Element & Component

React elements are plain objects, are cheap to create. ReactDOM takes care of updating the DOM to match React elements. Elements are immutable, once created, you can't change its children or attributes. On the other hand, a component is an independent re-usable piece of code either in the form of a JS class or function that can generate a React element.

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));

React only updates what has changed inside an element, by comparing it with its older state. Screenshot 2020-04-14 at 11.33.40 AM.png

Props & State

Props are read-only. Props allow you to pass data into a component. A component must never modify its own props. Although React is flexible with that, there is a rule - A component should act like a pure function which means that they should not change its own input. Rules about state -

  1. Do not modify state directly. (if you do then it will not re-render the view. Always use setState)
  2. State updates may be asynchronous. React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. Use, the second form of setState that takes a function.
    this.setState((state, props) => ({
    counter: state.counter + props.increment
    }));
    
  3. State updates are merged with the current state of the component.

Class Components

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Calling the base constructor with props is required when you use local state.

DOM Events & Synthetic Events

Synthetic events are cross-browser wrappers around the browser’s native events, to make sure that they work identically in all browsers.

Returning false from an event handler doesn't prevent default action like in DOM events. If you need access to the underlying event, use the nativeEvent attribute to get it. Here are the

boolean altKey
number button
number buttons
number clientX
number clientY
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
number pageX
number pageY
DOMEventTarget relatedTarget
number screenX
number screenY
boolean shiftKey

Why are keys needed in lists of components?

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

When there are no keys defined, and there is a change in the list items as shown above, then React doesn't know which item is the newest one, so React compares all the elements in the list, to find out which one of them has changed. This is inefficient. So, by adding keys, like in the above example, React will know that the items with keys 2015 & 2016 have not been changed, and there is a new item with key - 2014 and it will render just that child. Also, using an index as a key is an anti-pattern and should be avoided when the order of the list items can change. More info here .

Controlled Components & Uncontrolled Components

In HTML, the form and its elements like input maintain their own state. When you let React keep a track of the state of the form and the React local state for the component becomes the single source of truth, then the form elements are called controlled components. Example -

Here React controls the state of the form elements

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

In uncontrolled components, the state is handled by the DOM itself. Details here.

When to use uncontrolled components?

Since an uncontrolled component keeps the source of truth in the DOM, it is sometimes easier to integrate React and non-React code when using uncontrolled components. It can also be slightly less code if you want to be quick and dirty. Otherwise, you should usually use controlled components.

More info here

Composition vs Inheritance

React has a powerful composition model that should be used instead of inheritance when you want to re-use code between components. Using composition it is possible to only use a part of an object in another object, but in the case of inheritance, your child object must contain the whole functionality of the parent. So instead it is better to use composition as they do at Facebook for thousands of components. More details here.

Advanced React Concepts

React Context

React context allows you to pass data into child components directly without the need to pass it through intermediate components. It should be used sparingly because it makes component reuse more difficult.

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language. For data that is specific to a few components only, yet needs to shared across them, use a state management library like Redux or Easy-Peasy

Read here .

Error boundaries

A JavaScript error in a part of the UI which contains Declarative code, should not break the whole app. Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

Error boundaries do not catch errors for:

  • Event handlers (Use try-catch inside event handlers)
  • Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
  • Server-side rendering
  • Errors are thrown in the error boundary itself (rather than its children)

Higher-Order Components

A higher-order component (HOC) is an advanced technique in React for reusing component logic. They are a function which takes a component as input and returns a more advanced component. It recommended not to mutate a component inside a HOC, because by doing that it can not be re-used.

Ex: Redux's connect() function, withRouter() from react-router-dom (Gives access to the page URL)

Render Props

The term “render prop” refers to a technique for sharing code between React components using a prop whose value is a function. To put it in another way, a render prop is a function prop that a component uses to know what to render. It allows a prop to dynamically determine what to render.

Detailed article here.

Codepen link with example.

Examples - Formik, ReactRouter

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

Note: you can implement most higher-order components (HOC) using a regular component with a render prop.

// If you really want a HOC for some reason, you can easily
// create one using a regular component with a render prop!
function withMouse(Component) {
  return class extends React.Component {
    render() {
      return (
        <Mouse render={mouse => (
          <Component {...this.props} mouse={mouse} />
        )}/>
      );
    }
  }
}

React.PureComponent vs React.Component

The basic difference is that, React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison. React.PureComponent’s shouldComponentUpdate() only shallowly compares the objects.

React.memo

A useful way to achieve performance boosts is by using - React.memo

Note: Only extend PureComponent when you expect to have simple props and state, or use forceUpdate() when you know deep data structures have changed.

Details here .

React Hooks

Added in React 16.8. They allow you to use state and other React features without writing a class. Hooks allow you to reuse stateful logic without changing your component hierarchy. Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don’t work inside classes

Using classes adds a lot of boiler-plate code like binding event handlers and the use of the "this" keyword a lot.

useState() hook

It declared a state variable and returns a tuple with a reference to the state variable and a setter function.

const [name, setName] = React.useState('Biswa');

useEffect() hook

It allows you to perform side-effects inside React functions. Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. useEffect Hook is a combination of componentDidMount, componentDidUpdate, and componentWillUnmount. useEffect tells React to do something after render.

Note: useEffect runs after every render.

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
    return () => {
       // Cleanup code, runs before unmount, equivalent to componentWillUnmount
    };
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
// To emulate componentDidMount pass an empty array[]

Rules of hooks

  • Only call hooks at the top level (Don't call inside loops, conditions & nested conditions)
  • Only call hooks from React functions or custom hooks & not regular functions.

Custom hooks

A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

Additional hooks

useReducer()

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useCallback()

Returns a memoized callback. Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useMemo()

Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useRef()

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useImperativeHandle

Read here .

useLayoutEffect

The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint. Read here .

Difference between useEffect & useLayoutEffect

A great blog post here .

More details here .

Other React Concepts

Code-splitting using import()

import("./math").then(math => {
  console.log(math.add(16, 26));
});

Webpack will perform code-splitting when it sees this code. This is available out-of-the-box with next.js and create-react-app.

Dynamic Import & React lazy loading The lazy component should be rendered inside a Suspense Component. (Note: lazy() and suspense are not supported in server-rendered apps, so you can use Loadable for it)

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

Route based lazy loading of components

Pages are loaded lazily when the route changes. Details here.

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

Forwarding refs

Read here .