The Journey of #100DaysOfCode (@sourabhbagrecha)

#Day77 of #100daysofcode

Thank you Captain @Stennie_X :saluting_face: .
As we saw in the State Reducer pattern that we can provide custom reducers to our hook and that will in return update our state accordingly, but what if we want to give this state control to a component that can mutate the state of this component(hook) from outside.
So, here we are with our Control Props React Pattern implementation, that’s exactly applicable in the above said situations.

function useToggle({
  initialOn = false,
  reducer = toggleReducer,
  on: controlledOn,
  onChange,
} = {}) {
  const {current: initialState} = React.useRef({on: initialOn})
  const [state, dispatch] = React.useReducer(reducer, initialState)

  const onIsControlled = controlledOn != null
  const on = onIsControlled ? controlledOn : state.on

  const dispatchWithOnChange = action => {
    if (!onIsControlled) {
      dispatch(action)
    }
    onChange(reducer({...state, on}, action), action)
  }

  const toggle = () => dispatchWithOnChange({type: actionTypes.toggle})
  const reset = () => dispatchWithOnChange({type: actionTypes.reset, initialState})

 ... some prop collection implementation here...

  return {
    on,
    reset,
    toggle,
    getTogglerProps,
    getResetterProps,
  }
}

function Toggle({on: controlledOn, onChange}) {
  const {on, getTogglerProps} = useToggle({on: controlledOn, onChange})
  const props = getTogglerProps({on})
  return <Switch {...props} />
}

function App() {
  const [bothOn, setBothOn] = React.useState(false)
  const [timesClicked, setTimesClicked] = React.useState(0)

  function handleToggleChange(state, action) {
    if (action.type === actionTypes.toggle && timesClicked > 4) {
      return
    }
    setBothOn(state.on)
    setTimesClicked(c => c + 1)
  }

  function handleResetClick() {
    setBothOn(false)
    setTimesClicked(0)
  }

  return (
    <div>
        <Toggle on={bothOn} onChange={handleToggleChange} />
        <Toggle on={bothOn} onChange={handleToggleChange} />
        .... some more JSX here ...
    </div>
  )
}

In the above implementation, we are making both the <Toggle/> components synchronized.

2 Likes

#Day78 of #100daysofcode

Today I took my component that implemented the Control Prop React Pattern to the next level.
If you have worked with React’s Input elements, then you might be aware of the following warning:

Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.

We should throw a similar warning whenever a developer is misusing our controlled props.
As per Kent C Dodds, there can be 3 scenarios where it will be considered a misuse:

  1. When on/value prop has some value, but onChange prop has no function assigned to it.

  2. Passing a value for on but later passing undefined or null

  3. Passing undefined or null but later passing a value

function useToggle({
  initialOn = false,
  reducer = toggleReducer,
  on: controlledOn,
  onChange,
} = {}) {
  const {current: initialState} = React.useRef({on: initialOn})
  const [state, dispatch] = React.useReducer(reducer, initialState)

  const onIsControlled = controlledOn != null
  const on = onIsControlled ? controlledOn : state.on

  const hasOnChange = !(!onChange);
  useEffect(() => {
  // here we are checking if on is controlled and the component has no onChange function passed to it, if so we are throwing a warning.
    if(onIsControlled && !hasOnChange){
      console.error(`An \`on\` prop was provided to useToggle without an \`onChange\` handler. This will render a read-only toggle. If you want it to be mutable, use \`initialOn\`. Otherwise, set either \`onChange\` or \`readOnly\`.`)
    }
  }, [onIsControlled, hasOnChange]) 

  const dispatchWithOnChange = action => {
    if (!onIsControlled) {
      dispatch(action)
    }
    onChange?.(reducer({...state, on}, action), action)
  }

  const toggle = () => dispatchWithOnChange({type: actionTypes.toggle})
  const reset = () => dispatchWithOnChange({type: actionTypes.reset, initialState})

 ... some prop collection implementation here...

  return {
    on,
    reset,
    toggle,
    getTogglerProps,
    getResetterProps,
  }
}
5 Likes

