Save the “zombies”: How to add state and lifecycle methods to stateless React components

UPDATE 09/2021:
Things have changed quite a bit since 2017 — these days you can easily add state and lifecycle functions via React Hooks so the methods outlined here may no longer be relevant to you.
There are however some uncommon lifecycle functions like getSnapshotBeforeUpdate
, getDerivedStateFromError
and componentDidCatch
which do NOT have a Hook equivalent yet, in which case this article might still come in handy.
Additionally, it might be a fun read and / or give you some inspiration so go ahead and read it anyway!
The world of React is like a horror movie: On one side we have the handsome stateful components running around without a single care in their life(-cycles), while their stateless brethren, the zombies of this weird world, are doomed to roam the fields always longing for something they will never get: brai… I mean, state!
But unlike most horror movies, we could get a happy ending for our undead friends — by not only providing them with state, but also breathing life(cycles) into them.
Before we dive into the code though, let me clarify a few things first: I know there are already solutions out there like recompose that can help you achieve this, but I feel learning how to do it without external packages is invaluable.
Additionally, I don’t want to get into a discussion why this might be a good idea in the first place or not since you could probably argue for days over this point alone.
So with that out of the way, let’s begin!
“Ryuuga waga teki wo kurau”

If you’re anything like me, you love arrow functions. And even if you’re not like me or just don’t like arrow functions that much, you probably know that they can be super useful.
For those not “in the know”, let me fill you in:
Arrow functions have 2 benefits in comparison to regular functions (apart from having a cooler name —if you don’t believe me just add “arrow” to a random word and that makes it approx. 10 times cooler. “Arrow pig” — see?):
- They are less verbose:
// regular function
function () {
return someStuff;
}// arrow function
() => (someStuff)
2. They don’t have their own “this” but instead infer it from their lexical scope
(For more detailed information check out http://exploringjs.com/es6/ch_arrow-functions.html)
But how does this relate to React?
Glad that you asked. With the power of arrow functions, instead of defining a a simple component like this:
class MyComponent extends React.Component {
render() {
return (
<div>
... // More stuff
</div>
);
}
}
we can instead express it as:
const MyComponent = () => (
<div>
... // More stuff
</div>
);
Less typing and arguably easier to reason about — what’s not to love about it?
Well, the downside to this is it’s a stateless component now — which in itself isn’t bad, I mean it’s considered best practise for a reason. But as the name implies, it naturally has no state and doesn’t provide access to its lifecycle functions.
(Yes, you read that right, “its” lifecycle methods. Even though ESLint scolds you for not using stateless components whenever you can, React will internally convert stateless components to regular ones anyway. React Fiber promises performance benefits in the future, but as of now, it seems there is actually no real difference.)
So the question is: How can we save zombie-kind and add state and lifecycle methods to a stateless component?
Higher Order Components to the rescue!

Higher order components (=HOCs) are functions that take a component as input and return a new component which renders the original component. In essence, they wrap your original component into another component.
This is useful for adding or modifying props, functionality or other aspects of the original. If you used Redux you probably already used “connect” which is such a higher order component.
(For more information check https://facebook.github.io/react/docs/higher-order-components.html)
A really basic HOC (that actually doesn’t do anything useful) might look like this:
const HOC = Component => class extends React.Component {
constructor(props) {
super(props);
}
render() {
return <Component {...this.props} />;
}
};
It just takes a component, does its constructor magic and then renders the original component while passing down the props that the original would have received if it wasn’t wrapped (since now the wrapper component gets those props instead).
To apply this HOC to a component there’s two common ways. You either wrap your component right where you define it:
const MyStatelessComponent = HOC(() => (
<div>
... // More stuff
</div>
));
or if you’re fancy, you can just wrap the components export statement instead:
export default HOC(MyStatelessComponent);
I usually opt for the latter but there are cases where the former might come in handy as well.
Introducing: Mr. StateProvider!

Since we ultimately want to provide our component with state, let’s rename the thing to StateProvider and make it accept another argument — the initial state:
const StateProvider = (Component, initialState) => class extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
}
render() {
return <Component {...this.props} />;
}
};
So far so good, now the wrapper component’s internal state is set to whatever initialState was. But here’s problem: we can neither access said state nor manipulate it from our component, so let’s remedy that:
const StateProvider = (Component, initialState) => class extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
}
render() {
return <Component {...this.props} state={this.state} setState={this.setState.bind(this)} />;
}
};
We just pass the state and the regular old setState function itself as props to our stateless component. Note how we have to bind the this.setState(…) to “this” (the wrapper component) or else it wouldn’t work. You could also pass an arrow function to our component like this if you prefer:
setState={(newState, callback) => this.setState(newState, callback)}
In any case we now have access to the state in our stateless component, hooray!
We’re half way there — feel free to award yourself a cookie or a cool beverage of your choice.
The circle of life

