Skip to content

Instantly share code, notes, and snippets.

@convict-git
Last active August 30, 2021 04:30
Show Gist options
  • Save convict-git/552692bd04675481cfaf478f62d181b9 to your computer and use it in GitHub Desktop.
Save convict-git/552692bd04675481cfaf478f62d181b9 to your computer and use it in GitHub Desktop.
Revisit React from the Docs (Notes for Noobs)

Revisit React from the {Docs, Epic react, et.} (Notes for Noobs)

Table of Contents

Main concepts

  • {} can have any javascript expressions. What is a js expressions? Anything that resolves to a value. So we can have even IIEF using arrow functions inside {}.

    Example:

<div>
  Hello{" "}
  {(() => {
    x += 1;
    return "world";
  })()}
</div>
  • Components are made up of elements.

  • 💡 React elements are immutable. Once you create an element, you can’t change its children or attributes. An element is like a single frame in a movie: it represents the UI at a certain point in time.

  • 💡 Thinking about how the UI should look at any given moment, rather than how to change it over time, eliminates a whole class of bugs.

  • 💡 Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen.

  • 💡 All React components must act like pure functions with respect to their props (and state?).

  • Class components should always call the base constructor with props. (super(props))

  • General lifecycle: constructor() ➡️ render() ➡️ componentDidMount() ➡️ componentDidUpdate() ➡️ componentWillUmount()

  • Enforcing rerender by modification to state will only be through setState(), hence manual changes to state won't have any effect.

  • ⭐ A React Component can re-render for any of the following reasons:

    • Its props change
    • Its internal state changes
    • It is consuming context values which have changed
    • Its parent re-renders
  • Beware of asynchronous nature of setState(), hence do NOT rely on values, instead use pure functions.

  • 💡 Data always flows down the tree; any state is always owned by some specific component and its effect should only be seen down the tree (sent as props).

  • key as prop is NOT accessible inside the component, use a different property like id or title for manual identification (any need of identification other than for render purpose, which is handled by react using key)

  • 💡 Controlled Component: Let react handle the "single source of truth" 😃

  • Share the state by lifting the state up to the lowest common ancestor. But note that now the state is sent as prop (which is read-only), and to let the child component change the state, you might have to send a handler as prop to the child.

  • 💡 Specialisation: If you want a component A to be some specialisation of component B, then A could just return B but with additional props (including new children).

  • 💡 Figure out the absolute minimal representation of the state your application needs and compute everything else you need on-demand. ➡️ Identify which components mutates, or owns, the states.

  • Class components: Side effects are NOT permitted in the render function but, are permitted in hooks, life-cycle methods and event handlers.

Notes

Code Splitting

  • Use React.lazy() for lazy loading modules and wrap the component with the Suspense component giving a fallback prop. Manage the recovery using Error Boundaries
    • NOTE: React.lazy() NOT yet for server side rendering
    • NOTE: React.lazy() currently only supports default exports.
  • Web bundler (or the native browser) keeps a cache of all the resolved values of the dynamic imports promises.

Context

  • Use React.createContext() for "global" props (Passed down the tree at all levels, and can be overridden for indivisual subtrees).