#Day79 of #100daysofcode

Today I implemented some capabilities to our Control Prop component to throw an error when a component goes from uncontrolled to a controlled (or vice versa) state.
Typically, React would throw the following error in the above-mentioned state:

Warning: A component is changing an uncontrolled input of type undefined to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.

Here’s what our custom hook would look like when the above situation needs to be handled.

function useToggle({
  initialOn = false,
  reducer = toggleReducer,
  onChange,
  on: controlledOn,
  readOnly = false,
} = {}) {
  const {current: initialState} = React.useRef({on: initialOn})
  const [state, dispatch] = React.useReducer(reducer, initialState)

  const onIsControlled = controlledOn != null
  const on = onIsControlled ? controlledOn : state.on

  const {current: onWasControlled} = React.useRef(onIsControlled)
  React.useEffect(() => {
    if(onIsControlled && !onWasControlled){
      console.error(`\`useToggle\` is changing from uncontrolled to be controlled. Components should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled \`useToggle\` for the lifetime of the component. Check the \`on\` prop.`)
    }

    if(!onIsControlled && onWasControlled){
      console.error(`\`useToggle\` is changing from controlled to be uncontrolled. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled \`useToggle\` for the lifetime of the component. Check the \`on\` prop.`)
    }
  }, [onIsControlled, onWasControlled])

  const hasOnChange = Boolean(onChange)
  React.useEffect(() => {
    if(!hasOnChange && onIsControlled && !readOnly){
      console.error(`An \`on\` prop was provided to useToggle without an \`onChange\` handler. This will render a read-only toggle. If you want it to be mutable, use \`initialOn\`. Otherwise, set either \`onChange\` or \`readOnly\`.`);
    }
  }, [hasOnChange, onIsControlled, readOnly])

  function dispatchWithOnChange(action) {
    if (!onIsControlled) {
      dispatch(action)
    }
    onChange?.(reducer({...state, on}, action), action)
  }

  const toggle = () => dispatchWithOnChange({type: actionTypes.toggle})
  const reset = () =>
    dispatchWithOnChange({type: actionTypes.reset, initialState})

... some prop collection implementation here...

  return {
    on,
    reset,
    toggle,
    getTogglerProps,
    getResetterProps,
  }
}
2 Likes

#Day80 of #100daysofcode

Today I extracted those error handling functionalities into a custom hook that can be used across different other hooks and components without re-writing the core logic behind it.
So here are our custom hooks:

function useControlledSwitchWarning(
  controlPropValue,
  controlPropName,
  componentName,
) {
  const isControlled = controlPropValue != null
  const {current: wasControlled} = React.useRef(isControlled)

  React.useEffect(() => {
    if(isControlled && !wasControlled){
      console.error(`\`useToggle\` is changing from uncontrolled to be controlled. Components should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled \`useToggle\` for the lifetime of the component. Check the \`on\` prop.`)
    }

    if(!isControlled && wasControlled){
      console.error(`\`useToggle\` is changing from controlled to be uncontrolled. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled \`useToggle\` for the lifetime of the component. Check the \`on\` prop.`)
    }
  }, [componentName, controlPropName, isControlled, wasControlled])
}

function useOnChangeReadOnlyWarning(
  controlPropValue,
  controlPropName,
  componentName,
  hasOnChange,
  readOnly,
  readOnlyProp,
  initialValueProp,
  onChangeProp,
) {
  const isControlled = controlPropValue != null
  React.useEffect(() => {
    if(!hasOnChange && isControlled && !readOnly){
      console.error(`An \`on\` prop was provided to useToggle without an \`onChange\` handler. This will render a read-only toggle. If you want it to be mutable, use \`initialOn\`. Otherwise, set either \`onChange\` or \`readOnly\`.`);
    }
  }, [
    componentName,
    controlPropName,
    isControlled,
    hasOnChange,
    readOnly,
    onChangeProp,
    initialValueProp,
    readOnlyProp,
  ])
}

