CodeX
Published in

CodeX

On replacing componentDidUpdate and other React life cycle methods with hooks

I’ve addressed React’s life cycle before on this blog because it’s a common cause of acute nervous breakdown for many devs…

For a React engineer, having a deep understanding of component life cycle is like having an extra 15 inches if you’re a basketball player. And it’s especially helpful when refactoring class-based components to functional components — something I do just about all day long as a React engineer in 2021. It’s mostly just a matter of adjusting syntax, but what about life cycle methods like or ?

Let’s take an honest-to-goodness real-life example from my work: a , which will be when I’m through with it. It’s got lots of moving parts:

import { connect } from 'react-redux';
import { fetchPerformance } from '...redux actions...';
class Calculator extends Component { state = {
isModalOpen: false,
selectedDateRangePrices: []
...more delicious state here...
};
componentDidUpdate = ( previousProps, previousState ) => {
if ( ...something about state/props changes ) {
this.setState( {
isModalOpen: true,
...more nice juicy state...
} );
}
};
render () {
return (
...a beautiful calculator component...
);
}
}const mapStateToProps = (state) => ( {
start_date: state.closedEnds.start_date,
end_date: state.closedEnds.end_date,
starting_price: state.closedEnds.starting_price,
ending_price: state.closedEnds.ending_price,
ttl_return: state.closedEnds.ttl_return,
avg_annual: state.closedEnds.avg_annual,
time_calculated: state.closedEnds.time_calculated
} );
export default connect( mapStateToProps, { fetchPerformance } )( Calculator );

In we fields, s, and a modal which displays some useful information, hidden or displayed according to a boolean flag set in state. We also have to juggle some data from a Redux store along with our local .

I need to switch that flag to only if certain attributes of and change; so, in class-based React, naturally I write that logic in using and . But… refactoring this to hook-based React isn’t a matter of straightforward character-for-character translation.

React’s docs aren’t very helpful here: they tell us we “can think of as , , and combined…” but doesn’t give us anything helpful or familiar like . So what do?

Here’s what the React docs should explain: Essentially, everything that happens to a component during its life cycle, between mount/update/unmount/s, can be described as the component’s “side effects” or just “effects” (hence the name). Class-based React handles these side effects with methods and reference arguments — a little clunky and not very declarative. Functional React handles all side effects together in one hook, , and all references together in another hook, . More elegant and React-ish.

Back in my , let’s say I want to open the modal whenever changes in my component’s local :

// The modal opens if state.selectedDateRangePrices has changedcomponentDidUpdate = ( prevProps, prevState ) => {
if ( this.state.selectedDateRangePrices !== prevState.selectedDateRangePrices ) {
this.setState( { isModalOpen: true } );
}
}

It’s easy to translate this to hook-based React. Take care of state with the hook; then, add a that opens the modal in the body of its callback, and add to its dependencies array:

function Calculator( { ...props galore... } ) {  const [ state, setState ] = useState( { 
isModalOpen: false,
selectedDateRangePrices: []
...state, wonderful state...
} );
const { start_date, end_date, starting_price, ending_price, ttl_return, avg_annual, time_calculated } = useSelector( state => state.openEnds ); useEffect( () => setState( { ...state, isModalOpen: true } ), [ state.selectedDateRangePrices ] );}

Here’s where it gets trickier: what if I only want to set state and open that modal if the props from Redux change? How about even trickier conditions, like only opening it when three or more have changed? Or when both of two particular ones have changed?

// The modal only opens if both ttl_return and avg_annual have changedcomponentDidUpdate = ( prevProps, prevState ) => {
if ( this.props.ttl_return !== prevProps.ttl_return && this.props.avg_annual !== prevProps.avg_annual ) {
this.setState( { isModalOpen: true } );
}
}

We don’t have the reference from , so to translate a component like this to hooks, I make my own reference with React’s hook. I’m not the first or the only one to say it: is the unsung hero of React hooks. Think of refs as little storage boxes that hold onto anything — results, DOM elements, other variables — that the engineer (you) can call upon using the attribute . Refs are incredibly useful for situations like this where we’re fine-tuning a component’s life-cycle with elements or data from outside that component’s side effects:

function Calculator( { ...props galore... } ) {  const [ state, setState ] = useState( { 
isModalOpen: false,
selectedDateRangePrices: []
...state, wonderful state...
} );
const { start_date, end_date, starting_price, ending_price, ttl_return, avg_annual, time_calculated } = useSelector( state => state.openEnds ); const previousRef = useRef(); useEffect( () => {
if ( previousRef.current ) {
if ( ttl_return !== previousRef.current.ttl_return && avg_annual !== previousRef.current.avg_annual ) {
setState( { ...state, isModalOpen: true } )
}
}
return () => {
previousRef.current = { ttl_return, avg_annual };
};
} );
}

What’s going on here?

  • We start by defining .
  • We write a , which runs when a component is first mounted or whenever it updates. Inside, the callback checks if our condition is met — i.e., if both current Redux props are equal to their counterparts in . We also check if is so we don’t open our modal when the page is first loaded.
  • This s a cleanup function, defining any logic which needs to run before a components unmounts/updates — the equivalent of . This cleanup function updates our , so the component can hang onto references just like it could with .
  • The logic we defined, and the cleanup function we , means we don’t need a dependencies array.

You can also create a custom hook with inside — called , maybe — but 1) that’s unnecessary and 2) that requires giving up a lot of the control and specificity that makes so powerful.

On that note, consider yourself warned: is not a substitute for good React design! It’s awesome to stretch beyond the normal flow of from parents to children… but, stretch too far beyond it and you’ll end up with some very wacky bugs or, at least, indecipherable code that’s a nightmare to edit. Every reference should have clear logic and a compelling reason for existence— lacking either is a clear sign to find something within the component’s side effects to get what you need, or consider restructuring your code.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Josh Frank

Oh geez, Josh Frank decided to go to Flatiron? He must be insane…