To easily access the lifecycle methods of our wrapper component, we can make use of the fact that they are really just another object property.
What this means is that instead of writing
...didComponentUpdate() {
// Do stuff
}...
you could define the function somewhere else like
function doStuffWhenComponentUpdate() {
// Do stuff
}
and then manually set
this.didComponentUpdate = doStuffWhenComponentUpdate;
in the constructor — the outcome would be the same.
So let’s add a 3rd parameter to our StateProvider called lifeCycleHooks which is an object holding our functions:
const componentDidUpdate = () => {
// Do stuff
};const componentDidMount = () => {
// Do other stuff
};const lifeCycleHooks = {
componentDidUpdate,
componentDidMount,
};...export default StateProvider(MyStatelessComponent, someState, lifeCycleHooks);
Then we can just iterate over the passed in object’s property keys to set our lifecycle methods:
Object.keys(lifeCycleHooks).forEach(
(functionName) => {
this[functionName] = lifeCycleHooks[functionName];
},
);
Adding those improvements, our StateProvider now looks like this:
const StateProvider = (Component, initialState, lifeCycleHooks) => class extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
Object.keys(lifeCycleHooks).forEach(
(functionName) => {
this[functionName] = lifeCycleHooks[functionName];
},
);
}
render() {
return <Component {...this.props} state={this.state} setState={this.setState.bind(this)} />;
}
};
The attentive reader might have noticed that since we set the functions inside the constructor, we can’t actually do the same thing for the constructor as a lifecycle function itself. It’s a chicken or egg kind of situation.
A simple solution to this dilemma is to name the function we would put in our lifecycleHooks object not “constructor” but “_constructor” (so we don’t accidently overwrite it in our Object.keys loop) and then just manually call it inside the normal constructor:
if (lifeCycleHooks._constructor) {
lifeCycleHooks._constructor(props);
}
These additions turn StateProvider into this:
const StateProvider = (Component, initialState, lifeCycleHooks) => class extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
Object.keys(lifeCycleHooks).forEach(
(functionName) => {
this[functionName] = lifeCycleHooks[functionName];
},
);
if (lifeCycleHooks._constructor) {
lifeCycleHooks._constructor(props);
}
}
render() {
return <Component {...this.props} state={this.state} setState={this.setState.bind(this)} />;
}
};
We now have a means to access state and lifecycle methods in our stateless components, how cool is that? We are an awesome team, you and me, yay!
The shocking truth
To be brutally honest though, I have omitted a tiny little detail which might sour your mood —

We have 2 components!
Yes, I know — shocking right? But it makes sense: 1+1=2 most of the time — and this is such a time.
As neat as our solution is, it can’t hide the fact that we are adding quite a bit of overhead to our stateless component by adding another component on top. Depending on your use case this might not matter much at all, but in the worst case it will be quite detrimental to your apps performance. So just to be on the safe side, it’d be great if we could get rid of this additional overhead.
Furthermore, having two components means that “StateProvider” will appear in all error traces (those on your dev console for example) and the React dev tools hierarchy tree, which isn’t ideal in my book.
Hmmm….. Component or function?

