We’re in the midst of a big paradigm shift in the world of software development. Just like object oriented programming became fashionable a couple of decades ago, functional programming may become an idea of similar significance. The term functional is becoming synonymous with words like pure and stateless. If you’re interested in why this is I suggest you look up functional programming in general. In this post I’m going to cover React’s transition from stateful classes to stateless functions in practice.
Anatomy of a React Class Component
Let’s first have a look at a typical React class component.
Constructor
The constructor sets the initial state of the component and it has to make sure to call the parent’s constructor as well:
constructor(props) {
super(props);
this.state = {text: "Initial state"};
}
Lifecycle
These are the three lifecycle methods commonly used when working with component classes:
componentDidMount() {
// do something on mount
this.setState({text: "Component did mount"});
}
componentDidUpdate(prevProps) {
// do something on update
if (prevProps.prop != this.props.prop) {
this.setState({text: "prop changed"});
}
}
componentWillUnmount() {
// do something on unmount
}
Render
The render method does all the rendering:
render() {
return <div>{this.state.text}</div>;
}
Functional Component Explained
Now, let’s do the same as above but with a functional component:
(props) => {
const [text, setText] = useState("Initial state");
useEffect(() => {
setText("Component did mount");
return () => { /* do something on unmount */ };
}, []);
useEffect(() => {
setText("prop changed");
}, [props.prop]);
return <div>{text}</div>;
};
There’s a lot to unpack here! Let’s go over this line by line:
- As you can see, props are given as an argument to the function.
- To initialize a component’s state you call
useState
which will return the initial state (given by the optional parameter of the function) as well as another function to update the state. You can calluseState
as many times as you like and initialize more than one state! - The next two calls are both calls to
useEffect
yet with one important difference in the second parameter. The function passed as a parameter touseEffect
is called whenever one of the provided dependencies change. These dependencies are specified by the second parameter which, in case of the first call, is an empty array. This means there are no dependencies and therefor the effect is called only once, on mount. - The function passed to the first
useEffect
also returns a function. This is a cleanup function which, in this case, is called on unmount. - The function passed to the second
useEffect
has a dependency onprops.prop
and is therefor called every time the value of this dependency changes. Note: This is not just a componentDidUpdate as it’s called only if that one prop changes. It doesn’t care whether there are other props or not! Just as withuseState
you can calluseEffect
as many times as you like. - Inside the functions passed to
useEffect
we usesetText
to update the text state. - In the end we just return the rendered node as we would inside a render method.
By now it should be clear that it is really all functions. Functions that take functions as parameters and may even return functions. One of the benefits is that these functions can be called where ever you like, whenever you like, and, most importantly, as often as you like.
All these built-in functions we used here are called Hooks, in case you wondered.
But Wait, There’s More!
Higher Order Components (HOC)
Experienced React developers are probably very familiar with the concept of higher order components. These are components that wrap other components and inject certain props into them. Let’s say you’ve got a HOC that injects hocProp into your component. In this case you’ll probably have something like this:
export withHocProp(MyComponent);
Inside the withHocProp
function there’s most likely something like this going on:
…
<WrappedComponent
{...this.props}
hocProp=…
/>
…
For functional components you don’t create a withHocProp
function anymore — instead you create a useHocProp
function that can be called as a Hook and just returns the value for hocProp:
const useHocProp = () => {
…
return hocProp;
};
An important note is that useHocProp may call other hooks like useEffect
and useState
in order to become a fully featured component itself!
The official React documentation has a good example for this.
Contexts
If you have worked with React Contexts chances are you also created withContext
higher order components to inject the desired context into components.
These HOC functions for contexts are no longer needed and are replaced by React’s very own useContext
Hook. This is just another function call inside your component:
const context = useContext(MyContext);
Other Hooks
There are other important Hooks that you can you use in order to optimize the performance of your application for example.
useMemo optimizes function calls based on dependencies much like we’ve seen with the useEffect
. Let’s say you have a function that calculates a value based on a prop. You could move this into a state and update the state with useEffect
whenever the prop changes — or you could just call the function whenever the prop changes using useMemo
:
const value = useMemo(
() => expensiveFunction(props.prop),
[props.prop]
);
The value of value is now updated everytime props.prop changes but not every time the component changes. The function expensiveFunction is therefor only called when needed.
useCallback is similar to useMemo in the sense that it returns a memoized callback. Let’s say you have a click handler that is only dependent on props.prop
. You could then create a callback function that is only re-evaluated when its dependency changes (it’s still called on every click, of course):
const clickHandler = useCallback(
() => {
// do stuff depending on props.prop
},
[props.prop]
);
return <button onClick={clickHandler}>Press me</button>;
If you’d like to know what other Hooks exists I suggest you take a look at React’s Hooks API Reference.
Conclusion
Although it may take some getting used to, functional React feels more powerful and even more elegant to me personally.
If you or your company hasn’t moved on to functional components yet it’s probably time you reconsider because the concepts of functional programming are here to stay — as is apparent with many languages and frameworks becoming increasingly functional in nature.