Error Boundaries

  • [OPINION] It's better to remove the component than showing a broken UI

  • Take care of event handlers yourself.

  • Re-mounting of error boundaries can be done using key prop if you are using a custom ErrorBounday class component instead of react-error-boundary. (Refer) (Get react-error-boundary from npm). Issue with using key is it will force remount of the whole wrapped component in the ErrorBoundary everytime the key changes.

  • You have to trigger resetErrorBoundary (more likely in FallbackComponent) for onReset() to work [Check bonus 7 in Epic React's React Hooks fetch API]

Forwarding Refs

  • Useful when you want to pass down the ref to the childs and refer them from some ancestor. One good example is when we use Higher Order Components (say, a Logger Wrapper component) and we want to refer the wrapped component.
function hocWrapper(Component) {
  class Wrapper extends React.Component {
    // .. do something with life cycle methods

    render() {
      const { forwRef, ...rest } = this.props;
      return <Component ref={forwRef} {...rest} />;
    }
  }

  return React.forwardRef((props, ref) => {
    return <Wrapper {...props} forwRef={ref} />;
  });
}

Hooks

  • ⭐ Hooks embrace JavaScript closures ⭐ which enable side-effects without React specific APIs, custom hooks et cetera. Closure is ❤️.

  • useState() preserves the state between re-renders. (Normally, variables “disappear” when the function exits but state variables are preserved by React).

  • useEffect() adds the ability to perform side-effects (look it as a combined API for componentDidMount(), componentDidUpdate() and componentWillUnmount()).

    • Effect is run after the DOM manipulation and in async with re-painting (see how useLayoutEffect() differs)
    • Return a callback fn for clean ups (like componentWillUnmount())
  • Hooks are JavaScript functions, but they impose two additional rules:

    • Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
    • Only call Hooks from React function components. Don’t call Hooks from regular JavaScript functions
  • ⭐ The state of each component is completely independent. Hooks are a way to reuse stateful logic, NOT state itself, all states and effects inside of it are fully isolated. In fact, since each call to a Hook has a completely isolated state, you can even use the same custom Hook twice in one component.
    This is in contrast when using custom hooks for contexts because then the state/value coming from the provider is shared between all the consumers of the provider. (So take care of race conditions here!)

  • 💡 Each effect “belongs” to a particular render (callback given to useEffect() is different each time since it's an arrow function).

  • Effect cleanup (return callback from useEffect()) happens after every re-render, and NOT just once during unmounting, and that's how it makes it a little different from componentWillUnmount().

  • Use the dependency list of props/states (second argument in useEffect()) to skip effects and run only when the desired prop(s)/state(s) is changed. Also, an empty list [] will trigger the effect only at mount and unmount, and NOT on re-renders.

  • Callback can be passed to setState() with first arg being prevState which needs to return the value for new state. TODO Add why we do so!

const [state, setState] = useState({});
setState((prevState) => {
  // Object.assign would also work
  return { ...prevState, ...updatedValues };
});
  • Similarly, lazy computations can be done for useState() inital value by passing a callback which returns the initial value of the state. Also, this lazy callback will be called just once in the lifetime of the component (so no calls during re-renders).
// Lazy initialization
const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});
  • Batching of useState() calls: If the state changes are triggered asynchronously (e.g. wrapped in a promise), they will not be batched; if they are triggered directly, they will be batched.

  • Usually you’ll want to declare functions needed by an effect, inside of the effect. Link

React Patterns

Context Module Functions

  • Making the custom hook contract such that the user of the hook, in some cases, doesn't need to care about how exactly the dispatch is called, rather just use our helper function and pass the dispatch (along with the current state and updates, if needed).
    This importable helper function is termed here as Context Module function. This gives user the flexibility to call dispatch by themselves for simpler operations and use the context module function for the complex ones (Looks bad for consistency:exclamation:).

  • A good use case is when we have multiple dispatchers, and they need to follow a certain order as the business logic. In that case, you probably don't want the user of the hook to remember the order as they will most likely miss something and it can harm the consistency of the state.

  • Why to export this function rather than keeping this function inside the hook or the Provider itself? Memoisation sucks, dependency list becomes unmanageable. Also, code splitting, tree shaking(:question:) and lazy loading isn't possible.

Compound Components

  • React.Children - iterate over the children prop as list,
    React.cloneElement - Make a copy of the element, props are merged shallowly.

  • Compound Components can provide props to there children components implicitly. States could be passed down as props to the children using this pattern without lifting it off and sending it explicitly.

  • We can make it even more flexible by wrapping the children prop with a Provider and hence allowing all the nested components to access the context which contains the shared state.

  • NOTE: A DOM component is a built-in component like <div />, <span />, or <blink />. A composite component is a custom component like <Toggle /> or <App />. We need to make sure that when we clone element, the element should actually accept a prop sent via cloneElement (built-in components will fail you here).