Now, we can utilize these hooks just by passing arguments to them. One thing to note here is that we don’t need these warnings when our app is in production mode, these warnings are just there to alert the developer using them in order to avoid any anti-pattern and misuse of this hook.
Since we extracted our core logic into custom hooks, this gives us the opportunity to implement Dead Code Elimination.
Since there is no significant value in displaying an error for misuse of our hook to the end-user, we can simply avoid these checks, and by avoiding these checks(using dead code elimination) we can leverage the following benefits:

  • It shrinks program size
  • It eliminates unnecessary computation
  • End User will not be able to see these warnings, which is often the intended behavior. (because these errors are only meant for developers utilizing these hooks in their components)

Take a look at the following implementation to see the above in action:

if (process.env.NODE_ENV !== 'production') {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    useControlledSwitchWarning(controlledOn, 'on', 'useToggle')
    // eslint-disable-next-line react-hooks/rules-of-hooks
    useOnChangeReadOnlyWarning(
      controlledOn,
      'on',
      'useToggle',
      Boolean(onChange),
      readOnly,
      'readOnly',
      'initialOn',
      'onChange',
    )
  }
3 Likes

#Day81 of #100daysofcode

Today I learned about performance optimization in React.
Sometimes during the initial render of the component(parent), we may not want to load assets(or components) that are heavier in size and might take significant time to load which can affect the User Experience of our app. And maybe we just want to load it conditionally or wait for the user to perform some action.
In such cases, we can utilize the lazy loading API of React. It provides us with a <Suspense> wrapper that allows us to provide a fallback UI that appears while the component is getting loaded.
Let’s take a look at an example to understand this better, suppose we want to display a globe that is very heavy in size and we just want to show it when the user checks the checkbox for show globe :white_check_mark: . We can utilize Suspense in the following manner, such that the very heavy Globe component doesn’t get loaded in the initial render hence the user doesn’t have to wait even in very slow internet connections:

import * as React from 'react'
const loadGlobe = () => import('../globe')
const Globe = React.lazy(loadGlobe)

function App() {
  const [showGlobe, setShowGlobe] = React.useState(false)

  return (
    <div
      style={{
        display: 'flex',
        alignItems: 'center',
        flexDirection: 'column',
        justifyContent: 'center',
        height: '100%',
        padding: '2rem',
      }}
    >
      <label style={{marginBottom: '1rem'}}>
        <input
          type="checkbox"
          checked={showGlobe}
          onChange={e => setShowGlobe(e.target.checked)}
          onMouseOver={loadGlobe}
          onFocus={loadGlobe}
        />
        {' show globe'}
      </label>
      <div style={{width: 400, height: 400}}>
        <React.Suspense fallback={<div>loading...</div>}>
          {showGlobe ? <Globe /> : null}
        </React.Suspense>
      </div>
    </div>
  )
}

export default App
2 Likes

#Day82 of #100daysofcode

Today I learned about the useMemo hook, which is basically providing us a way to preserve the results of any expensive calculations/computations as long as the dependencies provided to it don’t change.
It can dramatically improve the performance of our app.
Let’s take an example where we are performing some complex computations by calculating the distance between two points x and y.

function Distance({x, y}) {
  const distance = calculateDistance(x, y)
  return (
    <div>
      The distance between {x} and {y} is {distance}.
    </div>
  )
}

Now, whenever this component gets re-rendered, the compute-intensive distance calculation will take place. This will eventually slow down our app, and when there are multiple components in our app utilizing some compute-intensive functions that are getting executed for the same values just because the component has to re-render, it can significantly hamper our user experience.

Therefore, we need a way to optimize our app in such a way that it memoizes the results and can avoid re-calculating them on every render.

useMemo to the rescue!

function Distance({x, y}) {
  const distance = React.useMemo(() => calculateDistance(x, y), [x, y])
  return (
    <div>
      The distance between {x} and {y} is {distance}.
    </div>
  )
}

