Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Joe Mesot
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Joe Mesot@JoeMesot )

Initialize Instance Variables In getInitialState() For Consistency In ReactJS

By Ben Nadel on

The ReactJS documentation is pretty solid when it talks about how to define and consume the state (ie, the view-model) inside of a React component. But, it's not very clear on where and when to deal with general component state that exists outside of the view-model. As such, it's left up to the developer to figure out where, why, when, and how to approach non-view-model initialization. And, the more I noodle on it, the more it makes sense to manage instance variables inside the getInitialState() method of the React component.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

The first time that I ran into this separation of concerns was when dealing with timers (ex, setTimeout()). Suddenly, I had a value - the timer reference - which didn't directly influence the rendering of the virtual DOM. As such, it didn't seem right to be part of the "this.state" collection. But, if I defined it as an instance variable on the component, it was unclear as to when it should actually be defined.

Timers are dependent upon the DOM, which means that we can't actually initiate a timer until the componentDidMount() method is invoked. As such, you may be tempted to defer instance variable declaration until the componentDidMount() method is run. But, to think about component state in this mannor is to lose the forrest for the trees.

It's easy to become so mired in the concept of state that you forget the context in which you are working. Everything starts to center around the "state" and how it is maintained and mutated over time. What you quickly lose sight of is the fact that the state - the view-model - is nothing more than an instance variable on the React component.

And, when you step back and think about the state as nothing more than an instance variable on the React component, things become a lot clearer. There's no longer a separation between "state" and "non-state" - there is only a collection of instance variables, one of which happens to represent the view-model. When you have this epiphany, you realize that all instance variables should be declared during object construction, even if some of them only apply to DOM (Document Object Model) interaction executed during client-side rendering.

If you're using ES6 components (which I have not yet tried personally), this would all go in your class constructor. But, if you're using the React.createClass() approach, the closest thing we have to a constructor it the getInitialState() method. As such, I would use this method as a means to define all of the instance variables, including, but not limited to, the view-model.

To see what I mean, I've set up a small ReactJS demo in which a portion of the state is updated based on a setTimeout() timer. And, while I cannot actually call the setTimeout() method until the componentDidMount() method, you'll see that I am declaring both the timer and the timer delay in the getInitialState() method:

  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Initialize Instance Variables In getInitialState() For Consistency In ReactJS
  • </title>
  •  
  • <link rel="stylesheet" type="text/css" href="./demo.css"></link>
  • </head>
  • <body>
  •  
  • <h1>
  • Initialize Instance Variables In getInitialState() For Consistency In ReactJS
  • </h1>
  •  
  • <div id="content">
  • <!-- This content will be replaced with the React rendering. -->
  • </div>
  •  
  •  
  • <!-- Load scripts. -->
  • <script src="../../vendor/reactjs/react-0.13.3.min.js"></script>
  • <script src="../../vendor/reactjs/JSXTransformer-0.13.3.js"></script>
  • <script type="text/jsx">
  •  
  • // I manage the Demo widget.
  • var Demo = React.createClass({
  •  
  • // I get called once for pre-mount state initialization.
  • getInitialState: function() {
  •  
  • // I maintain a reference to the pending timer for the deferred
  • // displaying of the message. While not used for rendering directly,
  • // this is part of the component's general state and should live
  • // alongside the rest of the state.
  • this.timer = null;
  • this.timerDelay = this.getRandomTimerDuration( 1000, 2000 );
  •  
  • // Return the initial view-model.
  • return({
  • message: null
  • });
  •  
  • },
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I get called after the component has mounted in the browser's DOM.
  • componentDidMount: function() {
  •  
  • // Now that we know we have the DOM, including the global Window object,
  • // we know we have access to the setTimeout() function. Let's kick off
  • // the deferred message timer.
  • this.timer = setTimeout( this.handleTimer, this.timerDelay );
  •  
  • },
  •  
  •  
  • // I get called once before the component is removed from the browser's DOM.
  • componentWillUnmount: function() {
  •  
  • // Clear the timer in case the component was mounted and then unmounted
  • // before the timer has a chance to resolve.
  • // --
  • // NOTE: This demo doesn't actually unmount the component; but, I am
  • // trying to get used to thinking about the full component life cycle.
  • clearTimeout( this.timer );
  •  
  • },
  •  
  •  
  • // I update the view-model when the message timer has resolved.
  • handleTimer: function() {
  •  
  • this.setState({
  • message: "Bang!"
  • });
  •  
  • },
  •  
  •  
  • // I return the virtual DOM representation of the view-model.
  • render: function() {
  •  
  • var messageNode = this.state.message
  • ? ( <strong>{ this.state.message }</strong> )
  • : "..."
  • ;
  •  
  • return(
  • <p>
  • Wait for it: { messageNode }
  • </p>
  • );
  •  
  • },
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I return a value between the given bounds, inclusive.
  • getRandomTimerDuration: function( min, max ) {
  •  
  • return( min + Math.floor( Math.random() * ( max - min + 1 ) ) );
  •  
  • }
  •  
  • });
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // Render the root Demo and mount it inside the given element.
  • React.render( <Demo />, document.getElementById( "content" ) );
  •  
  • </script>
  •  
  • </body>
  • </html>

I'm sure that some of you are looking at this code and still think that the timer instance variable shouldn't even be defined until the componentDidMount() method is called. But, let me ask you this: should the same reasoning apply to the handleTimer() method? I mean, if you are adamant about not defining "this.timer" until the componentDidMount() method is invoked, I assume you would be similarly adamant about not defining the "this.handleTimer()" method until the componentDidMount() method is invoked?

When you think about methods, though, you start to see the "forrest" again. You realize that your instance methods are all defined (and auto-bound if you're not using ES6) during instance construction, regardless of how they get used. At this point, you might realize that deferring instance variable declaration until the componentDidMount() method invocation is actually the exception, not the rule.

When I first got into ReactJS (and granted, I'm still very new to it), I thought about ReactJS components as just that - ReactJS components. But really, in my mind, I was conflating the mechanics of the Objects with their use case. First and foremost, they are JavaScript objects that have a lifecycle (ie, setup, usage, and teardown). Then, secondarily, they are ReactJS components that have a specific purpose. Thinking about the code in this way makes it easier to reason about where and when instance variables should be defined.




Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.