One handy lifecycle for using class components is getDerivedStateFromProps. The main thing I use this lifecycle is to implement some controlled components.

However, there is no concept of life cycle of function components, so there is no such method, but careful students can see that there is a solution to this problem in the official documentation.

Before looking at the official solution, take a look at when the getDerivedStateFromProps life cycle for the class component is executed.

The execution time of getDerivedStateFromProps

GetDerivedStateFromProps is triggered on both Mount and Update in the source code, and the timing is synchronous. In the source code, it is a simple value change, so no new updates are issued.

const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
  applyDerivedStateFromProps(
    workInProgress,
    ctor,
    getDerivedStateFromProps,
    newProps,
  );
  // Assign the value to state directly here
  instance.state = workInProgress.memoizedState;
}
Copy the code

Answers to the official FAQ

I think some of you might do something like this if you haven’t read the official answer.

const [state,setState] = useState()
useEffect(() = >{
  if('value' in props){
    setState(props.value)
  }
},[props])
Copy the code

Because I wrote this when I first used Hook, it is not the problem that the component frequently launches the update schedule, but the useEffect is asynchronous, there may be some minor problems.

Throw the wrong method and see the official solution.

function ScrollView({row}) {
  const [isScrollingDown, setIsScrollingDown] = useState(false);
  const [prevRow, setPrevRow] = useState(null);

  if(row ! == prevRow) {// The Row has changed since the last rendering. Update the isScrollingDown.setIsScrollingDown(prevRow ! = =null && row > prevRow);
    setPrevRow(row);
  }

  return `Scrolling down: ${isScrollingDown}`;
}
Copy the code

The official solution is to put the modified value directly in the function body.

Logically, this is the same timing as getDerivedStateFromProps, but if you look at it this way and console in the function body, you’ll see that the setState method inside the function body appears to trigger a re-rendering of the function component. Because you console multiple values.

The reason is that the function component will re-execute the function component every time it wants to fetch a new Hook value or whatever. If it executes setState in the function body, React will record it.

Take a quick look at the source logic.

// Children returned after running the function component
let children = Component(props);

// An update was initiated during the execution of the function component
if (didScheduleRenderPhaseUpdateDuringThisPass) {
  children = Component(props);
}
Copy the code

If an update schedule is initiated during the execution of the function component in the source code, the function component is executed synchronously again to obtain the new children value.

// setState will enter if judgment during execution
if (fiber === currentlyRenderingFiber) {
  // Record updates initiated during execution
  didScheduleRenderPhaseUpdateDuringThisPass = true;
} else {
  // Initiate update scheduling
  scheduleUpdateOnFiber(fiber, lane, eventTime);
}
Copy the code

So it looks like a rerender is triggered, but it’s really just the function component executing again. What’s wrong with that?

The official answer, however, will certainly be fine… I think if it’s a super heavy, like the 3000 line function component that someone else wrote before, it will definitely have some small performance impact.

So how do you make it look like the true getDerivedStateFromProps lifecycle?

My little thought

If we use useState, setState assigns the props value to state, which must cause the function component to re-execute. If we can manually control whether the function component refreshes, we’re done.

So you can use useRef to store the value of state, and then execute useState alone to initiate the update schedule.

const useDerivedState = (props) = > {
  const rerender = useState()[1];
  const stateRef = useRef();

  if(props? .value) { stateRef.current = props.value; }const setState = (value) = > {
    stateRef.current =
      typeof value === "function" ? value(stateRef.current) : value;
    rerender({});
  };

  return [stateRef.current, setState];
};
Copy the code

So it looks like a perfect reproduction.

And THEN I sealed a bag.