Now the above component would smartly memoize the results and utilize them on every render, until and unless there is a change in any one of those dependencies(x or y).

2 Likes

#Day83 of #100daysofcode

Today I learned about the Performance tab in the browser developer tools. I learned some really new and exciting features which we can utilize to simulate real-world scenarios, like CPU throttling and internet speed.
Not only that, but we can also record some events and analyze the performance of our app function-by-function and a really nice and insightful graph will be shown to us that visualizes the performance of our app.

Chrome Developer Tools never ceases to impress me.

As you can see from the visualization that matchSorter is the most expensive function in my app is matchSorter because it has the biggest width among all the functions.
Once I click on that, I get a summary of the aggregated time for the concerned function, and gives us a really nice breakdown of how the total time is getting distributed.

3 Likes

#Day84 of 100daysofcode

Since I have been learning about React App’s performance for a few days, I can not conclude this without mentioning Web Workers.

Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface.

It’s obvious how this Web Worker API can help us in increasing our App’s performance.

Let’s see this in action:
In this example, we are going to use the Workerize package to ensure that we are offloading these heavy calculations that are not directly responsible for Browser’s UI paint process.

Previously we were importing the heavy function directly into our component like this:

import {getItems} from '../filter-cities'

But the above is not utilizing the Web Worker to execute.

We can create a new layer over the ../filter-cities file which will be Workerizing this function:

//FileName: workerized-filter-cities.js
import makeFilterCitiesWorker from 'workerize!./filter-cities'

const {getItems} = makeFilterCitiesWorker()

export {getItems}

And then we will use the above file to consume a getItems function that is utilizing the Web Worker in our component in the following manner:

import {getItems} from '../workerized-filter-cities'
4 Likes

#Day85 of #100daysofcode

Today I learned about the React.memo higher order component.
In React, a component can re-render for any of the following reasons:

  1. Its props change
  2. Its internal state changes
  3. It is consuming context values which have changed
  4. Its parent re-renders

But what if we don’t want a particular component to re-render unless and until there’s a good reason to re-render it(like the update needs to reflect on the DOM)?

For the above-mentioned reason, we can simply use React.memo to ensure that the component only gets re-rendered until and unless its own props get changed.
Here’s an example implementation on how we can do the same on a ListItem which is a child component of a really long List Component:

function ListItem({
  getItemProps,
  item,
  index,
  selectedItem,
  highlightedIndex,
  ...props
}) {
  const isSelected = selectedItem?.id === item.id
  const isHighlighted = highlightedIndex === index
  return (
    <li
      {...getItemProps({
        index,
        item,
        style: {
          fontWeight: isSelected ? 'bold' : 'normal',
          backgroundColor: isHighlighted ? 'lightgray' : 'inherit',
        },
        ...props,
      })}
    />
  )
}

The above component will re-render whenever its parent gets re-rendered, and that can cause serious performance issues when the list is significantly long.
Now, we can simply wrap this ListItem component using the following syntax:

ListItem = React.memo(ListItem)

And this will avoid all the unnecessary re-render we just discussed.

3 Likes

#Day86 of 100daysofcode

Today I learned comparator custom comparator functions in React.memo HOC(higher-order components).
Because of our React.memo optimization yesterday, our list items won’t get re-render if their parent re-renders.

We can see this in React Devtools’ Profiler as below:

Suppose we want to have a feature in our app, where any time the user hovers over any one of those list items, they will get highlighted as shown below:

Now every time a user hovers over any list item the complete list along with every list item in it will get re-rendered because every ListItem component has a prop called highlightedIndex as we have seen yesterday.
Let’s take a look at our Profiler again when we hover over any item in the list:

But we don’t want to re-render all the items in the list because all of them have no visible UI updates to reflect in the DOM. We just want to update the list item that has been highlighted or which was highlighted before but it’s no longer highlighted.

For cases like these where the component’s props have changed but we still want to re-render only when a specific condition takes place, we can use Comparator Functions.
Let’s see it in action:

ListItem = React.memo(ListItem, (prevProps, nextProps) => {
  if (prevProps.getItemProps !== nextProps.getItemProps) return false
  if (prevProps.item !== nextProps.item) return false
  if (prevProps.index !== nextProps.index) return false
  if (prevProps.selectedItem !== nextProps.selectedItem) return false

  if (prevProps.highlightedIndex !== nextProps.highlightedIndex) {
    const wasPrevHighlighted = prevProps.highlightedIndex === prevProps.index
    const isHighlightedNow = nextProps.highlightedIndex === nextProps.index
    return wasPrevHighlighted === isHighlightedNow
  }
})

In the above implementation, we are passing a comparator function as the second argument to React.memo that will accept two parameters ( prepProp & nextProp ).
Both these parameters will include prop state(before and after) whenever a re-render get triggered for the concerned component.
Here we are checking if any of the props other than highlightedIndex change, we simply need to re-render this component, so we are simply returning false to tell React that this component need to re-render.
But what about the highlightedIndex prop, well we are still checking whether it is changed or not but along with that we will also check whether the component that is in consideration has anything to do with the change in the highlightedIndex. If the listItem doesn’t need to be highlighted or un-highlighted based upon its own index we won’t re-render it, otherwise we will re-render it.

Now, I will hover over a few list items one-by-one and you can see in the profiler that only the affected components(as per the highlightedIndex prop) in the list are being re-rendered, otherwise, all the other listItem will remain as-is, which is a great performance boost:

3 Likes

#Day87 of #100daysofcode

Today I learned how we can optimize our component re-render process even more efficiently using primitive values through props.
To recap, our ListItem component used to look like this:

function ListItem({
  getItemProps,
  item,
  index,
  selectedItem,
  highlightedIndex,
  ...props
}) {
  const isSelected = selectedItem?.id === item.id
  const isHighlighted = highlightedIndex === index
  return (
    <li
      {...getItemProps({
        index,
        item,
        style: {
          fontWeight: isSelected ? 'bold' : 'normal',
          backgroundColor: isHighlighted ? 'lightgray' : 'inherit',
        },
        ...props,
      })}
    />
  )
}

Here, what we are doing is simply taking the selectedItem & highlightedIndex props and calculating the isHighlighted & isSelected values inside the component, which can become troublesome, especially when we want to optimize the re-renders by comparing that particular prop’s state. Hence, we had to do a workaround as we did above to ensure if we really need to re-render or not.

But this kind of workaround is difficult to maintain in larger codebases, where there are 100s of components with many childrens nested into each other.

Therefore we need a better approach to solve this. The simplest way to achieve this, is to pass Primitive values through props instead of fields that later needs to calculated inside the component.
For e.g. instead of passing the selectedItem & highlightedIndex, we will simply pass the isHighlighted & isSelected values from the parent or may be parent’s parent component.

Let’s see this in action:

function ListItem({
  getItemProps,
  item,
  index,
  isSelected,
  isHighlighted,
  ...props
}) {
  // The below two lines are no longer useful, since instead of calculating them in the component, 
  // we will take this value from the props directly.
  // const isSelected = selectedItem?.id === item.id
  // const isHighlighted = highlightedIndex === index
  return (
    <li
      {...getItemProps({
        index,
        item,
        style: {
          fontWeight: isSelected ? 'bold' : 'normal',
          backgroundColor: isHighlighted ? 'lightgray' : 'inherit',
        },
        ...props,
      })}
    />
  )
}

ListItem = React.memo(ListItem)

Now, we just need to update the parent component like this, where instead of passing the highlightedIndex we are passing whether the component is highlighted or not, and the same for selectedIndex as well, we are passing whether the concerned component is currently selected or not:

function Menu({
  items,
  getMenuProps,
  getItemProps,
  highlightedIndex,
  selectedItem,
}) {
  return (
    <ul {...getMenuProps()}>
      {items.map((item, index) => (
        <ListItem
          key={item.id}
          getItemProps={getItemProps}
          item={item}
          index={index}
          isSelected={selectedItem?.id === item.id}
          isHighlighted={ highlightedIndex === index}
        >
          {item.name}
        </ListItem>
      ))}
    </ul>
  )
}

Menu = React.memo(Menu)
3 Likes

#Day88 of #100daysofcode

Today I learned about the React-Virtual hook that let us virtualize scrollable elements in React.
Suppose we have a really large list of elements where only a small amount elements would be visible at any point of time.
In cases like this, we do not want to reflect those items on the DOM which can not be shown to the user. We will just load them asynchronously when neededas per the user’s scroll event.

React Virtual provides us with an API that help us in doing the same, checkout the code below to learn how we can utilize it in our gigantic lists where a very small sub section of the list is needed.

  const rowVirtualiser = useVirtual({
    size: items.length,
    parentRef: listRef,
    estimateSize: React.useCallback(() => 20, []),
    overscan: 10,
  })
2 Likes

#Day89 of #100daysofcode

Today I learned about how to optimize context value. The way how context work is whenever there is a change in the provided value from one render to another, it will trigger a re-render to the alltge components consuming that context.
Stay tuned to see this in action.

2 Likes

#Day90 of #100daysofcode

Today I learned how we can optimise our Context values in our provider consumer implementation in our React app.
Take a look at the below Context provider to see how easily we can wrap our values that the consumers of this provider will consume to eliminate any unnecessary re-renders of consumers abd their children.


function AppProvider({children}) {
  const [state, dispatch] = React.useReducer(appReducer, {
    dogName: '',
    grid: initialGrid,
  })
  const value = React.useMemo(() => [state, dispatch], [state])
  return (
    <AppStateContext.Provider value={value}>
      {children}
    </AppStateContext.Provider>
  )
}
3 Likes

#Day91 of #100daysofcode

The above optimization is great because React thinks that the value array([state, dispatch]) passed to the provider is a new array every time the parent re-renders, but that shouldn’t be the case given the actual values inside the value array doesn’t not change, it’s just that its reference has changed. Therefore we are using React.useMemo() to ensure that it only passes a brand new array to the provider when the inner values get changed actually, otherwise it should use a memoized version of the value array and the children shouldn’t re-render unnecessarily.

Now, let’s take a closer look at all the consumers of this provider.

function Grid() {
  const [, dispatch] = useAppState()
  ...
}
function Cell({row, column}) {
  const [state, dispatch] = useAppState()
  ...
}
function DogNameInput() {
  const [state, dispatch] = useAppState()
  ...
}

The Grid component is a consumer of our AppProvider and it doesn’t need the state value, but it will still get re-rendered whenever there is a change in the state value, and we definitely want to avoid that.

This situation can be handled using with the help of a famous Computer Science design principle called Separation of Concerns.
We will simply split our context into 2 contexts but will place it in a single provider, like this:

const AppStateContext = React.createContext()
const AppDispatchContext = React.createContext()

function AppProvider({children}) {
  const [state, dispatch] = React.useReducer(appReducer, {
    dogName: '',
    grid: initialGrid,
  })
  return (
    <AppStateContext.Provider value={state}>
      <AppDispatchContext.Provider value={dispatch}>
        {children}
      </AppDispatchContext.Provider>
    </AppStateContext.Provider>
  )
}

And we will wrap the AppProvider around our complete app so that we can utilize it from the children components, like this:

function App() {
  const forceRerender = useForceRerender()
  return (
    <div className="grid-app">
      <button onClick={forceRerender}>force rerender</button>
      <AppProvider>
        <div>
          <DogNameInput />
          <Grid />
        </div>
      </AppProvider>
    </div>
  )
}

To ensure that our provider doesn’t get consumed from outside the boundary and handle this error gracefully, we are going to create the following 2 utilities:

function useAppState() {
  const context = React.useContext(AppStateContext)
  if (!context) {
    throw new Error('useAppState must be used within the AppProvider')
  }
  return context
}

