React: How does useDeferredValue and useTransition work ?
Tags: react, javascript, react18, concurrent
Published: 2022-10-20 09:30:37

As we know that Concurrent was enabled by default in React 18.

To implement this feature, there are new 2 hooks that come into the game: `useDeferredValue` and `useTransition`

You might want to know how it work. Let’s find out

## I. **useDeferredValue**
### **1. Mount phase:** 
In mount phase (first render), **useDeferredValue** will be treated as below :
```typescript
function mountDeferredValue<T>(value: T): T {
  const hook = mountWorkInProgressHook();
  hook.memoizedState = value;
  return value;
}
```
[**View Source**](https://github.com/facebook/react/blob/v18.2.0/packages/react-reconciler/src/ReactFiberHooks.new.js#L1931-L1935)

This mean it will save the current value into **memoizedState** and directly return it.

So on first render, deferredValue will be the same as input value.

### **2. Update phase:** 
When component have an update, **useDeferredValue** will be treated as below:

```typescript
function updateDeferredValue<T>(value: T): T {
  const hook = updateWorkInProgressHook();
  const resolvedCurrentHook: Hook = (currentHook: any);
  const prevValue: T = resolvedCurrentHook.memoizedState;
  return updateDeferredValueImpl(hook, prevValue, value);
}
```
```typescript
function updateDeferredValueImpl<T>(hook: Hook, prevValue: T, value: T): T {
  const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
  if (shouldDeferValue) {
    if (!is(value, prevValue)) {
      const deferredLane = claimNextTransitionLane();
      currentlyRenderingFiber.lanes = mergeLanes(
        currentlyRenderingFiber.lanes,
        deferredLane,
      );
      markSkippedUpdateLanes(deferredLane);
      hook.baseState = true;
    }
    return prevValue;
  } else {
    if (hook.baseState) {
      hook.baseState = false;
      markWorkInProgressReceivedUpdate();
    }
    hook.memoizedState = value;
    return value;
  }
}
```
[**View Source**](https://github.com/facebook/react/blob/v18.2.0/packages/react-reconciler/src/ReactFiberHooks.new.js#L1957-L2000)

1. in **updateDeferredValue**, it will get the previos value that was already stored in **memoizedState** and put the previos value and current value as parameter to next function **updateDeferredValueImpl** 
<br>

2. in **updateDeferredValueImpl**, there will be a check if the current lanes contain a UrgentLanes 
```javascript
const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
```
 Dig into [includesOnlyNonUrgentLanes](https://github.com/facebook/react/blob/v18.2.0/packages/react-reconciler/src/ReactFiberLane.new.js#L459-L462) we will know UrgentLane are : 

 ```SyncLane | InputContinuousLane | DefaultLane;```

 So we can see that if current process contain a **UrgentLane** it will skip update DeferredValue and return the previos value immediately after schedule a render with **Transition Lane** (not prioritized). <br>
Otherwise if current process contain only **nonUrgentLanes**, it will memoize and update the new value on **useDeferredValue**. 

The flow is simply as below diagram: 


![image](https://user-images.githubusercontent.com/9906907/196644089-54ca4023-b660-45e3-90cd-6f6803543df0.png)


<br>

## II. **useTransition**
### **1. Mount phase**

```typescript
function mountTransition(): [
  boolean,
  (callback: () => void, options?: StartTransitionOptions) => void,
] {
  const [isPending, setPending] = mountState(false);
  // The `start` method never changes.
  const start = startTransition.bind(null, setPending);
  const hook = mountWorkInProgressHook();
  hook.memoizedState = start;
  return [isPending, start];
}
```
[**View Source**](https://github.com/facebook/react/blob/v18.2.0/packages/react-reconciler/src/ReactFiberHooks.new.js#L2049-L2059)

Interesting, in mount phase, **useTransition** will use **useState** (mountState) to store its state "`isPending`".

Next, it will generate start function by bind `setPending` (dispatch setState) into `startTransition`, then put this function into hook. And return to the component. 

Let's see what's **`startTransition`** do (*I've removed all comments and warning code to make function shorter*) : 

```typescript
function startTransition(setPending, callback) {
  const previousPriority = getCurrentUpdatePriority();
  setCurrentUpdatePriority(
    higherEventPriority(previousPriority, ContinuousEventPriority),
  );
  setPending(true);
  const prevTransition = ReactCurrentBatchConfig.transition;
  ReactCurrentBatchConfig.transition = 1;
  try {
    setPending(false);
    callback();
  } finally {
    setCurrentUpdatePriority(previousPriority);
    ReactCurrentBatchConfig.transition = prevTransition;
  }
}
```
So at first, it will ensure the current priority is higher than `ContinuousEventPriority`, then set `isPending` to `true` to mark as transition start. 
After that, it will save current value of `ReactCurrentBatchConfig.transition` into `prevTransition` and then set it to 1.

Guess what, setting `ReactCurrentBatchConfig.transition` to 1 will force put next updates into `Transition Lane`.
```typescript
const isTransition = requestCurrentTransition() !== NoTransition;
  if (isTransition) {
    if (currentEventTransitionLane === NoLane) {
      currentEventTransitionLane = claimNextTransitionLane();
    }
    return currentEventTransitionLane;
  }
```

You can check it in [**requestUpdateLane**](https://github.com/facebook/react/blob/v18.2.0/packages/react-reconciler/src/ReactFiberWorkLoop.new.js#L475-L496)
After process callback, it will reset `ReactCurrentBatchConfig` into original by `prevTransition` variable.

### **2. Update phase**
Update phase for **useTransition** is kind of simpler, since trigger function `start` was already memoized in hook.
```typescript
function updateTransition(): [boolean, (() => void) => void] {
  const [isPending] = updateState(false);
  const hook = updateWorkInProgressHook();
  const start = hook.memoizedState;
  return [isPending, start];
}
```

# **Conclusion:**
By looking at their code, we can see that **useTransition** and **useDeferredValue** will basically put the process into **Transition Lane**. 
Also, **Concurrent** does not mean React doing multi-task in the same time. It just mean `React` will process tasks with priority order. To do that, React has its Scheduler that may be discussed in another post.