{"id":30,"date":"2016-07-06T21:52:04","date_gmt":"2016-07-06T21:52:04","guid":{"rendered":"https:\/\/andrewperr.in\/blog\/?p=30"},"modified":"2016-12-11T07:03:15","modified_gmt":"2016-12-11T07:03:15","slug":"reactjs-state-props-and-reusable-components","status":"publish","type":"post","link":"https:\/\/andrewperr.in\/blog\/2016\/07\/06\/reactjs-state-props-and-reusable-components\/","title":{"rendered":"ReactJS: State, Props and Reusable Components"},"content":{"rendered":"<p>So here&#8217;s a thing.<\/p>\n<p>As I build more components in ReactJS, I&#8217;m starting to uncover some interesting use cases. One such case that came up recently involved reusing components &#8212; a component that can be used both on its own and as part of another component. Let me explain.<\/p>\n<p>A new feature on <a href=\"https:\/\/projectnom.com\" target=\"_blank\">ProjectNom<\/a> involves connecting your user profile to a Twitter account. To make this a more seamless experience, I decided to build a simple React component that surfaces the current state of your Twitter account.<\/p>\n<p>For example, if you haven&#8217;t added your Twitter account yet:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.mageuzi.com\/blog\/wp-content\/uploads\/2016\/07\/twitter-button1.png\" alt=\"Connect Twitter Account Example\"  height=\"50\" class=\"aligncenter wp-image-780\" \/><\/p>\n<p>If you want to add your Twitter account, then we have to send you to the Twitter website to authenticate, so we display the following:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.mageuzi.com\/blog\/wp-content\/uploads\/2016\/07\/twitter-button2.png\" alt=\"Redirect to Twitter Example\"  height=\"50\" class=\"aligncenter wp-image-781\" \/><\/p>\n<p>And finally, if you&#8217;ve already connected your Twitter account:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.mageuzi.com\/blog\/wp-content\/uploads\/2016\/07\/twitter-button3.png\" alt=\"Twitter Connected Example\" height=\"75\" class=\"aligncenter wp-image-783\" \/><\/p>\n<p>As you can see, this component revolves heavily around manipulating a button, so I knew that would be the primary element in the render.<\/p>\n<p>But thinking more generally, the second state with the busy indicator was something that jumped out as a component that I could use elsewhere on the site. So, I decided to build a &#8220;BusyButton&#8221; component which could be used both as part of this Twitter component but also on its own whenever I needed a button to display a &#8220;busy&#8221; state.<\/p>\n<p>So first, let&#8217;s make a BusyButton suitable for our Twitter component. As described in my earlier post, it&#8217;s best practice to store state in the outermost parent component. Child components just use their props. So, using that philosophy, we can build our BusyButton component like so:<\/p>\n<pre class=\"size-big\"><code class=\"language-javascript\">module.exports = React.createClass({\r\n\trender: function() {\r\n\t\tvar buttonStyle = \"btn-\" + this.props.style;\r\n\t\t\t\t\r\n\t\tif (this.props.busy)\r\n\t\t{\r\n\t\t\tvar busyLabel = this.props.busyLabel\r\n\t\t\t\t? React.DOM.span({style: {paddingLeft: 10}}, this.props.busyLabel)\r\n\t\t\t\t: \"\";\r\n\r\n\t\t\treturn React.DOM.button({className: \"btn \" + buttonStyle, style: {minWidth: 75}, disabled: \"disabled\"},\r\n\t\t\t\tReact.DOM.i({className: \"fa fa-circle-o-notch fa-spin\"}),\r\n\t\t\t\tbusyLabel\r\n\t\t\t);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar icon = this.props.icon\r\n\t\t\t\t? React.DOM.i({className: \"fa fa-\" + this.props.icon, style: {paddingRight: 10}})\r\n\t\t\t\t: \"\";\r\n\r\n\t\t\tvar attributes = {className: \"btn \" + buttonStyle, type: this.props.type, id: this.props.id, onClick: this.props.onClick};\r\n\t\t\t\r\n\t\t\tif (this.props.disabled) {\r\n\t\t\t\tattributes.disabled = \"disabled\";\r\n\t\t\t}\r\n\r\n\t\t\treturn React.DOM.button(attributes,\r\n\t\t\t\ticon,\r\n\t\t\t\tthis.props.label\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n});<\/code><\/pre>\n<p>As you can see, this component is driven entirely with props. If this is a child component, then that makes sense. If the parent gets rendered again, it will pass new props to the child, and the child&#8217;s behavior will change when it renders.<\/p>\n<p>So far so good.<\/p>\n<p>But now we come to our second acceptance criteria. We want this button to have the same behavior on its own: a button that can be marked as busy, and have the same disabled state and indicator icon.<\/p>\n<p>With our current component, we have to set everything with props. That&#8217;s fine for the component&#8217;s initial render, but if we want to change the component&#8217;s behavior after the fact, we have a problem. Props are supposed to be immutable &#8212; an initial state and nothing more. (see: <a href=\"https:\/\/facebook.github.io\/react\/tips\/props-in-getInitialState-as-anti-pattern.html\" target=\"_blank\">Props in getInitialState Is an Anti-Pattern<\/a>)<\/p>\n<p>Truth be told, we could ignore this advice and use something like <a href=\"https:\/\/facebook.github.io\/react\/docs\/component-specs.html#updating-componentwillreceiveprops\" target=\"_blank\"><code>componentWillReceiveProps<\/code><\/a> to make the component behave like we expect. The issue I have with this approach is that it ignores the fact we are fundamentally talking about changes in the component&#8217;s state. The &#8220;active&#8221; and &#8220;busy&#8221; behaviors are two different <em>states<\/em> that the button can be in. And the button can freely move between those states as necessary.<\/p>\n<p>This was the conundrum: I needed the component to run from props in order to keep state isolated in one spot, but I also needed the component to maintain state so that it could be easily manipulated when used on its own. I never found a satisfactory answer to this problem, so I welcome any suggestions or best practices for others who may have encountered this scenario.<\/p>\n<p>In the meantime, I&#8217;ve done something of a compromise:<\/p>\n<pre class=\"size-big\"><code class=\"language-javascript\">module.exports = React.createClass({\r\n\tgetInitialState: function() {\r\n\t\treturn {\r\n\t\t\tbusy: this.props.initialBusy,\r\n\t\t\tdisabled: this.props.initialDisabled\r\n\t\t};\r\n\t},\r\n\r\n\tbusy: function() {\r\n\t\tthis.setState({busy: true});\r\n\t},\r\n\t\r\n\tactivate: function() {\r\n\t\tthis.setState({busy: false});\r\n\t},\r\n\t\r\n\tdisable: function() {\r\n\t\tthis.setState({disabled: true});\r\n\t},\r\n\t\r\n\tenable: function() {\r\n\t\tthis.setState({disabled: false});\r\n\t},\r\n\r\n\trender: function() {\r\n\t\tvar buttonStyle = \"btn-\" + this.props.style;\r\n\t\t\t\t\r\n\t\tif (this.state.busy)\r\n\t\t{\r\n\t\t\tvar busyLabel = this.props.busyLabel\r\n\t\t\t\t? React.DOM.span({style: {paddingLeft: 10}}, this.props.busyLabel)\r\n\t\t\t\t: \"\";\r\n\r\n\t\t\treturn React.DOM.button({className: \"btn \" + buttonStyle, style: {minWidth: 75}, disabled: \"disabled\"},\r\n\t\t\t\tReact.DOM.i({className: \"fa fa-circle-o-notch fa-spin\"}),\r\n\t\t\t\tbusyLabel\r\n\t\t\t);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar icon = this.props.icon\r\n\t\t\t\t? React.DOM.i({className: \"fa fa-\" + this.props.icon, style: {paddingRight: 10}})\r\n\t\t\t\t: \"\";\r\n\r\n\t\t\tvar attributes = {className: \"btn \" + buttonStyle, type: this.props.type, id: this.props.id, onClick: this.props.onClick};\r\n\t\t\t\r\n\t\t\tif (this.state.disabled) {\r\n\t\t\t\tattributes.disabled = \"disabled\";\r\n\t\t\t}\r\n\r\n\t\t\treturn React.DOM.button(attributes,\r\n\t\t\t\ticon,\r\n\t\t\t\tthis.props.label\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n});<\/code><\/pre>\n<p>I ended up using state, since it conceptually made sense for the component. But rather than manipulate state directly, I&#8217;ve exposed some custom functions on the component that allow its user (whether it be a parent component or custom JavaScript) to control whether the button is in an active or busy state.<\/p>\n<p>The disadvantage is that you <em>have<\/em> to use these functions. Passing down props from the parent no longer works, because this component now has state, and state takes precedence over props. Luckily, this is a basic component with only two primary states (active &#038; busy), so it&#8217;s easy to manage.<\/p>\n<p>But it&#8217;s clear that React doesn&#8217;t have good support for this scenario, and I&#8217;m not entirely happy with this solution. For a framework that is built entirely around the concept of reusable components, it isn&#8217;t very clear how reusability is supposed to work.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>So here&#8217;s a thing. As I build more components in ReactJS, I&#8217;m starting to uncover some interesting use cases. One such case that came up recently involved reusing components &#8212; a component that can be used both on its own and as part of another component. Let me explain. A new feature on ProjectNom involves [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[2,3],"tags":[9,13],"class_list":["post-30","post","type-post","status-publish","format-standard","hentry","category-computers","category-programming","tag-javascript","tag-reactjs"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p85fRU-u","_links":{"self":[{"href":"https:\/\/andrewperr.in\/blog\/wp-json\/wp\/v2\/posts\/30","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/andrewperr.in\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/andrewperr.in\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/andrewperr.in\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/andrewperr.in\/blog\/wp-json\/wp\/v2\/comments?post=30"}],"version-history":[{"count":2,"href":"https:\/\/andrewperr.in\/blog\/wp-json\/wp\/v2\/posts\/30\/revisions"}],"predecessor-version":[{"id":32,"href":"https:\/\/andrewperr.in\/blog\/wp-json\/wp\/v2\/posts\/30\/revisions\/32"}],"wp:attachment":[{"href":"https:\/\/andrewperr.in\/blog\/wp-json\/wp\/v2\/media?parent=30"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/andrewperr.in\/blog\/wp-json\/wp\/v2\/categories?post=30"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/andrewperr.in\/blog\/wp-json\/wp\/v2\/tags?post=30"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}