function useDispatchState() {
  const context = React.useContext(AppDispatchContext)
  if (!context) {
    throw new Error('useDispatchState must be used within the AppProvider')
  }
  return context
}
2 Likes

#Day92 of #100daysofcode

Continuing my learnings on performance optimizations for React Apps, today I learned about how to optimize expensive re-renders for components that are consuming a Context.
Let’s take a look at this Cell component, the JSX written for this component is a little bit expensive in terms of compute resources:

function Cell({row, column}) {
  const state = useAppState()
  const cell = state.grid[row][column]
  const dispatch = useAppDispatch()
  const handleClick = () => dispatch({type: 'UPDATE_GRID_CELL', row, column})
  return (
    <button
      className="cell"
      onClick={handleClick}
      style={{
        color: cell > 50 ? 'white' : 'black',
        backgroundColor: `rgba(0, 0, 0, ${cell / 100})`,
      }}
    >
      {Math.floor(cell)}
    </button>
  )
}
Cell = React.memo(Cell)

Even though we have memoized this component, every time the global grid’s state changes, this component will get re-rendered because the state object’s reference changes.

Now we can’t do much about this, but as a small optimization we can create a man in the middle component that will do the heavy-lifting of Context consumption and this component will handle the implementation:

function CellImpl({cell, row, column}) {
  const dispatch = useAppDispatch()
  const handleClick = () => dispatch({type: 'UPDATE_GRID_CELL', row, column})
  return (
    <button
      className="cell"
      onClick={handleClick}
      style={{
        color: cell > 50 ? 'white' : 'black',
        backgroundColor: `rgba(0, 0, 0, ${cell / 100})`,
      }}
    >
      {Math.floor(cell)}
    </button>
  )
}
CellImpl = React.memo(CellImpl)

The above CellImpl component will take care of all the expensive visible DOM updates, and it accepts a prop called cell which basically contains the updates to the concerned CellImpl and it will only re-render if this cell prop gets changed.

Below, we have created a man-in-the-middle component that we just discussed will calculate the cell value and update it if any updates were received from the App State Context(useAppState) and forward the same via a cell prop to the CellImpl component.

function Cell({row, column}) {
  const state = useAppState()
  const cell = state.grid[row][column]
  return <CellImpl cell={cell} row={row} column={column} />
}
Cell = React.memo(Cell)

Previously the Profiler logs were showing an update to every cell component even if there weren’t any DOM updates that require a re-render.


But now, the DOM update heavy lifting is offloaded to CellImpl component and it will only re-render if the concerned cell value actually gets changed.

4 Likes

#Day93 of #100daysofcode

Today I learned how to implement a higher order component that consumes a slice of the app’s state. Let’s consider an example from yesterday’s implementation of Cell & CellImpl.
Instead of repeating the code every now and then to achieve this, we can create a custom HOC(Higher Order Component) that will wrap the implementation and outputs an even more advanced memoized version of it.

function withStateSlice(Comp, slice) {
  const MemoComp = React.useMemo(Comp)
  function Wrapper(props) {
    const state = useAppState()
    return <MemoComp state={slice(state, props)} {...props}/>
  }
}

And now all we need to do is wrap it over a component that needs to memoized like CellImpl we have seen yesterday.

function Cell({state: cell, row, column}) {
  const dispatch = useAppDispatch()
  const handleClick = () => dispatch({type: 'UPDATE_GRID_CELL', row, column})
  return (
    <button
      className="cell"
      onClick={handleClick}
      style={{
        color: cell > 50 ? 'white' : 'black',
        backgroundColor: `rgba(0, 0, 0, ${cell / 100})`,
      }}
    >
      {Math.floor(cell)}
    </button>
  )
}

Cell = withStateSlice(Cell, (state, {row, column}) => state.grid[row][column])
3 Likes

#Day94 of #100daysofcode

Today I learned about Recoil- a state management library for React.
As we have seen above, it takes a lot of effort and consideration in order to increase the performance of our React App by avoiding unnecessary re-renders.