Prop Collection and Getters

  • Lots of flexible/resuable components and hooks have some common use-cases(or call it the prop settings).

  • Prop collection gives the user of our component/hook the flexibility to re-use some default/recommended props which is exported along with our component/hook.

  • Prop getters are functions which take Prop collection even further by giving user the flexibility of over-riding or composing props (allowing their own logic as well as the default logic).

State reducers

  • Allowing custom reducers for inversion of control. This helps the user of our component/hook to customise reduce for some or all actions (if needed). This eliminates the need of changing the original component/hook and still being able to support new feature requests at some level.

  • We might want to export our default reducer as well so that the user can create a custom reducer for some selected actions, and for the rest of actions, leave it to our default reducer. Also, it's a good practice to export an actionType object for our actions so as to eliminate the bugs due to typos in action.type.

Performance

  • Eager loading: Use dynamic imports (using React.lazy and React.Suspense) and trigger the fetching as soon as the you get the hint of it being required (say hover/focus over some element).

  • Webpack comments can be helpful to defer the loading.

    • import(/* webpackPrefetch: true; */ ../something) Tells the browser that the resource is probably needed for some navigation in the future, so fetch it as soon as you are done with the other things.
    • webpackChunkName magic comment which will allow webpack to place common modules in the same chunk. This is good for components which you want loaded together in the same chunk (to reduce multiple requests for multiple modules which will likely be needed together)
  • Sometimes re-render/reconcilation is costly and since a child component can be rerendered if its parent rerenders, this can cause unnecessary re-renders and slow down the application. React.memo can be used to prevent these (or React.PureComponent for class components). Sending primitive values as props rather than doing the computation inside, can also save the pain to write custom comparators.

  • Windowing: Lazy just-in-time rendering.

  • Perf death by thousand cuts: None of the components are slow in isolation, the problem comes when lots of components need to re-render when there’s a state update.

    • We solve the problem of our components getting a re-render triggered because of some state update in its ancestor (but wasn't required as the props didn't change) by using React.memo, but this usually brings a lot complexity as the props needs to be memoized either using React.useMemo or React.useCallback and involves maintainance of a depedency list. Also it adds the overhead of memo checking whether or not rerender is needed.

    • State colocation usually helps in solving this. The principle is "Place code as close to where it's relevant as possible". If multiple components share some state, find the lowest common ancestor in the component tree and manage the state there. Same applies to Contexts, place the providers as close to the required components as possible.

    • We can also use a hoc wrapper when we know our component uses only the sliced part of the context. Abstract out the consumer out of our component ➡️ Change the props to accept the sliced value directly ➡️ Memoize the component ➡️ Use the hoc wrapper.

      function withStateSlice(Comp, slice) {
        const MemoComp = React.memo(Comp); // Memoised Component
      
        function Wrapper(props, ref) {
          const state = useAppState(); // use context here
          const slicedState = slice(state, props);
          return <MemoComp ref={ref} state={slicedState} {...props} />;
        }
      
        // for better debugging experience, using displayName
        Wrapper.displayName = `withStateSlice(${Comp.displayName || Comp.name})`;
      
        // Return forwardRefed memoiszed version of sliced wrapper
        return React.memo(React.forwardRef(Wrapper));
      }
      
      /* definition of Cell component goes here .... */
      
      // wrap Cell with withStateSlice
      Cell = withStateSlice(
        Cell,
        (state, { row, column }) => state.grid[row][column] // slice function
      );
    • Refer Recoil js

Some useful Click Me

Lingo

  • State
  • Mount / Unmount
  • Hot reloading
  • “Inversion of Control” : "Here you go! You have complete control over how this thing works. It’s now your responsibility."

👀 Read later

Doubts

  • State lifting and pureness of components: Is passing a event handler to a child as prop to manage shared state breaks the idea of pure components?

TODO (to add)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment