React

React is a front end UI library. It has nothing to do with backend.

The most important concept in React is "Component", which is similar to "Widget" in Flutter. It is the basic unit of UI element.

There are two style to write a component, one is functional and the other is class. I personally find class-based syntax is more intuitive.

All Component names must be capitalized (both file, class name, and function), otherwise they will be treated as regular html, not JXS.

React hooks are great, much better than Flutter's state management. But you need to be careful of differentiating immutable types in state and have a good design on where you put your states, or whether to merge states to prevent re-render and loops. See this good article if loops are annoying.

The Basics

This video is great.

Rendering JSX

The following three code snippets are equivalent:

import React from "react";

const FunctionalComponent = () => {
 return <h1>Hello, world</h1>;
};
import React from "react";

function FunctionalComponent() {
 return <h1>Hello, world</h1>;
}
import React from "react";

class ClassComponent extends React.Component {
 render() {
   return <h1>Hello, world</h1>;
 }
}

Note that reacts might automatically wrap what you return with <div> tags. If you don't like that, use <>.... Also class is a reserved keyword in Javascript, so use className instead.

Export and Export Modules

Export with Default Name (function name is the same as file name)

export default App;

Named Export

export {App}

You can import things other than Components.

import avatar form './avatar.png';

Or use require

<img src={require('./avatar.png')}>

The following two imports are equivalent:

import React from 'react'
React.cloneElement(...)

import {cloneElement} from 'react'
cloneElement(...)

Passing props

An react element can look like this:

const element = <div />;

but it can also look like this: where name is like field and Sara is like arguments to pass into functions. We call {Sara} props (think as a dictionary of arguments).

const element = <Welcome name="Sara" />;

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

The following codes are equivalent:

const FunctionalComponent = ({ name }) => {
 return <h1>Hello, {name}</h1>;
};
const FunctionalComponent = (props) => {
 return <h1>Hello, {props.name}</h1>;
};
class ClassComponent extends React.Component {
  render() {
    const { name } = this.props;
    return <h1>Hello, { name }</h1>;
 }
}

You should not modify its own props.

Events

Events in javascript is written like onclick whereas in react is written like onClick. Also, you should not directly invoke functions in react:

html

<button onclick="foo()"></button>

react

<button onClick={foo}></button>

Although you can add onClick directly in html, it is recommended to document.getElementById, then .addEventListener to html object. // TODO: why

Note that onClick can be passed into Components props.

Handling state

The following two snippets are equivalent:

const FunctionalComponent = () => {
 const [count, setCount] = React.useState(0);

 return (
   <div>
     <p>count: {count}</p>
     <button onClick={() => setCount(count + 1)}>Click</button>
   </div>
 );
};
class ClassComponent extends React.Component {
 constructor(props) {
   super(props);
   this.state = {
     count: 0
   };
 }

 render() {
   return (
     <div>
       <p>count: {this.state.count} times</p>
       <button onClick={() => this.setState({ count: this.state.count + 1 })}>
         Click
       </button>
     </div>
   );
 }
}

Since react will compare the old state with the new state to decide if DOM should be updated, you should never modify the state directly. Instead, if your state is, say, a dictionary, make a shallow copy of it. This can be achieved by {...oldState, newKey: newValue} or [...oldState, newElem]

useReducer is a wrapper around useState that allow you to customize complex logic that updates states.

Other Convenient Functions for Children

Instead of children.map, you can use React.Children.map(...) that doesn't throw error when children is null.