Recoil on the other hand offloads that burden for us. Let’s take a look how it can help us in maximizing the performance while keeping the state secure and easily manageable:

import {RecoilRoot, useRecoilState, useRecoilCallback, atomFamily} from 'recoil'

const initialGrid = Array.from({length: 100}, () =>
  Array.from({length: 100}, () => Math.random() * 100),
)

const cellAtoms = atomFamily({
  key: 'cells',
  default: ({row, column}) => initialGrid[row][column],
})

function useUpdateGrid() {
  return useRecoilCallback(({set}) => ({rows, columns}) => {
    for (let row = 0; row < rows; row++) {
      for (let column = 0; column < columns; column++) {
        if (Math.random() > 0.7) {
          set(cellAtoms({row, column}), Math.random() * 100)
        }
      }
    }
  })
}

Now, all we need to do is plug the above into our component like this:

function Cell({row, column}) {
  const [cell, setCell] = useRecoilState(cellAtoms({row, column}))
  const handleClick = () => setCell(Math.random() * 100)
  ...
}
2 Likes

#Day95 of #100daysofcode

Today I learned about Testing in JavaScript. Also, I learned how to implement my own testing framework by following this JavaScript testing guide by Kent C Dodds:
Here’s my implementation of my very basic not-at-all-production-ready testing framework:

const {sum, subtract} = require('./math')

test('sum adds numbers', () => {
  const result = sum(3, 7)
  const expected = 10
  expect(result).toBe(expected)
})

test('subtract subtracts numbers', () => {
  const result = subtract(7, 3)
  const expected = 4
  expect(result).toBe(expected)
})

function test(title, callback) {
  try {
    callback()
    console.log(`✓ ${title}`)
  } catch (error) {
    console.error(`✕ ${title}`)
    console.error(error)
  }
}

function expect(actual) {
  return {
    toBe(expected) {
      if (actual !== expected) {
        throw new Error(`${actual} is not equal to ${expected}`)
      }
    },
  }
}

Testing tools like Jest provide expect & test functions out of the box along with some really productive CLI tooling.

2 Likes

#Day96 of #100daysofcode

Today I learned about Testing React Apps using by rendering a component and verifying our expected output given a test input and conditions.
Suppose we want to test the following <Counter/> component:

function Counter() {
  const [count, setCount] = React.useState(0)
  const increment = () => setCount(c => c + 1)
  const decrement = () => setCount(c => c - 1)
  return (
    <div>
      <div>Current count: {count}</div>
      <button onClick={decrement}>Decrement</button>
      <button onClick={increment}>Increment</button>
    </div>
  )
}

Now, we want to test the following 3 conditions:

  • When the component renders, the Current count should be set to 0
  • When a user clicks on the decrement button, the Current count should be set to -1
  • When a user clicks on the increment button twice after the previous step, the Current count should be set 1

Let’s see how we can leverage react-dom/test-utils & react-dom/client to get some utility methods to test our components:

import * as React from 'react'
import {act} from 'react-dom/test-utils'
import {createRoot} from 'react-dom/client'
import Counter from '../../components/counter'

global.IS_REACT_ACT_ENVIRONMENT = true

test('counter increments and decrements when the buttons are clicked', () => {
  const div = document.createElement('div')
  document.body.append(div)

  // Rendering the Counter component using the act method
  act(() => createRoot(div).render(<Counter />))

  const [decrement, increment] = div.querySelectorAll('button')
  const message = div.firstChild.querySelector('div')

  // Testing whether the Current count is set to 0 intially
  expect(message.textContent).toBe('Current count: 0')

  // Emulating a click on the decrement button
  act(() => decrement.click())
  
  // Testing whether the count got decreased
  expect(message.textContent).toBe('Current count: -1')

  // Emulating a click on the increment button twice
  act(() => increment.click())
  act(() => increment.click())

  // Testing whether the count got increased twice
  expect(message.textContent).toBe('Current count: 1')
})
3 Likes