Good thing there is something we can do about this and it’s something not everyone knows. Stateless components can be used not only as a component, but also be directly invoked as a function:
Imagine you had a stateless component called FancyButton.
Normally you’d use it like this inside another component:
const OtherComponent = () => (
<div>
<FancyButton />
<div>
);
This would make it so, that you end up with two components, OtherComponent and FancyButton itself — nothing special.
But what would happen if you’d use FancyButton as a regular function though?
const OtherComponent = () => (
<div>
{FancyButton()}
<div>
);
The output would be visually identical, but internally, we’d end up with only one(!) component. This is because using it as a function, FancyButton just outputs its JSX into OtherComponent’s JSX. Therefore it looks the same on the page, but FancyButton doesn’t actually get turned into a component by React. Neat!
So let’s do the same thing in our StateProvider to get rid of the rendering and any other overhead:
const StateProvider = (Component, initialState, lifeCycleHooks) => class extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
Object.keys(lifeCycleHooks).forEach(
(functionName) => {
this[functionName] = lifeCycleHooks[functionName];
},
);
if (lifeCycleHooks._constructor) {
lifeCycleHooks._constructor(props);
}
}
render() {
return Component({...this.props, state: this.state, setState: this.setState.bind(this)};
});
};
Essentially we are no longer wrapping a component with another component but are actually merging the two into what could be described as the “stateful version” of our stateless component.
Nomen est Omen
Now that we got rid of the additional component we can sit back and relax, right?
Sadly, no. As awesome as it is that we now have a single component, it also means that when you encounter an error it will only tell you that the error occurred “in StateProvider” since the stateless component itself is now gone. If you use StateProvider in more places this could get really confusing.

The same thing also happens in the React dev tools, where you won’t be able to find the stateless component anymore.
So what should we do? Abort? But we’ve come this far already!
Fear not, the solution to this dilemma is actually quite simple: We rename our StateProvider!
Components have a static getter function which returns their respective name.
The cool thing is, we can just overwrite it. By adding
const StateProvider (Component, ....) => class extends.... static get name() {
return Component.name;
}};
StateProvider will return the stateless component’s name instead of “StateProvider”. Our problems are solved!
This is the final version of our StateProvider:
const StateProvider = (Component, initialState, lifeCycleHooks) => class extends React.Component {
static get name() {
return Component.name;
} constructor(props) {
super(props);
this.state = initialState;
Object.keys(lifeCycleHooks).forEach(
(functionName) => {
this[functionName] = lifeCycleHooks[functionName];
},
);
if (lifeCycleHooks._constructor) {
lifeCycleHooks._constructor(props);
}
} render() {
return Component({...this.props, state: this.state, setState: this.setState.bind(this)});
}
};
Closing thoughts

I congratulate you for making it to the end (pretty much) unscarred. With some tweaking here and there we actually ended up with something that gives us everything we wanted with hardly any drawbacks.
One could argue that there is a lot more functionality that could be added (like giving the stateless component access to “this”) but for obvious reasons I can’t possibly cover them all here — so feel free to explore those on your own.
If saving the zombies made you all tingly in your heart, please hit that clap button below and remember to tweet / share the post with your friends so they‘re also prepared for the next zombie apocalypse.
Also, studies have shown that following me on Medium will increase your karma by at least 0.0000001% (and you can never have enough karma), plus it will get you notified when new posts are published as well, so give it some consideration, toodeloo!
Image credit (in no particular order): “Happy Zombie”: Humoropedia, “Hello my name is”: Pinterest, “both”: Heard County Parks & Recreation, “Shocking truth”: Youtube, “Circle of life”: Disney / Kotaku, “Handshake guy”: derstandard.at, “Higher Order Thinking Skills:” Pinterest, “Hanzo”: Blizzard, “Zombie”: Pixabay