When a parent component wants to add or modify the props of its children (for example, adding parent's ref), use React.cloneElement. Note that you can't directly change children without copying, since children is the prop of parent, doing so will mutate prop inside itself, which is bad.

If cloneElement feels too ad-hoc, you can use render prop instead:

class RenderPropComponent extends React.Component {
  render() {
    return (
        {this.props.render('Hello, Render Prop!')}
    );
  }
}

function App() {
  return (
      <RenderPropComponent render={(data) => <p>{data}</p>} />
  );
}

Providers

// Provider.js
import React from 'react';
const Context = React.createContext();
const data = 0;
export const Provider = ({children}) => {
  const [state, setState] = React.useState(data)
  return (
    <Context.Provider value={{data}}>
      {children}
    </Context.Provider>
  )
}

export const useDataContext = () => React.useContext(Context)
import {useDataContext} from "../providers/Provider.js"

const Component = () => {
  const {data} = useDataContext();
  return (<div>
            <h1>data</h1>
          </div>);
}
export default Component

Reducer

const reducerFn = (value) => {
  return {key: value}
}
function App() {
  const initialState = {key: 0};
  const [state, dispatch] = useReducer(reducerFn, initialState);

  return (<button onClick={() => dispatch({key: 1})}></button>);
}
export default App;

Routing

To install react router: npm i react-router-dom

import {BrowserRouter} from "react-router-dom"

const root = ReactDOM.createRoot(document.getElement(...))
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
)
import {Routes, Route, Link} from "react-router-dom"

function App() {
  return (
    <Routes>
      <Link to="/">link</Link>
      <Route path="/about" element={<h1>about</h1>} />
      <Route path="/index" element={<h1>index</h1>} />
    <Routes>
  )
}

Hooks

See short Hooks API Reference

useEffect

useEffect hook is used to watch a state change and then call functions that generates side effects. It is good for http fetch. UseEffect should return a function that, once called, clean up stuffs.

For all hooks, don't use them in loops, conditionals, or subfunctions. Hook must be inside Components.

useRef

function App() {
  const inputRef = React.useRef(null);
  const focusInput = () => {
    inputRef.current.focus();
  }
  return (
    <input ref={inputRef} />
    <button onClick={focusInput} />
  );
}

useRef() create a javascript object just like {current: ...}, but the ref stay the same for every render. useRef() does not notify when its content changes (ie. mutation .current will not cause re-render).

useCallback and useMemo

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is mostly for optimization.

Every value referenced inside the callback should also appear in the dependencies array

Don't do any sideeffect in useCallback and useMemo, because react may choose to forget some value for performance optimization. You should useEffect to establish semantic guarantee.

On Mounting (componentDidMount)

The following two snippets are equivalent:

const FunctionalComponent = () => {
 React.useEffect(() => {
   console.log("Hello");
 }, []);
 return <h1>Hello, World</h1>;
};
class ClassComponent extends React.Component {
 componentDidMount() {
   console.log("Hello");
 }

 render() {
   return <h1>Hello, World</h1>;
 }
}

When the ref attribute is used on an HTML element, the ref callback receives the underlying DOM element as its argument.

render() {
  return (
    <div
      style={{ width: '400px', height: '400px' }}
      ref={(mount) => { this.mount = mount }}
    />
  )
}

this.mount will hold a reference to the actual <div> the component is mounted to.

On Unmounting (componentWillUnmount)

The following two snippets are equivalent:

const FunctionalComponent = () => {
 React.useEffect(() => {
   return () => {
     console.log("Bye");
   };
 }, []);
 return <h1>Bye, World</h1>;
};
class ClassComponent extends React.Component {
 componentWillUnmount() {
   console.log("Bye");
 }

 render() {
   return <h1>Bye, World</h1>;
 }
}

React Rendering Engine

Let's deep dive into how react is rendered.

Batching Update

When you need to call multiple setState() in sequence, there is no guarantee that no render will be made in between where state might not be consistent for your application. We can observe that setState inside Promise may not be batched by react, creating problems.

Therefore, at the cost of re-render virtual DOM, any interdependent states should be grouped as one single state.

When not to useMemo

See this article

Consider:

const MyComponent({page, type}) {
  const resolvedValue = getResolvedValue(page, type)
  return <ExpensiveComponent resolvedValue={resolvedValue}/>
}

When resolvedValue is a primitive and the value is the same as last render, ExpensiveComponent won't gets re-rendered. But when resolvedValue is not a primitive, without using useMemo, the object reference changed, and ExpensiveComponent will get re-rendered.

Consider:

const myComponent({page, type}) {
  const resolvedValue = useMemo(() => {
     return getResolvedValue(page, type)
  }, [page, type])

  return <ExpensiveComponent resolvedValue={resolvedValue}/>
}

So here we will update resolvedValue's reference only when page and type's reference changes. Reference changes for primitives means difference in value, for objects means difference in memory address.

Table of Content