This is a follow-up tutorial to my last one Step-by-Step Minimalistic React Web App with Webpack 4 and Just the Bare Essentials, where I covered the philosophy behind React, how to setup the infrastructure for a React app, and how to write a very simple React app with just a single line that announces “Hello! I’m a React app!!” but which otherwise does not have any functionality. In this tutorial I iterate upon that work to demonstrate how to develop a real useable React app with various functionality, and for that, it will need to be a multi-widget app. Generally, a non-trivial React app will have to be built as a hierarchy or widgets, and in this tutorial I will demonstrate how to do that. I will cover additional concepts such as React widget props
and state
.
I recommend that whoever is unfamiliar with React first read my previous tutorial; however, in addition to that, I will also summarize the philosophy behind React here:
I’m going to base my work off the React + Webpack 4 infrastructure boilerplate from the previous tutorial, specifically the branch webpack4--preact--package.json
git checkout webpack4--preact--package.json
, from which I am forking a new branch react-multi-widget
git branch react-multi-widget; git checkout react-multi-widget
to develop the app in.
The specific commit I’m starting with is 1aa99111f0aec7359944b0d55351a14ae3cabdb7 test page
.
Remember, for each commit hash in red rendered in this tutorial, you can click on the yellow “test page” link after the hash to see the test page for that commit.
After switching to this branch git checkout react-multi-widget
, we need to run npm install
to install the packages specified in package.json
.
We need to run ./node_modules/webpack/bin/webpack.js --config ./webpack.config.js
to transpile the app.
We are starting with a Preact-based development build that is completely identical to the previous tutorial. Since Preact is being used, the transpiled JavaScript output file webpack_out/minimalistic_react.js
is relatively small 98 KB raw size.
I will now change the various name, description, and URL strings to reflect this new multi-widget app project. I have done this in the commit 1a221ffca9b249403148c444b4f28cc53845ff5a test page
. Note that I also changed the Webpack output JavaScript filename webpack_out/minimalistic_react.js
to webpack_out/main.js
. As can be seen from the test page (by clicking on the link next to the commit hash), the test page title and first line have changed to reflect this new project.
This branch will be used for development, so the project should be setup for quick and easy debugging. However, the substitution of Preact for React is going to prevent us from using the React Dev Tools plugin to inspect the app as it is being developed. So for now I will comment-out the configuration logic that tells Webpack to substitute Preact for React. The commit for that is 183d64e42379b4d86fb901ccb69d80e298c6d21b test page
. I only commented-out this config because I plan to uncomment it in the future production branch.
The raw size of webpack_out/main.js
has increased from 98 KB raw size to 715 KB. This is not a problem for development when the work is being done on a local machine.
Continued development will require lots of iterative steps, so it’s very important that these take as little time as possible. To save time, I will:
Have Webpack transpile the source code in the watch
mode, documented in the BUILD_INSTRUCTIONS
as ./node_modules/webpack/bin/webpack.js --config ./webpack.config.js --watch
.
I will use the NPM package http-server
to serve the app directly from its development directory. I manually added this package as a dev dependency to the package.json
with the command npm install http-server --save-dev
, and documented this in the example repo via the commit 3d757d92be93f35d7cc8ec82efcdb573433be617 test page
. The official package instructions recommend installing it globally with npm install -g http-server
, but in this tutorial I installed it locally just not to mess with the global system config. The server can now be run from the project root directory via the command ./node_modules/http-server/bin/http-server -a 127.0.0.1
. All files in the development directory will now be accessible via the local URL http://127.0.0.1:8080, and the test page file as http://127.0.0.1:8080/try_me.html.
Keep in mind that without the -a 127.0.0.1
option, http-server
will make the directory it’s running in (and all its subdirectories) also be available over the machine’s external IP address, making it accessible to at least every other machine on the local subnet.
OK, we’re finally ready to delve into the pure React logic of our app! The existing implementation is a React app in the sense that it uses the ReactDOM
API, but it does not consist of any React widgets. Since we’re going to be building a multi-widget app, we need to refactor as much as possible of what we have so far into widget-based logic, and then continue building more widgets on top of that.
So far, the app only renders a <div>
with the string Hello! I'm a React app!!
inside. I extracted that into a separate widget called HelloWidget
with the commit 8e3ac6c643aa41faac89071d24e169872f93d317 test page
.
Notice how I started declaring the widget with the line class HelloWidget extends React.Component {
The class
keyword is an ES6 / ES2015 concept, and as you can see, I’m directly using the React
object, even though in the previous tutorial I only imported it but did not use it directly. And I’m now passing the JSX expression <HelloWidget />
rather than <div>...</div>
to ReactDOM.render(...)
. I have declared a React widget separate from telling React to render it.
The JSX expression <div>Hello! I'm a React app!!</div>
previously rendered with ReactDOM.render(...)
still gets rendered by that API, just not directly, but as part of the rendering of the <HelloWidget>
widget. The old expression itself is being returned from the render()
method of the class HelloWidget
.
As this code demonstrates, all that’s needed to render a widget in JSX is to put the familiar HTML tag brackets <
and >
around the associated class name, and the content goes between the opening and closing tags. If there’s only one tag, then it should terminate with a slash /
just like in XML. Unterminated tags will be treated as syntax errors, hence the JSX expression for rendering the HelloWidget
is <HelloWidget />
not <HelloWidget>
.
As can be seen on the test page for the commit 8e3ac6c643aa41faac89071d24e169872f93d317 test page
, while the underlying logic has changed, the app behaves exactly the same.
Now that a separate widget has been created, it can be modified at will without disturbing any other logic of the app. So I’m going to add some CSS styles to demarkate it with a border, add some padding, and center the text inside via the commit e942d13fa3ed1d391f6193cdc4001d115c8d8462 test page
. Now that the text is centered and has a border around it, this <HelloWidget>
widget is beginning to look like an actual widget!
One important React / JSX concept that has to do with this change is that even though the well known HTML style
attribute was used to pass the CSS styles, the value of the attribute has not been specified in the traditional HTML format as a long string of CSS property / value pairs. In fact, had it been specified as a string, that would have resulted in a nasty ReactDOM runtime exception “Invariant Violation: The `style` prop expects a mapping from style properties to values, not a string.” React requires that CSS styles be specified as JavaScript objects / JSON expressions rather than strings. In addition to that, React requires that the CSS properties that contain dashes within their names be converted to camelCase
to get rid of the -
. This requirement allows for the CSS properties to be specified as keys of JSON expressions without quotes (even though technically keys without quotes are not valid JSON syntax). Since CSS properties are specified as JSON string values, and JSON string values always require quotes per JSON syntax, CSS properties do not need to be converted to camel case.
Another important take-away from this commit is that I rendered a JavaScript value — that is, the JSON expression with the CSS — inside the JSX markup using curly braces {}
. JavaScript values are denoted in JSX via these curly braces. In this case I have a 2nd set of nested curly braces inside the first JSX set, and this inner set is for the conventional JSON expression being rendered here.
Now that the widget has a border it is possible to distinguish it from other widgets. So I’m going to use it as a template for a new widget intended to allow the user to change the text being displayed. I just did this in the commit 92560c33b2eb1c6e297a79dc21c3aa575338feae test page
. The new widget has the same structure, but different text inside. In future commits I’ll add additional logic to allow the user to change the text. For now both widgets can be seen one after the other on the test page for this commit. Hooray, our app is now officially multi-widget!
One important thing to mention about the diff of this latest commit, is that not only was the call to ReactDOM.render(...)
modified to include the new widget, but both the old widget and the new had to be enclosed within an additional <div>
container. This is because React requires that “Adjacent JSX elements must be wrapped in an enclosing tag”. Without this enclosing / wrapping <div>
(or some other valid tag), the JSX transpiler will produce a syntax error with such a notice.
Cloning of one widget into another also cloned some logic that could be consolidated into one place. For less confusion, easier upgrading and maintenance going forward, to not have to copy the duplicate logic again and again with each new widget, and to not have a situation where the duplicate logic is modified inconsistently in one place but not another, leading to an inconsistent user experience, we want to minimize the volume of our source code and consolidate it as much as possible. In this case, it is particularly easy to consolidate the CSS styles into a single object to be available to both widgets. I just did that with the commit 860d6c519d6ebc6f6a1f1221cddabb3ce699240b test page
.
Here I’m adhering to the philosophy of keeping the code “DRY”, as in “Don’t Repeat Yourself”. Since this change only has to do with the way the source code is organized, but does not actually change the CSS rendered to the browser, the appearance of the app will remain the same.
There is a problem with the way the 2 widgets are getting laid out as their borders are touching. I just fixed this by giving them a CSS margin with the commit 9794a8d03ff16431a4191479329d8538d52a8a52 test page
. There is now a clear separation between both widgets.
This new widget is intended to allow the user to change the text. Perhaps the best user experience would be to auto-convert the statically-rendered text into an editable text field when the user clicks on it; however, such a mechanism would be overly complex for the concepts I’d like to demonstrate in this tutorial, so I’ll go with a much simpler user interface with which the user will click a button to get a text entry prompt to change the text.
For now I will just add the <button>
with the caption “Change text…”, which I just did in the commit 828ffa197a24529225c8fdac08739fe5260a86c8 test page
. (The 3 dots at the end of the caption indicate that this action will not be immediate but will require additional user input.)
The “Change text…” button is uncomfortably close to the text above it. There is a CSS padding: 1em
style being set on the whole widget, but not on the <div>
with the text. If only we could apply that CSS style to that <div>
as well, but if we do, that would double the observable padding between that text and its widget border, making the 2nd widget look inconsistent from the 1st. And if we also remove that CSS style from objStyleCommon
so that it is no longer doubled for the 2nd widget, then the 2nd widget will look correctly, but the 1st widget will now be missing its padding.
So the real solution is to split the CSS style padding: 1em
from objStyleCommon
to allow us to apply the former either together with the latter (for the 1st widget) or separately (for the 2nd widget). We would essentially create a separate parallel JSON object just with padding: 1em
, and call it, say, objStyleContent
, and then merge the 2 objects together for the widget 1 without the <button>
, and apply them separately in the widget 2 to have the padding go between the content <div>
with the text and the <button>
.
But unfortunately the JSON format does not provide a way to merge together key value pairs from parallel JSON expressions. To do that we would have to pass both JSON expressions to some JavaScript Object merging function, such as Object.assign(...)
. Explicitly calling a merging function would work, but that would clutter-up our source code, especially considering that the nature of developing with React requires merging objects often. So instead we’re going to go with the new and much less cluttery EcmaScript spread syntax, which is denoted by ...
in the input logic, and transpiled to the standard Object.assign(...)
in the output browser logic by Babel. The only slight problem that remains is that so far (at the time this is written), the spread operator has been standardized only for the plain indexed arrays [ ... ]
, but not JSON Object hash maps { ... }
that we’re working with here. However, there is a Babel plugin we can install to add spread syntax support for object literals as well, which will allow us to use this syntax to merge our JSON expressions.
The work to be done in this step is similar to the work done in the earlier Preliminary preparation section in this tutorial, as well as that done in the previous tutorials on setting up the basic app infrastructure, in the sense that we’re going to be installing NPM packages for Babel and tweaking Babel configuration. However, we did not need the spread syntax before.
The NPM package for this is babel-plugin-transform-object-rest-spread
(It confusingly includes rest
in its name because it also adds support for rest destructuring, that also uses 3 dots in its syntax, but is the opposite of what spread does.) I installed it as a dev dependency with the command npm install babel-plugin-transform-object-rest-spread --save-dev
, and committed the changes to package.json
as 4311b26cbb8398b3884c3165d8d8ffcac595b416 test page
.
(Obviously if using the package.json
after this change, the package can be installed with npm install
.)
Installing the plugin by itself will not make Babel use it, it is necessary to update the Babel configuration in webpack.config.js
to tell it to do so. I just made these changes in the commit 891b4eaf94e15d97ccafebc1b6c30170d7e1b0fc test page
.
Now that we added support for the spread syntax, we can go on to the next step that depend on it.
So as discussed in the 1st paragraph of (4.B), I’m going to separate the CSS style padding: '1em'
from objStyleCommon
and into a separate object I’m going to call objStyleContent
. I will then have the option of using the spread syntax to merge both of these objects together into a single <div>
, or to pass them separately into different <div>
s depending on how I want to structure the widget I’m rendering. So in case of the 1st widget with the static text Hello! I'm a React app!!
I will combine them together, whereas for the 2nd widget with the text and the separate <button>
I will apply the former to the whole widget, and the latter only to the <div>
with the text. The padding will then separate the text from the <button>
, but since the rest of the styles will still be applied to both widgets, they will still have a consistent appearance.
And I just made this change in the commit 8366171cce77475b38e7c3daf31513eabe009519 test page
. As can be seen, there’s now a consistent padding around the text, but the Change text...
<button>
is uncomfortably close to the border. I will fix that in the next step.
An important detail to notice in this step is that I’m still using the curly brackets { }
and the double-curly-brackets {{ }}
to pass data to React for rendering. I’m using single-curly-brackets here — <div style={ objStyleContent }>Some text here....</div>
, and double-curly-brackets here — <div style={{ ...objStyleCommon, ...objStyleContent }}>
. As before and always, the outer curly brackets denote that we’re embedding a value inside JSX, whereas the inner curly brackets in the latter case denote that we’re composing a JSON expression right inside our value expression. I combined the outer and the inner brackets together because that looks cleaner, but a more easier to read variant of that latter case would be <div style={ { ...objStyleCommon, ...objStyleContent } }>
. Here I’m composing a JSON expression { ...objStyleCommon, ...objStyleContent }
that just happens to be nested inside the curly brackets that are part of the JSX syntax for embedding values. Notice the spread syntax I’m using with this object literal to create a new object that combines the key value pairs from the objects objStyleCommon
and objStyleContent
, thereby combining all the CSS styles stored in these objects, and applying them to the container <div>
of the 1st widget.
As noted in the previous step, after removing the CSS padding
from the 2nd widget container <div>
, the “Change text…” <button>
“kissed” the widget border. So in this step I’m going to add CSS margin
to the <button>
to fix that. The commit for that is ac61e8d56b44ee8de1c6b571a62f92aa4981395b test page
. The <button>
is now an appropriate distance away from the border.
Now that a widget with the structure of a content <div>
with a <button>
underneath has been created, it can be used as a template to create a new widget with a similar structure. Whereas the 2nd widget is intended to allow the user to edit some text, I decided to make this 3rd widget to allow the user to have a list of items that they can add to, modify, and remove. The list of items will be implemented via a <ul>
with nested <li>
s inside the content <div>
, and it will have 3 <button>
s with captions “Add new item…”, “Edit item…”, and “Remove item…”. Again, a more-optimum user experience would be to have little custom mini-widget controls be associated with each list item for these operations, but such a mechanism would be overly complex for the concepts covered in this tutorial.
And I just created the new <ListWidget>
with the commit 85dd785554e8287ac79b1bbee9663b5fee3734a7 test page
. There are now 3 widgets of varying complexity. Since so far I’ve only coded-out the widget structure and not the functionality, the buttons don’t yet work, so that’s what we’ll move on to next.
It can also be interesting to look at the current test page with the React Developer Tools extension. As can be seen from the screenshot above, the extension is detecting all the React widgets on the page. Also notice that the root container has a prop named children
which is an array 3 elements long. Those 3 elements are the 3 widgets nested in the root container. The nodes specified between the opening and closing tags of a React component get stored in this array referenced to by the children
prop.
If we drill-down the node representing the <ListWidget>
, we’ll get to see its current structure. The readout indicates that this widget is not an aggregate of other React components, and it also does not contain any children
specified between its opening and closing tags. Indeed, it was rendered with just the single self-closing tag <ListWidget />
.
Keep in mind that there is a distinction between a widget being an aggregate of other widgets, and having children widgets. The former are specified in the JSX markup inside the widget, whereas the latter are specified between the widget opening and closing tags outside the JSX markup of the widget. So there are 2 kinds of “children”, internal children, and external children. Only the child nodes in the the latter case (the external children) get appended to the children
array.
Also notice that the CSS styles specified in JSX via JSON objects are still represented as JSON objects.
The regular Developer Tools Elements tab still contains just the regular HTML markup that the JSX was rendered into.
Looking at the source code webpack_in/entry.jsx
up to this point, we can see a serious problem in that the <button>
JSX markup and the associated CSS style being repeated in the logic 4 times. This means that if we, say, wanted to add an additional CSS style, we’d have to add it in 4 places, and this code bloat would only get worse for each new button added. So I’m going to extract the <button>
logic into a new widget unsurprisingly callled <ButtonWidget>
. I just did that in the commit 08a33ec32356c200dc1b2957782a9f95a34ae870 test page
.
The new test page looks the same as the previous, but the underlying React structure has changed as observed in this latest screenshot. We now have a new widget <ButtonWidget>
that’s nested once inside <TextWidget>
and 3 times inside <ListWidget>
. So not only are we now having a multi-widget React app, but we’re also having a multi-nested-widget React app! Efficient React development involves building larger and larger widgets by combining and nesting the smaller widgets.
Another big takeaway from this step is this caption
prop, that has been detected by the React Dev Tools. This is similar to the children
prop in the previous step. However, wereas the children
prop was generated automatically by the React framework, the caption
prop was explicitly specified in the JSX markup as the desired caption for that button. If we inspect other <ButtonWidget>
s with the React Dev Tools, we’ll notice that each one of them has a different value for the caption
prop that corresponds to its own rendered caption.
This leads us to another major concept in React development, which is the props
. React props
is an associative array (a JavaScript Object
) of key value pairs, that can be passed-down to any React component via the JSX expression used to render it, and is normally intended to pass various configuration parameters down to the component, to direct it how it should render itself and behave on the page. In the case of the <ButtonWidget>
in the screenshot above, the prop we’re looking at is caption
, and it has been passed-down from its parent widget <ListWidget>
as a string with the value of “Remove item…”. The line that did this reads <ButtonWidget caption="Remove item..." />
The widget then dutifully used this prop to render the <button>
caption, as it was programmed to do. If the value passed in that prop was different, it would render that other value instead, as it has for the other buttons.
Each React component can access its props
as a regular JavaScript Object
from within its member method using the reference this.props
, and this logic can be embedded inside a JSX expression. For the <button>
JSX markup it looks like this — <button style={{ margin: '1em'}}>{ this.props.caption }</button>
. As can be seen, the curly brackets are used to denote that the value is being embedded inside a JSX expression. (React also takes care to encode any HTML tags that may be present within the value to prevent a dangerous injection of raw HTML.)
Another major concept is that React will cause any widget whose props
have changed from above (in its external JSX markup) to be immediately re-rendered in the DOM. This way the widget will always reflect the most latest state of its props
. But there’s a caveat — while the props
can be changed from the outside of a React widget, they should never be changed from the inside. React was not built for such a mode of operation, so that kind of change will not be treated by the framework as a real props
change, but eventually could get rendered-out anyway with unpredictable results. React is based on the assumption that no widget will ever change its own props
, and so, no widget ever should; however, widgets are free to use or ignore the props
passed to them according to their re-programmed logic.
So never do this.props = myWonderfulNewProps;
from any method of any React component. Other ways to control a React widget state and behavior will be covered in later parts of this tutorial.
As discussed in the previous step, any React component can potentially receive any kind of props
; however, receiving certain incorrect props
, or receiving none at all when they are expected, can cause some React components to behave unpredictably depending on how they are internally coded to use those certain props
. For example, if no caption
prop was specified to a ButtonWidget
in the previous step, then its HTML <button>
s would get rendered with a very weird appearance without any kind of caption, and there would be no Exception
thrown, no error, and no generated warning of any kind. The user might not even notice the problem until they needed to find that button.
Thankfully, a special mechanism has been developed, to be used in conjunction with React, to allow automatic detection of scenarios when incorrect props
are passed to React widgets, which would then cause a runtime warning to be emitted. (Such a warning could then be easily seen in the browser JavaScript console, and could also be detected in the course of unit and integration tests, which would alert the developer.) Originally this entire mechanism was built into React, but since version 15.5 it has been partially extracted into a separate NPM package to allow its use with other frameworks as well.
In this tutorial we’re using React major version 16, and so since the mechanism has been partially extracted into a separate package, the first step to work with it is to install that package, which is prop-types
. I just did that with the commit 1f72ad2ac0210b64a07d103d33f7175ff528ae2a test page
. As always, after adding packages to package.json
it is necessary to run npm install
to install the added packages.
I added the actual validation with the commit 7ac62909045ba80a088034c0c9cc1682b44d6d65 test page
. Taking a look at the commit diff, we can see that I had to explicitly import the package PropTypes
, and that then I specified the props
to be validated with the field propTypes
that I set on the ButtonWidget
object (that contains the widget implementation). From this point on, React will read from this field everytime new props
are passed, and verify each prop
listed in this object. (For now there’s only one field listed, but this will shortly change as we continue with this tutorial.) The validation itself is specified as the value corresponding to they key name of the field. In this case, the value for the key caption
is PropTypes.string.isRequired
, which means that the field caption
must be of type string
and must be specified in the widget’s external JSX markup. (This is just a simple example, but some other supported validations are listed on the prop-types
package page.)
From this point on, passing-in a non-string for the caption
prop, like a number, will result in a warning of Warning: Failed prop type: Invalid prop `caption` of type `number` supplied to `ButtonWidget`, expected `string`.
, while not passing-in that prop at all will result in a warning of Warning: Failed prop type: The prop `caption` is marked as required in `ButtonWidget`, but its value is `undefined`.
Up to this point we only laid-out the static structure of our widgets, but not implemented the dynamic functionality. It’s now time to start on that as well. We will begin with the <TextWidget>
because it is simpler, and then we’ll continue to the more complex <ListWidget>
.
Our goal is to allow the user to change the hard-coded text “Some text here….” to anything they want, which is obviously impossible as long as it remains hard-coded. The first step to making it “soft-coded” is to convert it to become a mutable value of some variable, inside which it can be easily changed.
We could convert it into a props
field just like caption
, but the problem with that is that as discussed above, this.props
should never be changed from inside a React component, and we want the <TextWidget>
to be smart enough to autonomously update its own state when the user wants to change the text. So we need to find a way to change the state from the inside. And in fact there does exist such a way, and unsurprisingly, this concept is called the React widget state. Also unsurprisingly, this state data can be accessed from inside any React component member method with the expression this.state
, analogous to this.props
.
There are 2 ways to change this.state
member:
this.state
directly to point to a new Object
.this.state
indirectly by calling the method this.setState(...)
with a new new Object
.
This distinction is very important, and the former is only appropriate for logic within constructors, while the latter is the only proper technique for logic within (or called from) non-constructor methods. The reason for this is that just like with a this.props
change, the React framework will also re-render the widget upon a this.state
change, but the only way for React to know that this.state
has changed is if it was changed via a call to this.setState(...)
, which triggers the appropriate internal React event handlers needed to register the change. Conversely, when we’re in a constructor, that means that the widget has not yet been rendered, but will certainly get rendered in the near future with whatever this.state
is pre-initialized to up to that point, and so forcing a premature render with this.setState(...)
when it should happen anyway would be superflous, and contrary to the React component lifecycle that mandates that certain preliminary things be done first before any widget is initially rendered.
Therefore, when we want to pre-initialize the widget with a certain state, we should do it by changing this.state
directly from inside the constructor, and when we want to modify the state some time later in its lifecycle after it has initialized, we must do it indirectly by calling this.setState(...)
.
So I just added a constructor
method to the widget <TextWidget>
with the commit 07d74e9dc273f089efadda339b75c60b4720bc36 test page
, and then pre-initialized this.state
to the initial text, and embedded the value of this.state.text
in <TextWidget>
‘s JSX markup with the commit 89ecb689548b7de4a87530d84357d7653fd64b09 test page
.
As can be seen in these commits, the expression I used to declare the widget constructor is constructor(props) { ... }
, and then I immediately used that props
parameter to call super(props);
, and then did not use that parameter again. Even though props
is not used anywhere else in this logic for now, this is all just part of the mandatory React boilerplate for writing a constructor
method.
As can be seen from the test page for the commit 89ecb689548b7de4a87530d84357d7653fd64b09 test page
, even though the underlying implementation of how the string “Some text here….” is initialized has changed, the app still looks the same. However, if we open the React Dev Tools, we’ll see that the text
state
field is now shown for the widget with its initial value under State.
Furthermore, since React Dev Tools allows editing state
and props
fields on the fly, we can just click on the initial value rendered in React Dev Tools, it will change to a text field and let us type-in whatever we want, and that will update our widget state, which React will then re-render, as shown above. This is a nifty little feature for experimenting with how our app will look with different values and configuration settings.
So in the previous step we refactored <TextWidget>
so that the text displayed could theoretically be changed, and we were able to change it via the React Dev Tools, but now we want to allow the user to modify the text by clicking the Change text… <button>
. I just did this in the commit f25a9fb0169a3c52c92b9f1f14efba9147872f51 test page
, where the Change text… button is actually working. As can be seen from the commit diff, this work can be broken-down into 2 main parts, which in turn can be broken-down further into sub-parts.
Add a way to detect <button>
clicks via an event handler.
onClick
prop via <TextWidget>
down to <button>
so that an event handler function can be passed on to the <button>
to be called when the user clicks it.propTypes
React validation for the new <TextWidget>
prop onClick
, to make sure that it is specified as a function
and not some other type.Implement the event handler from (1) to prompt the user for the new text, and update the widget state with the entry, which, as discussed previously, will cause the React framework to immediately re-render the widget to reflect that latest state, thereby displaying the text entered.
prompt(...)
, which will cause the browser to raise a modal prompt dialog, to query the new string from the user, passing-in the old value of this.state.text
as the default.prompt(...)
returns null
).this.setState(...)
with a new Object
containing the field text
with the user entry.
A particularly crucial take-away from this step is the expression I passed to this.setState(...)
. Rather than calling this.setState({text: strTextNew})
, I called this.setState({...this.state, text: strTextNew})
. For now, both variants would work the same; however, if additional state
fields were added to this widget in the future, then they would get cleared-out if I used the former variant. Such a scenario would present itself as a new regression bug, and would break functionality for the users in that any other data associated with the widget state would seem to disappear upon changing the text. The latter variant leverages the spread syntax previously used in this tutorial to merge together fields corresponding to CSS styles stored in different objects, but this time to merge-in any other fields besides text
that could be present in the old state
Object
that we want to propagate unchanged to the new state. (Technically the spread operator merges-in all the fields, and then we overwrite those fields we care about by explicitly specifying them underneath.) By considering this detail now, even though for now there are no other fields to merge, we’re making our source code more reliable by fortifying it against potential future regressions. This also demonstrates how useful and versetile the spread operator is for developing React-based apps. We’re going to keep using it extensively for implementing React state changes.
So the functionality of the simpler <TextWidget>
has been implemented, and now it is time to move on to the more complex <ListWidget>
.
As with the former, the first step with the latter is to move the hard-coded initial data into the widget state, and to modify the JSX markup to render it from there, I just did this with commits a33e37556582507e71fa15e1e13c0b6fc711ec29 test page
and d835e40b4cc3746251a1bf83eb1ba7233857ef3e test page
.
The former commit a33e37556582507e71fa15e1e13c0b6fc711ec29 test page
is just like 07d74e9dc273f089efadda339b75c60b4720bc36 test page
for the <TextWidget>
, while the latter d835e40b4cc3746251a1bf83eb1ba7233857ef3e test page
is somewhat similar to the corresponding <TextWidget>
commit 89ecb689548b7de4a87530d84357d7653fd64b09 test page
, but more interesting. This is because whereas in <TextWidget>
we only had to worry about one line of text, the <ListWidget>
has to support an arbitrary number of lines of text. So whereas for the former we could store our data in a simple string text
, for the latter we have to use an array of strings that I called items
.
Furthermore, whereas a string can be rendered easily by just embeddeding it directly into JSX markup, rendering an array (or any other non-primitive type) in this direct fashion would not produce pretty or well-readable results. To render an array properly, each element must first be mapped into its corresponding JSX markup, and it is this translation of the array into JSX that would get rendered.
This element-specific JSX markup technique allows custom styling of each element based on its data structure. This mapping is typically implemented leveraging the JavaScript Array.map(...)
function. Using this function involves passing it another function, a callback, which will get called by the former exactly once per each element, with the actual element being passed-in as the 1st parameter, and the expectation that the callback returns a translation of that element, thereby “mapping” it to some other data representation, and the element’s index passed-in as the 2nd parameter. Using these 2 parameters, I translated / mapped each element in the this.state.items
array into the JSX markup for the element’s string value being wrapped inside <li>
tags, so that the browser will still interpret it as a list item in the same way that the items were originally hard-coded before this change.
The test page for the commit d835e40b4cc3746251a1bf83eb1ba7233857ef3e test page
still looks the same as before, as only the underlying implementation has changed. However, inspecting the app with the React Dev Tools, as shown in the screenshot above, now presents the items
array in the widget state object being composed of the strings for the 3 initial items.
However, switching to the Console tab shows a weird new warning from Redux — Warning: Each child in an array or iterator should have a unique "key" prop.
.
This warning has to do with a very important React / JSX concept regarding rendering lists of items. The concept is that for better efficiency, React prefers to track which item in the list corresponds to which rendered markup. This way, when say, only one item in the list changes, React will not need to re-render the whole list, but only that specific item. And when an item is added or removed, React will only need to add or remove the corresponding DOM node.
So then what would the mechanism to enable React to correlate list items with their corresponding DOM nodes be? — It is quite simple, and is enabled by the developer through the widget’s internal JSX markup, by assigning each item a unique identifier via a special key
prop. When internal React logic receives the array of JSX markups corresponding to the items in the list, it will relay this unique identifier into the Virtual DOM for each item, and will keep tracking it this way. I just assigned such a prop with the commit 83e7ace269c356dbf293b7787775e1ccad7a3813 test page
.
Inspecting the app with the React Dev Tools after this commit reveals that each <li>
list item in the <ListWidget>
now has a new key
prop with a unique identifier. In the current implementation, this identifier is just the item’s array index. The previously-seen React warning regarding key
s is no longer be emitted.
Facebook covers this topic at https://fb.me/react-warning-keys. One of the things that Facebook says, is that “When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort”, which is exactly what I did here, because I had nothing better to use. Facebook does not recommend this, because array indices will shift when items at lower indices are inserted or removed, and that will cause React to re-render all items at the higher indices.
Ideally what we want to use as a stable unique ID is not the item’s array index, but a counter of how many prior items were added to the array before the latest item was added, which I will call the item counter. Such an identifier will always be equal to the index when items are only added but never removed, as is the case right now before the removal functionality has been implemented, but will diverge from the index in the alternative scenario. Since so far we’re only implementing the functionality to add items, index as the key
will work fine for now. After that, we will need to refactor the <ListWidget>
logic to generate and use the item counter.
This step is analogous to implementing the Edit text… functionality for the <TextWidget>
. We have the widget structure and JSX rendering logic complete, now we just need to implement the onClick
handler for the Add new item… <ButtonWidget>
. I just did that with the commit 74cf9b3256b34d3a5af231427d518553978f1448 test page
. The implementation here is very similar to that for the Edit text… functionality:
onClick
event handler as a prop to <ButtonWidget>
, to be called when the user clicks the button.prompt(...)
to raise a modal dialog box to query the user for the name of the new item, passing a string of Item X
, X
being the total number of items so far + 1, as the default text for the entry.this.setState(...)
to update the widget this.state
with a new object that includes the newly-added item as the last item in its items
array.
A particularly interesting detail about this commit is the nested use of the spread operator …, to not only merge all other state
fields into the new state (if they exist), as was also done in the <TextWidget>
, but to also merge the previously-added items into the new items
array before the newly-added item. This is a reminder, that any time we want to change any data in the React state, we must create a new instance of each object that wraps around that data going from the leaf node up to the root node, and bulk-merge all the items that are not changing from each old object into the new at every level.
As can be seen in the screenshot above, the app currently has a CSS problem, in that items of different-lengths will appear at varying distances from the bullet points due to incorrect alignment.
I just fixed that with the commit bebcb076d4cb9016f775c4ed09fa1415e6125bea test page
.
As discussed in the previous section, we are currently using the item array index as its key
prop when listing items, and this is a problem and is discouraged because array indices will shift when items are removed. Therefore, we can’t implement item removal functionality until we refactor the previous implementation, to generate and use the item counter instead. We need a unique identifier for implementing item editing functionality as well, and even though the index would work just fine, it is best to migrate to the item counter first, so that the logic for both implementations uses the same kind of a unique identifier for the sake of consistency. And so, we’re moving on to implementing the item counter as the unique identifier!
In order to use the item counter as a unique identifier, we must first keep track of the global item counter in the React state. I just added it with the commit 19d01ff41d0a482770a1ca5fa781fd12eb2b596a test page
.
The app still behaves in the same way, but if we inspect <ListWidget>
with the React Dev Tools, we will see that its React state now has a new root field total_added
. This field is keeping track of the global item counter, and gets incremented every time a new item is added. As can be seen from the screenshot, 2 new items were manually added after the initial default 3, giving the counter its current value of 5. This counter can only go up, never down, so it will always provide a fresh number, even when items added previously are removed.
The <ListWidget>
React state schema is still relatively simple, just an array and a counter, yet, the associated logic is already growing needlessly convoluted. The state data is being filled in 2 places — in the widget constructor, and in the event handler for the Add new item… button. The former routine hard-codes the whole initial state with static data, while the latter routine calculates what the data values should be at the time it is executed. For example, the former routine initializes the total_added
counter to the specific value of 3
, while the latter does not set any specific numeric value, just increments whatever numeric value is already there.
The use of hard-coded static values in the former routine, and delta logic in the latter routine, is an opposing design inconsistency, which is needlessly confusing for us developers having to maintain this logic. But our app still works because the inconsistent routines still produce consistent output in accordance with the state data schema. However, if we continue building on top of this inconsistent logic, then the extra-confusion will eventually cause our imperfect human minds to erroneously omit something, or forget to double-implement some crucial detail across both routines. That would then be an introduction of not just another design inconsistency, but of a new calculation inconsistency. And a calculation inconsistency would be a lot more serious, as it would break the so-far consistent output of our routines, thereby causing the app to behave inconsistently, or in simpler terms, prevent the app from working correctly. So even though our app still works for now, if we continue with this crappy logic, it may not work for long.
So to avoid ending up with a crappy broken app, we must get rid of the crappy inconsistent code as soon as we can. The longer we keep and build on top of bad code, the longer it will take and the more difficult it will be to clean it up later. We don’t want to calculate the values for the same fields in multiple places, so once again, we’re going to adhere to the DRY principle. So in this situation, we are simply going to move the logic for mutating the state to add new list items from the Add new item… button event handler into a separate utility routine, and then we’re going to leverage that same routine from both the event handler and the constructor.
We’re no longer going to hard-code our initial data directly into the constructor. From this point on, we will use the constructor to hard-code just the bare / empty React state that corresponds to the pure schema, while all the data calculations will be done within separate utility routines that we can call from anywhere else. This way, there will be a single consistent place for every data value calculation we want to do, and this eliminates these confusion-inducing / bug-inducing inconsistencies.
And I just made this change within the commit 5bd1dc34c858b3ff7939df405471bab6ce700021 test page
. The new method _mutateStateToAddItem(...)
will now be used to add new list item to the state. I prepended the method name with an underscore to indicate that it is not intended to be called from outside the widget. The method is defined as nested within the constructor, but its logic is directly within itself, not the constructor. Notice that I’m still initializing the widget state to the 3 default items — Item 1, Item 2, and Item 3, but this time by calling the new method 3 times rather than declaring the items directly. The constructor is now being used to directly store only the completely bare empty state representing the data schema, that is pointed to by the new variable objStateEmpty
. Our logic is now decent enough to continue building upon.
After implementing a global item counter, we want to leverage the value of that counter at the time it is incremented for a new list item as that item’s unique identifier. To do that, we need to be able to associate it with the list item, which is unfortunately is not yet possible with the current state schema.
Specifically, as can be seen from the screenshot above, each list item in the items
Array
is currently a simple String
storing the caption. This is a problem, because this scheme does not allow storing additonal data for each item, and we’re going to need an additional field to store each item’s unique identifier. Storing multiple fields for each item means that the elements of the items
Array
can no longer be just basic String
s, but must become Object
s / hash-maps that can have multiple key/value pairs / sub-fields. The caption will become just one of the sub-fields. I’m going to assign it to a sub-field I appropriately named caption
. The commit for that is e1f5345347c59ae6f50bbe0a5ed60446df6960d9 test page
.
The app still renders the same; however, inspecting the ListWidget
with React Dev Tools shows that its state data schema now has Object
s as elements of the items
s array with sub-fields caption
.
The previous step has allowed us to now add a new sub-field for each item to hold its unique key / identifier. As discussed, we will now take the value of the global item counter at the time each item is added, and use that as its static key. And I just made this change with the commit c8a1e79d9c58312ceab6be1f3021ebd4e7a9a6ac test page
. I inserted the logic to add new field id
into the routine _mutateStateToAddItem(...)
that is called whenever a new item is to be added.
Inspecting the app with the React Dev Tools now shows that there’s an id
field for each object in the items
array. This id
can now be embedded in the JSX as each item’s key
.
So now that we have the static unique identifier for each item, we can use that rather than the index as the items listing JSX key. I just did that with the commit 9e5a0be8e285e4925b5b86043a8bdde53d9f9bee test page
. As can be seen from the commit diff, it is a very small change, but only possible thanks to the prior work in this sub-procedure. Since we are now using the item counter as the unique JSX key, we can now move on to implementing functionality for item editing and removal.
So far we have implemented the functionality to add new items, which is actually pretty trivial. The functionality to edit and remove items is more complex, as it involves providing the user with a way of selecting which items they want to work with, and keeping track of that selection in the widget state. This will require refactoring the <ListWidget>
structure implementation and the state schema.
The most straight-forward way to give the user a way to select which items they want to edit or remove is to use checkboxes. We will render a check box for each item in place of its current bullet point, and add an event handler to update the <ListWidget>
React state
to indicate which item has been selected. (A better user-experience would be to have tiny buttons associated with each item for these operations, but that would involve more emphasis on UI design, which is outside the scope of this tutorial.) Then, when the user clicks either Edit item… or Remove item…, we’ll be able to know from the React state which item we need to work with.
Since adding checkboxes to list items involves customizing how they are rendered, a good first step is to extract the associated JSX markup into a separate widget, which I’m going to call <ListItem>
. I just did that with the commit f3a9c63bc267c07bea7f701ffac256ca1e5e7f56 test page
.
The app still looks and behaves the same, but inspecting it with React Dev Tools shows that the widget <ListItem>
is now being rendered for each list item, with the item text being passed to it via the prop caption
.
Adding a checkbox for each <ListItem>
is rather trivial, and I just did it with the commit 63f3ba834c6df52bffcdd6669b3a5d33535926e0 test page
, and got rid of the <ul>
bullets with the commit 06f4dfb8e6c1415a65669f6a315c82fe441f05e5 test page
.
So the checkboxes are now there, but checking or unchecking them does nothing.
So the next step is obviously to track the state of our checkboxes in the <ListWidget>
React state.
We need to modify the <ListWidget>
React state schema to keep track of not only each item’s unique id
and caption
, but also of whether or not the user selected it via its associated <checkbox>
. We can do this using an additional sub-field on each item object. I’m going to call this new sub-field is_checked
, and I just added it with the commit 459126ea591851e337864d31a7599b2a58ef4561 test page
. This is going to be used as a boolean field, with the value of false
to represent the unchecked state, and the value of true
for the checked state. I pre-initialized the field to true
just to make it easier to see that the React state is being properly propagated to the rendered checkbox when that logic is implemented.
The actual UI checkboxes are not yet updated to reflect the is_checked
state because that logic has not yet been implemented; however, the field itself, being set to its default value of true
can now be observed in the React Dev Tools. Apparently when React Dev Tools sees a boolean value, it helpfully inserts its own checkbox in front of it to allow the developer toggle it if they want to.
So as discussed in the previous step, up to now we have the field is_checked
in the state, but we are not yet updating the actual UI checkboxes based on its value. So I just commited the change to do that as b76d71b2c3c059885517608c32955be2217a7181 test page
. This commit adds a new prop isChecked
to <ListItem>
, and modifies <ListWidget>
to pass it as the value of is_checked
from the associated item in the React state.
If we inspect with React Dev Tools now, we can see this newly-added isChecked
prop, and we can also see that all the checkboxes are now checked. What may seem unusual, is that clicking on any of the checkboxes will not uncheck them. It is as if they are frozen, which in fact they are.
The reason why the checkboxes are frozen is because they are coded to strictly reflect the value of the isChecked
prop, which in turn is coded to strictly reflect the value of the is_checked
field corresponding to that item in the React state, where all such data is presently hard-coded as true
. The checkboxes will remain frozen until the corresponding React state fields change, but the logic to do that has not been implemented yet up to this step.
This gets us into a crucial concept regarding the React philosophy, and why React is named React, in that React embraces Unidirectional Data Flow. This means that the data can flow in only one direction — from the React state, through props, through JSX, and on to the DOM, but never back from the DOM. This improves development efficiency and reduces opportunity to introduce bugs in contrast to the pre-React DOM-based toolkits. Had we been using some pre-React DOM-based toolkit, clicking on a checkbox would have immediately changed its visible state, and then we would have been expected to read its new state from the DOM, and update the state of our app according to that state. This would require a lot more logic to implement, and if the implementation was not entirely correct, result in useability bugs due to certain edge cases where the widget would end up in one state, and the app in another. Under the React philsophy and methodology, to change the state of a widget based on user input, we must first listen for specific user-input events, and then, for each event, update the React state according, which in turn will cause the widget to re-render with the updated state value embedded in its JSX markup, thereby appearing to the user in its new desired state. Essentially, when developing with React, if we don’t want our app to be read-only, then we must intercept and handle / react to, the user input, hence the name — React. We must react to React.
So when we explicitly pass what we want to be a user-editable value in the JSX markup for a DOM input, then unless we want that input to be read-only, we must also pass an event handler for user interaction with that input, and implement it to update the React state accordingly. If we don’t pass that event handler for a <checkbox>
, React will emit the warning Warning: Failed prop type: You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.
. It will emit a similar warning if we make this mistake with another type of an input field.
And so in this step we’re going to implement this user-input event handler for our <checkbox>
es so that it flips the corresponding is_checked
field in the React state, so that the checkboxes work as expected and we no longer get this weird React warning. The user-input event we’re going to be listening to on the <checkbox>
, same as the name of the prop for it, is onChange
. We’re going to pass the event handler via this prop to <checkbox>
from <ListItem>
‘s internal JSX markup. The event handler itself will not be in <ListItem>
, but in <ListWidget>
, because the React state associated with the checkboxes is stored inside <ListWidget>
. The event handler will be passed from <ListWidget>
to <ListItem>
via a new prop onChangeChecked
. And the commit for that is 9ebd9ca822c758e146804d3c6211843dcc1b5382 test page
.
Checkbox state can now be changed by the user, and this is reflected both in the <checkbox>
widgets, in their props
, and in the React state. We are now ready to move on to implementing the user functionality for item editing and removal.
Item editing and removal is going to be facilitated by clicking the buttons Edit item… and Remove item…; however, even though these operations can only be applicable when at least 1 item is selected, in the implementation up to now these corresponding buttons are enabled all the time. This potentially confuses the user into thinking that these buttons could be applicable in a situation when they are not. To minimize this confusion, we want to disable these buttons when no items are selected. Furthermore, we only want to enable the former when only 1 item is selected, as only 1 item can be edited at a time, while the latter needs to stay enabled when 1+ items are selected, since multiple items can be deleted at once.
And I just made this change with the commit 9b0124fd4776fe28b995aff6c29d738e22439e34 test page
. The change modifies both the <ButtonWidget>
and the <ListWidget>
. Specifically, it adds logic for a new prop isDisabled
to be passed from the latter to the former. The commit also adds logic to the latter to determine how many items are selected by appending them to an Array
arrItemsSelected
, and then using the length
of this Array
in the JSX markup to pass isDisabled
set to true
to the edit button whenever the number of elements selected is not 1, and to the remove button when it is 0.
Both buttons are disabled when no items are selected. Notice how the isDisabled
prop is true
for both.
Both buttons are enabled when only 1 item is selected.
Only the Remove item… button is enabled when multiple items are selected. However, the button caption is no longer accurate, as it uses singular noun when multiple buttons would be removed. This grammatical inconsistency can be considered to be mildly confusing to the user, but it is easy to mitigate.
We can modify the JSX markup to render Remove items… rather than Remove item… when more than 1 item is selected. I just did this via the commit 894c565bbdaf5c935b50ddbe7790671b8c1c21ca test page
.
Noun is singular when nothing is selected.
Noun is still singular if only 1 item is selected.
Noun becomes plural when multiple items are selected.
Now that the buttons state and captions reflect the proper application React state, we can finally move on to implementing the actual button functionality. And I just implemented item editing with the commit 5871aa5614ec43de1ce9a1a449d4693943dc9c86 test page
.
The nature of this implementation is such:
Adds a new onClick
handler to the Edit item… <ButtonWidget>
, in which it…
takes the 1st (and the only) item from the arrItemsSelected
Array
,
…and passes its current caption as the default text to the browser API prompt(...)
function, to query the user for the edited caption.
Relays the user edited caption entry, along with the ID of the 1st selected item, to a new <ListWidget>
internal method _mutateStateToUpdateItemCaption(...)
,
…which sets a new React state with the entered string.
The newly-edited caption can be seen in the application React state.
And the commit for the item removal functionality is ccb861ec33034027aacc08137bdefa216dd875d5 test page
. This commit:
Adds a new onClick
handler to the Remove item… <ButtonWidget>
, in which it…
Retrieves all the selected items captions from the arrItemsSelected
Array
,
…passes the list of captions to the browser API confirm(...)
function, to raise a confirmation dialog for the user to ask if they indeed want to remove these items, and …
…returns from the event handler if the user clicked Cancel.
Otherwise calls a newly added internal method _mutateStateToRemoveItemsChecked(...)
passing it the old state, which returns the new state with the selected items filtered out.
This commit also makes heavy use of JavaScript template literals`${ ... }`
to properly format text sentences with various gramatical variations for listing a single item vs 2 or more items. For example, just 2 items should be separated by an and but not a comma, while 3 or more items should be separated by commas, while the last 2 also need to be separated by an and.
Up to now I made the items be selected by default, just to make it easier to test the associated functionality as it was being developed, but now that that work is complete, it would be more convenient for the user if the items were not selected by default. And the commit for that is eae4f0808bd5559d59d84a5385ce1de753d75c76 test page
.
This is how the whole application looks like now. The items are no longer selected by default.
So far our multi-widget React app has 3 root widgets of increasing complexity — the <HelloWidget>
, the <TextWidget>
, and the <ListWidget>
. The latter 2 widgets are built as a hierarchy of sub-widgets. This technique of combining hierarchies of smaller widgets to compose ever-larger widgets is very common in working with React, as well as with UI development in general.
We can continue to practice building these multi-widget widgets some more by developing yet another widget, this time for selecting colors via the RGB triplet. The user will be able to type in the numeric values 0 – 255 for the Red / Green / Blue components manually, or by selecting them via elevator-like up / down counter buttons. There will be a preview pane above the inputs, whose background will be filled with the selected color.
As previously, we will begin by laying out the general structure of the new widget with JSX markup. And the commit for that is 2c4292a3ff95b32beea0c9507c90c85c14250f11 test page
.
As can be seen in the screenshot above, the widget has the same green outer border as the other widgets, and inside it is composed of the large centered preview pane with a black border and 3 text inputs below it. The 3 text inputs are intended for the entry and display of the RGB triplet numeric values, while the elevator-like up / down counter buttons to increment / decrement the values have not been implemented yet.
As can be seen in the React Dev Tools pane, the JSX markup for each of the RGB text inputs and the <div>
encompassing it along with its label is being repeated 3 times for each color component. Not only does this result in longer logic, but any additional markup we might want to add to this markup would also need to be repeated 3 times. So therefore, the next obvious step is to extract the JSX markup for a single color component into its own widget.
And so I extracted the JSX markup for each color component entry into a new widget <ColorComponentEntry>
. The commit for that is b0c49bf380d16c35cee474593052ca7e5e10e61c test page
. I pretty much just copied and pasted the JSX markup for a single color component from <ColorSelector>
into the new widget <ColorComponentEntry>
, and then rendered the latter 3 times from the former.
The <ColorSelector>
widget still looks exactly the same, but it is now not just a single flat widget, but a multi-widget composed of 3 sub-widgets <ColorComponentEntry>
. The label for each <ColorComponentEntry>
is being passed-in as a prop label
. Now that our basic widget structure is ready, it’s time to move on to implementing the functionality.
And of course with the nature of React development, the first step in implementing the functionality is to come up with the state schema, and initialize the initial state, which I just did with the commit 7fbb11e00b3118410eb3b027b3ed0798b867de0b test page
.
The state schema now consists of the field color
, with the 3 RGB triplet sub-fields r
, g
, and b
, shown sorted by the React Dev Tools in alphabetic order. The triplet is initialized to 255 for each component, and this makes our initial color pure white.
Of course for now these values are just being set, but not actually rendered. The next step in implementing the user functionality is to render them to the user.
The commit to render the color component values in their corresponding entry widgets is c002e8c647f80fff787c71ab93e284b1df44eec7 test page
. This commit:
Modifies the widget <ColorComponentEntry>
to relay a new prop value
to be rendered as the value within its <input>
field.
Modifies the widget <ColorSelector>
to relay the value from the this.state
to the corresponding <ColorComponentEntry>
via the prop value
.
The propagation of the color component values can now be seen in the React Dev Tools, and obviously within the text fields of the UI. However, the text entry fields are currently frozen just like the <ListItem>
checkboxes were when their checked
attribute was passed-in. As discussed at the time that was implemented, this is due to the Unidirectional Data Flow that prevents DOM inputs from autonomously reacting to user input. Instead, the DOM inputs are contrained to the values passed to them via the JSX markup, and if we want to update their state according to the user input, we must implement special logic to react to that user input to pass-in new appropriate values via the JSX markup.
If we switch to the Console tab, we will see the now familiar React warning Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.
The next step is obviously to implement this onChange
handler, or otherwise the user will not be able to change any of the values from the hard-coded 255
.
Accepting user input for new color component values is somewhat tricky due to 2 constraints:
Value must be numeric.
Value must be within the range [0 – 255].
Handling user entry of invalid values is a tricky situation, because in such a scenario it would be necessary to display some error message, and all that requires extra logic and needs extra space in the UI for the notification string. So I decided to avoid this problem by simply not allowing an invalid entry to be made. This will be done in 2 ways:
If the user types-in a non-digit, that input will be ignored. This way there will be no way to enter a non-numeric value.
If the user types in a digit such that the new entry will be greater than 255, then the first digit will be deleted until the entry becomes <= 255. For example, if the old entry is 234 and the user types in 5, then the resulting temporary value of 2345 will be truncated to 345, and then again to the final accepted new entry of 45.
And the commit for all this cool functionality is eee3928417185ee28cdc100d94f816d9d5f812f2 test page
. The changes are:
New onChange
handler is added to the color component entry <input>
markup. Inside this event handler, the newly-entered value is available via event.target.value
. It is immediately validated against a regular expression composed of all digits by a special guard that aborts the routine if that’s not the case. After that, calling a new recursive function convertValue(...)
will delete the first digit until the new value is <=255. The resulting value will be passed to an event handler in the <ColorSelector>
via…
…new prop onChangeValue
has been added to <ColorComponentEntry>
to allow <ColorSelector>
to pass-in an event handler for when a new user-entered value is accepted.
The JSX markup within the <ColorSelector>
widget has been modified to pass-in the new event handler onChangeValue
to <ColorComponentEntry>
to receive the new user-entered value.
And within this event handler the <ColorSelector>
widget will update its React state for the corresponding color component to the new user-entered value.
Color component values can now be modified by the user. The onChangeValue
event handlers can now be seen in the React Dev Tools being passed from the <ColorSelector>
widget to its <ColorComponentEntry>
ies sub-widgets.
As can be seen in the screenshot above, the color preview is still white even though a non-white color is now specified. The next step will be to implement the functionality to update the color preview pane according to the entered color.
And the logic to fill the color preview pane with the selected color is quite simple. The commit for it is b5c48ade2606d1d8094af2fbde8e10c1c04c83c5 test page
. The new logic:
Extracts the color
field from this.state
using an object destructuring assignment,
and modifies the JSX markup for the color preview pane to pass the color value via the regular old CSS property background-color
with the value specified using the CSS rgb(...)
syntax.
The color preview pane is now being filled with the selected color.
The border around the preview pane is static solid black. Perhaps the app would look better if the border color changed dynamically to the color opposite of that in the preview pane. I just implemented this with the commit ce5f5ab4fa63d83468891f1d74cceae9c4ad6cc0 test page
. The “opposite” color is determined by subtracting each color component from 255.
Looking at the current source code for <ColorSelector>
, it is easy to see that the 3 onChangeValue
handlers contain almost the same logic for updating the widget state. In the commit 7682ec689ac957c80e2feeb1e33f8bef39b6e4a2 test page
I consolidated this repeating logic into a new function updateStateForColor(...)
.
The basic functionality of the <ColorSelector>
widget has been implemented, and the code is in pretty good shape. Now we can make tweaks and adjustments to the functionality for better user experience and convenience. For instance, it would be convenient for the user if they could press an elevator-like counter button to increment or decrement a color component value by a single click rather than have to type backspace and various digits. I just implemented that with the commit 2287c161c46e6928501a147c30a782f7a98da865 test page
. The commit:
Tweaks the <ColorComponentEntry>
widget JSX markup to add 2 <button>
s, one with the caption ▲ for incrementing up, and another with the caption ▼ for decrementing down.
Each added <button>
has an implemented onClick
handler that increments or decrements accordingly.
Each onClick
handler includes a guard that aborts the routine to prevent the value from being mutated outside the valid bounds [0 – 255].
Notice how each onClick
handler gets the initial value from this.props.value
which is the value passed-in from the parent <ColorSelector>
widget, and finally calls the passed-in onChangeValue
handler to relay the new value back to the parent.
The <ColorSelector>
widget now has the elevator-like up / down counter buttons for quick incrementing / decrementing of each color component. As can be seen through the React Dev Tools, there’s a pair of the 2 elevator <button>
s inside each <ColorComponentEntry>
widget.
The latest tweak looks good on the surface, but after playing around on the test page, it is easy to realize that there’s a major defect with the current implementation in that the user has to click a counter button each time for every step. So if say, the user wants to do 10 steps, they’d have to click 10 times, or 255 times if they want to cover the complete range for just one color component. This is serious work that might even cause a repetitive stress injury, and it’s not convenient or user-friendly at all. To make this feature practical, we want to auto-step as long as the user is holding down the button. To do that, we need to:
The commit for that is e5ddcfe83a8460146a7f3845c747b58ffddc2a9c test page
. All this commit does is create a new widget <ButtonForCounter>
that is a proxy between the button
and the <ColorComponentEntry>
widget. The volume of logic is not reduced, but a new level of abstraction has been created in that the <ColorComponentEntry>
widget now does not have to worry about detecting button clicks, but only about counting the value up or down. The purpose of the new <ButtonForCounter>
widget is to accomodate the continuous stepping / counting logic to be added next.
And the commit for that is a02cce358f038c6a2ffeaf386c4f8405c254a422 test page
. This commit modifies <ButtonForCounter>
to:
…add new internal method _onCountStart
that will call the parent widget (<ColorComponentEntry>
) onCount
handler, and then keep calling itself recursively with a 25 millisecond timeout, to propagate the auto-counting until the new class field _flagCount
is set to false
,
…add new internal method _onCountStop
that will set the new class field _flagCount
to false
to stop the auto-counting,
…tweak the JSX markup to listen for when the user presses or lets go of the <button>
, and call _onCountStart
or _onCountStop
accordingly.
Now just a touch of a counter button keeps the value counting, and the color adjusting, without the user having to do anything. This allows the user to observe as the color gradually changes to pick their favorite, which is a great feature to have in this kind of widget!
The only small detail that’s missing is that ideally the counter buttons should be rendered disabled when they’re at the end of their range. I just implemented this with the commit 0a280725033e9254f4fa33dbb3b89e9b1b0db1c8 test page
. This commit:
Adds new prop isDisabled
to <ButtonForCounter>
to allow disabling it.
Tweaks JSX markup for <ButtonForCounter>
to relay the isDisabled
prop to <button>
as disabled
.
Tweaks JSX markup for <ColorComponentEntry>
to render the <ButtonForCounter>
for counting up disabled when the current color component value is 255, and to render the <ButtonForCounter>
for counting down disabled when the value is 0.
Tweaks the counting logic within <ButtonForCounter>
to not count when the widget is disabled.
The elevator counter buttons are now rendered disabled at their limits. The passing of the prop isDisabled
from <ColorComponentEntry>
to <ButtonForCounter>
can now be seen via the React Dev Tools.
The <ColorSelector>
widget is now complete!
The app functionality originally planned at the beginning of this tutorial is now complete!
The app functionality is now complete, but all the app logic is crammed into a single file webpack_in/entry.jsx
. While the functionality is complete for now, sooner or later additional functionality will be desired, and cramming all the logic into one file is just not convenient, and generally not practical. To deem this app complete, we should organize all the logic across separate files.
Not all of our logic is strictly JSX. Some of it is non-JSX EcmaScript. That logic will be slit-out into *.es files, but to support that we must first tell Webpack to transpile those as well. And I just did that with the commit edba721de380806823fe8c45137f9bb029071f34 test page
. The *.es files are intended to be anything > ES5, and they will be subject to transpilation to support ES6+ language features in contrast to the *.js files.
The JSON expressions containing CSS styles are currently used by all 4 widgets of the app. I just extracted them into separate file webpack_in/styles.es
with the commit 0155a626acfacc76cdb341ecab9b9f1f566cb79f test page
.
The <ButtonWidget>
has been extracted into separate file webpack_in/button_widget.jsx
with the commit 59750a035b276b3f48385c408f9dd37c910f21ed test page
.
The <HelloWidget>
has been extracted into separate file webpack_in/hello_widget.jsx
with the commit 0919b00dc9e29212134b4e16de1af38c2683554e test page
. Unlike <ButtonWidget>
it does not need to import prop-types
, but does need to import the local file webpack_in/styles.es
extracted earlier.
The <TextWidget>
has been extracted into separate file webpack_in/text_widget.jsx
with the commit 1f86f44636ed8dbdb86ad0ab7519e8dddf5cba23 test page
. Unlike <HelloWidget>
, it also needs to import <ButtonWidget>
extracted earlier.
The <ListWidget>
has been extracted into separate file webpack_in/list_widget.jsx
with the commit 3d48cfbf469708b0738138117a411d6b00eda456 test page
. Unlike <TextWidget>
, it also needs to import prop-types
. After this extraction, webpack_in/entry.jsx
no longer needs to import <ButtonWidget>
, as <ListWidget>
was the last widget that needed it.
The <ListWidget>
has been extracted into the same file together with its sub-widget <ListItem>
. The latter has not been extracted into a separate file because it is not that large and is not needed by any other logic.
The <ColorSelector>
has been extracted into separate file webpack_in/color_selector.jsx
with the commit 29a06c4d4c9e0c0ad635738d164834fcf3fe2458 test page
. After this extraction, webpack_in/entry.jsx
no longer needs to import prop-types
and webpack_in/styles.es
, as these were only needed by <ColorSelector>
and its sub-widgets.
The sub-widgets <ButtonForCounter>
and <ColorComponentEntry>
have been extracted into the same file together with their parent widget <ColorSelector>
. As with <ListItem>
earlier, this sub-widget logic can stay with its parent widget for now because it is not that large and is not needed anywhere else.
The application logic has been completely distributed across separate files, each of which is not too large and can be modified independently. The file webpack_in/entry.jsx
shrank from over 1000 lines to just 26, with all of the application logic migrated into its dependencies. Keeping the source code organized, small, and compact allows for much easier maintanance.
The application source code can now be deemed complete. The current size of the development build bundle file main.js
is 770 KB, downloaded auto-gzipped as 172 KB. The bundled JavaScript was 715 KB raw, 164 KB auto-gzipped before the app implementation, so looks like the app itself is taking 55 KB.
The large bundle file is obviously impractical for production use, so time for compressed production build!
For this app I’m using the git branch react-multi-widget--production
.
As in my previous tutorial section Minifying React web apps for production, all that’s required is to change mode: 'development'
to mode: 'production'
(or to remove the line entirely) in the Webpack 4 configuration file webpack.config.js
. The commit for this is 4172fe6a28e2730523eb3b454565e25e8509dee8 test page
. And the bundle file is 114 KB raw, 34.96 KB auto-gzipped after this change. That’s a ~ 7-fold reduction from 770 KB. Not bad! The bundled production JavaScript was 101 KB raw size before the app implementation, so now our app is taking only 69 KB.
So we obtained a decent production build using pure React, but let’s see if we might do even better with React’s ultra-efficient clone — Preact!
First, I will just swap Preact for React in the regular development build, and test-out how it will work. For this I will use the git branch react-multi-widget--preact
.
Swapping-in Preact for React requires modifying the Webpack configuration file webpack.config.js
to specify a substitution alias
for React. This has already been done in the previous tutorial section Getting even higher performance and efficiency with Preact but commented-out earlier in this tutorial in the section preliminary_preparation with the commit 183d64e42379b4d86fb901ccb69d80e298c6d21b test page
. I will now do the reverse of that to re-enable Preact with the commit 371bed7b13a43ca2ff6b3ae1c82eefb590e93f0e test page
.
The app still works exactly the same, but now the bundle file is 132 KB raw, 29 KB auto-gzipped. The auto-gzipped download size is even less than with React in production mode!
(Obviously, for a large app it would be necessary to run a full suite of integration tests when swapping the main app framework on the fly, but developing integration tests deserves a whole tutorial of its own.)
Now that we’re certain that the app functionality still works exactly the same with Preact instead of React, it is safe to continue to a Preact-based production build. The branch I will use for this is react-multi-widget--preact--production
.
Same as without Preact, it is necessary to change mode: 'development'
to mode: 'production'
in the Webpack 4 configuration file webpack.config.js
. The commit for this is 2bba85f6d4189d6bee7f108a4b79bc9612af5559 test page
. The bundle file size is now 34 KB raw, 10 K auto-gzipped. It went up from 22 KB raw, 8 KB before the app. This means that the app logic portion of the bundle download is only 2 KB, while the whole bundle requires as much download bandwidth as a small icon!
The example repo is at https://github.com/maratbn/example_step_by_step_minimalistic_react_app and can be cloned and used either to experiment some more or as a boilerplate for a new multi-widget React app. The repo contains commits for this tutorial as well as the previous tutorials on using Webpack 1, 3, and 4, but the commits are separated across different branches. The branches that apply to this tutorial all begin with react-multi-widget
. These branches are:
react-multi-widget
— Main development branch for the app.react-multi-widget--production
— Main production branch.react-multi-widget--preact
— Development branch with Preact swapped-in for React.react-multi-widget--preact--production
— Production branch for the Preact-based build.
If you want to use the example repo for your boilerplate, I recommend you base your project off the branch react-multi-widget
. When you want to deploy to production, fork-off a new release branch, and cherry-pick the production-optimizing commits from react-multi-widget--production
if you’d like to use pure React. If you’d rather use Preact in production for much smaller bundle, then cherry-pick Preact and production-optimizing commins from react-multi-widget--preact--production
instead.
One danger with developing with pure React, and switching over to Preact for production only, is that if for some reason some feature of the app turns out to not work entirely correctly under Preact, this would not be noticed during development. Therefore, I recommend having both React and Preact production builds, and subjecting both to a battery of integration tests before deployment, to see if a particuar test fails under Preact but not under React.
In this tutorial I went over building a multi-widget React web app. Some of the key concepts I covered are:
The philosophy behind React is to reduce logic volume and complexity.
Unidirectional data flow.
No direct DOM manipulation with React.
Productive development setup and workflow.
Divide and conquer strategy of consolidating common logic and extracting UIs into hierarchies of widgets.
Basics of JSX markup.
Use of camelCase when specifying dash-containing CSS properties within JSX markup.
Passing props and maintaining React state, and the difference between the 2.
Inspecting JSX markup, props and state with the React Developer Tools.
Implementing event handlers via props.
EcmaScript language features which facilitate easier merging of one Object
into another for terser JSX markup and state generation, and the associated Babel plugins.
The default “frozen” nature of input elements rendered via React, and the fundamentals of reacting to user input within a React app, and why React is called React.
Developing and refactoring React state schema.
Properly choosing appropriate data for, and specifying the key
prop within JSX markup.
Recognizing common React errors and warnings.
JavaScript template literals.
Splitting large code base across multiple small files.
Transpiling React app for production.
Swapping Preact for React to achieve smaller production bundle.
Enjoy developing and deploying efficient React multi-widget apps!
Copyright (c) 2010-2018 Marat Nepomnyashy
Except where otherwise noted, this webpage is licensed under a Creative Commons Attribution 3.0 Unported License.
Background wallpaper by Patrick Hoesly, used under Creative Commons Attribution 2.0 Generic License.