Build a complete React app, step-by-step with the MERN stack

Intro

What?

This is a step-by-step tutorial that will help you get up to speed with React quickly, and also build a complete app with the MERN (Mongo-Express-React-Node) stack. You'll also learn other tools that you typically use to build an app: Gulp, Browserify, Material-UI and React-Bootstrap.

Why?

Why another React tutorial when there are so many out there? Well, most of them focused on React alone. When I set out to write my React app, I couldn't find any advice on whether or not to use jQuery and how to fit in Bootstrap. And, how about bower vs. browserify? What about grunt? Or gulp? How do I put it all together? Which UI framework is appropriate?

Well, I found out, the hard way. Now that it's done, here it is.

How?

There is nothing that I invented here, it's all a string-together of links to existing resources. You're supposed to read the links (Read), then accomplish some tasks that I assign (Write), and just think about what and why we did stuff (Ruminate). Some folks like @keenavasan suggest taking a peek at the tasks (Write part) before starting to read, or mixing the two. Whatever works for you.

At the end of the exercise, you'd have built a simple CRUD app. I chose a Bug Tracking app for this purpose.

I encourage you type out the code yourself (not copy-paste). This is something I found very useful in the learn something the hard way series of tutorials. Use the github repo of the source code only for comparison, and when you are really, really stuck.

Edit (Apr/06/2016): We now have the source for other variants of this tutorial:

Who?

You're expected to know Javascript and have a fair bit of web app development in any language / stack. MEAN stack is ideal. I am assuming that you have access to a Linux VM or desktop to write and test code, this is the easiest way.

Which?

This was written during my Christmas break of 2015. It's highly likely that when you are reading this in, say, summer of 2016, things will be quite different.

Here are the versions of various stuff used when writing this tutorial:

OS: ubuntu 14.04 LTS

nvm: 0.29.0

node: 4.2

mongodb: 2.4.9

Rest of the stuff: take a look at package.json

1. Hello World

We'll start with a Hello World app, served from a very rudimentary server using Node and Express.

1.1 index.html as a file

We'll take a few shortcuts to get going really quickly. We'll create an html file that we can just open in a browser to get a really simple Hello World running.

Read

Write

Create an index.html file on your filesystem with (almost) the same contents as the quick start.

Replace the scripts build/react.js and build/react-dom.js with the ones from the CDN, so that it looks like the one in the Tutorial.

and with the ones from the CDN, so that it looks like the one in the Tutorial. Open index.html in a browser

Ruminate

Doesn't the HTML string need quotes? No, it's JSX!

Does the browser understand JSX? No, Babel (the third script in index.html) compiles it to Javascript on the fly.

What about React itself? Where's the real React library?

Compare your work with index.html in my Step 1.1

1.2 Serve it up

Instead of opening a html file in the browser, let's create an express/node web-server that serves the index.html instead.

Read

Write

Install nvm and the latest version of Node.

Run npm init and install express as per the Express Installation instructions.. Remember to use --save . We'll be using --save in pretty much all our npm installs.

and install express as per the Express Installation instructions.. Remember to use . We'll be using in pretty much all our npm installs. Use webapp.js as the entry point for the node server when you do npm init .

as the entry point for the node server when you do . Create a simple static file server to serve the index.html, which shall be placed under the directory static .

. Run the server using node webapp.js and point your browser to http://localhost:3000.

and point your browser to http://localhost:3000. You may want to make the installed version of node the default. For example $ nvm alias default 4.2 ) or the next time you enter the shell node will not be in your PATH, or you may get the wrong version.

Ruminate

Use the Network tab under your browser's developer console and checkout the time it takes to retrieve each resource.

Compare: Here are my full set of files and the changes for step 1.2.

2. Organize

In this section, we'll transform JSX into JS at build-time rather than at run-time. This is more efficient, and will also avoid loading Babel in the client (which, you should have noticed, is quite heavy ).

2.1 Split HTML and JS

Read

Write

Create a new file App.js along side index.html.

Move the script contents from index.html and use src= to refer to the new file.

Ruminate

Compare Here are my full set of files and the changes for step 2.1.

2.2 Transform

Read

React Tutorial - Using react from npm (Ignore the parts about webpack / browserify / bower. We'll be manually transforming the JSX files for now.)

Babel - CLI

Write

Move App.js to an src folder.

folder. Install babel-preset-react (locally) and babel-cli (globally).

Manually transform src/App.js to static/App.js . Write a shell script to do this.

to . Write a shell script to do this. Remove the run-time transform.

Add --watch so that changing the source file automatically generates the destination.

so that changing the source file automatically generates the destination. There are innumerable ways of getting this done. reactify was the de-facto till recently. I recommend the manual transform at this stage, since that shows clearly what's happening.

Ruminate

Look at the output generated by the babel/react transform.

What else does Babel do? Consider switching to ES2015, now that we are using Babel.

Compare with my step 2.2 and diff with 2.1. A global install of babel-cli is not visible here, though.

3. Compose Components

The key thing about React is creating and re-using components.

3.1 Use React.createClass

Read

Write

Change the Title of our page to something more meaningful

Change the id of the container where everything is rendered to main

Create a component called BugList which renders just a div with placeholder text. Render this component in the ReactDOM.render() method.

which renders just a div with placeholder text. Render this component in the method. Using minified React scripts hides errors. Use the non-minified version.

Ruminate

React classes must return a single DOM element in their render() function. Why is this so?

function. Why is this so? Compare with my diffs for Step 3.1 and complete 3.1 source.

3.2 Compose Components

Read

React Tutorial - Composing Components (stop just before Using Props)

Write

Create three classes - BugFilter , BugTable and BugAdd , with place holder text indicating that each section is meant for a filter, a table to list all the bugs, and a form to add a new bug.

, and , with place holder text indicating that each section is meant for a filter, a table to list all the bugs, and a form to add a new bug. Replace the placeholder in BugList with these three classes.

Ruminate

3.3 Communicate between components

Read

Write

Create a new class BugRow for a single table row for a bug, which displays one column each for id, status, priority, owner, title of the bug.

for a single table row for a bug, which displays one column each for id, status, priority, owner, title of the bug. Create a bordered table with a header

Add two BugRow instances from within <tbody> of the Bug Table.

instances from within of the Bug Table. Pass the bug attributes as parameters as props from BugList

Ruminate

Why doesn't border=1 attribute for the table work?

attribute for the table work? Think about the use of curly braces { } to step into Javascript world while inside the HTML representation. Is this like <?php or {{}} in Angular.js?

to step into Javascript world while inside the HTML representation. Is this like or in Angular.js? When does one pass parameters to children as props using this.props vis-a-vis as children using this.props.children ? Why not always use this.props ?

vis-a-vis as children using ? Why not always use ? Here is my diffs for Step 3.3 and complete 3.3 source.

3.4 Dynamic Composition

Let's now create the rows of bugs from an array, dynamically.

Read

Write

Similar to the React tutorial, create a global array of bugs.

Pass this as the props variable bugs from the BugList class down to the BugTable class.

variable from the class down to the class. Create an array of <BugRow> classes in the render() function of BugTable based on the passed-in props.bugs , and replace the hard-coded <BugRow> s with this.

Ruminate

Why is the special key property required?

property required? You could pass the entire bug object instead of each attribute from BugTable to BugRow . Which style would you use and why? Are there situations where you would use the other style?

to . Which style would you use and why? Are there situations where you would use the other style? I chose to pass the global data right from BugList , though we could have done it from BugTable itself. Why?

, though we could have done it from itself. Why? Here is my diffs for Step 3.4 and complete 3.4 source.

Props are immutable. We need to store our data in the State and update it dynamically.

4.1 Create initial state

Read

React Tutorial - Reactive state (ignore updating state for the moment)

Write

Add a getInitialState() function to BugList , which just returns the global bugs data.

function to , which just returns the global bugs data. Look at the props passed in from BugList to BugTable . Replace this with state.

Ruminate

There is no apparent change, why did we have to do this?

Here is my diffs for Step 4.1 and complete 4.1 source.

Read

React Guide - Interactivity and Dynamic UIs

React Tutorial - Updating state, but don't focus on the ajax part. Focus on this.setState() and its effect. We'll come back to Ajax later.

Write

Add a test button in BugList which will trigger a test method in the same class on an onClick event.

which will trigger a test method in the same class on an event. Let that test method call another method addBug() which adds a bug, taking a bug object as parameter.

which adds a bug, taking a bug object as parameter. Let addBug() modify the state. Get a copy of the current state's bug list, modify it by pushing the new bug, and set the new state.

modify the state. Get a copy of the current state's bug list, modify it by pushing the new bug, and set the new state. Add a console log message in render() .

Ruminate

Why is a render() on BugList automatically called when we change the state?

on automatically called when we change the state? Would render() also be called for the other classes?

also be called for the other classes? Does render() really change the DOM? Always?

really change the DOM? Always? Here is my diffs for Step 4.2 and complete 4.2 source.

4.3 Communicate child to parent

Read

Write

Replace the placeholder text in BugAdd with a real form, including the Add button. Don't attempt to show an initial value in the inputs, use the form in the old plain-HTML way.

with a real form, including the Add button. Don't attempt to show an initial value in the inputs, use the form in the old plain-HTML way. Pass the addBug function as a props variable to the BugAdd class.

function as a props variable to the class. Remove the test button and its handler. Add a handler ( onClick or onSubmit ) to the BugAdd class.

or ) to the class. In the handler, call the passed-in-props: the addBug function. Create a bug object by looking at the form values the traditional way, i.e., using form input values.

Ruminate

If the state had been maintained in BugTable instead of BugList , would this have been possible?

instead of , would this have been possible? Why does the bug list reset to the original when you hit Refresh on the browser? How do you make it stay?

Here is my diffs for Step 4.3 and complete 4.3 source.

5. Data on server

We'll now move the data store to the server, but not use a database yet. We'll store it in memory.

5.1 GET API

Read

Write

Create an initial array of bugs in the web server.

In the web server, create an endpoint /api/bugs .

. Stringify and return the array of bugs in this endpoint.

Test by typing the URL directly in the browser.

Ruminate

Inspect the request response on a browser refresh.

We get a 304 Not Modified response sometimes. What if we use curl on the command line?

Read this stackoverflow question and see if we need to disable caching.

Here is my diffs for Step 5.1 and complete 5.1 source.

5.2 POST API

Read

Write

Install body-parser. We'll need to use this to parse JSON in the request body.

Create a POST handler for /api/bugs in the web server that takes in a new bug as JSON and adds it to the bug array. Generate the bug ID based on current array length.

in the web server that takes in a new bug as JSON and adds it to the bug array. Generate the bug ID based on current array length. Return the new bug created (so that the generated id is known to the caller) as the response. Use res.json() instead of res.send() .

instead of . Test using curl -- send a JSON as the request body.

Note: If you don't use type when creating the bodyParser , you will have to explicitly send a header for content type as application/json .

Ruminate

5.3 Use the GET API

Read

Write

Include jQuery, we'll need this for making Ajax calls.

In our client app code, return an empty array in getInitialState. Get rid of the global bug data, we'll be getting this from the server now.

Make an ajax call to fetch the data in componentDidMount , use setState() to set the state to the returned data.

Ruminate

Look at the number of times render() is called. Can this be avoided? Does it need to be avoided?

is called. Can this be avoided? Does it need to be avoided? What's that bind(this) all about? What happens if we did not bind?

all about? What happens if we did not bind? Here is my diffs for Step 5.3 and complete 5.3 source.

5.4 Use the POST API

Read

React Tutorial - Adding new comments (We've already read and used the form related parts, now read the ajax call parts.)

React Tutorial - Optimistic updates

Write

Modify addBug to POST the bug to the server's api.

to POST the bug to the server's api. Do the setState on successful return of the POST API, using the response data as the new bug to push.

Ruminate

Could we have done optimistic updates? Why or why not?

Do we need a bind() for the error handler?

for the error handler? Here is my diffs for Step 5.4 and complete 5.4 source.

6. Save to database

We'll now use a database to act as the store for the list of bugs instead of the in-memory store.

6.1 Initialize

Read

Write

Play around with the Mongo shell, insert and fetch some documents in a collection.

Create a Mongo shell script that we can use to initialize a collection with set of initial values.

Remove existing records before inserting the initial default ones.

Run the shell script to initialize the DB.

Ruminate

MongoDB generates its own primary key called _id . Is it OK to use the same, or should we generate our own?

. Is it OK to use the same, or should we generate our own? If we need to generate our own sequence like 1,2,3 ... how complicated is it?

Here is my diffs for Step 6.1 and complete 6.1 source.

6.2 Connect and Read

Read

Write

Install mongoldb driver using nom (don't forget --save ).

). Connect to the db, and in its success function, (a) save the connection to a global variable, (b) start the web server.

Modify the GET API to query the data from the DB using find() , and convert it to an array.

, and convert it to an array. Send the array as a JSON in the response.

In the client code, replace id with _id which is generated by MongoDB.

Ruminate

Should we have used cursor.each() ?

? We're saving the connection in a global variable. Is this safe? What happens if the DB connection is lost? Restart the MongoDB server while the web server is still running to see what happens.

Here is my diffs for Step 6.2 and complete 6.2 source.

6.3 Write to DB

Read

Write

Modify the POST API to insert a record.

For the return value, you will have to find() the inserted record, whose _id is part of the result of the insert.

the inserted record, whose is part of the result of the insert. Now's the time to get rid of the in-memory database array of bugs.

Test, and also check via the mongo shell whether new records are being inserted.

Ruminate

Could we have just added the _id to the passed in but to be added and returned that instead of doing a findOne() again?

to the passed in but to be added and returned that instead of doing a again? findOne() is deprecated. Did you use the suggested alternative?

is deprecated. Did you use the suggested alternative? Here is my diffs for Step 6.3 and complete 6.3 source.

7. Build and Bundle

Now, we'll take a break to get a little more organized. As we go along, we'll be writing more React components, so it's a good time to modularize. We'll use browserify to modularize, and gulp to automate.

This step is entirely optional, you may skip this if you are OK with a single App.js file and not too particular about the loading time of the React library from the CDN.

7.1 browserify

Read

browserify, how to use a node-style require to organize client-side code.

Write

Install browserify globally, to start with. We won't save this in package.json, as we'll use a local install finally.

Install jquery , react and react-dom via npm.

, and via npm. Use require statements to include react, react-dom and jQuery in the app.

Manually browserify the transformed JS file, write out a bundle.js in the static directory.

in the static directory. Replace all scripts in the index.html with one bundle.js .

Ruminate

Should we install the client-side libraries using --save or --save-dev ? Why?

or ? Why? Inspect the Network traffic to ensure only a single javascript is loaded.

Look at the contents of bundle.js . What is browserify doing?

. What is browserify doing? Here is my diffs for Step 7.1 and complete 7.1 source. Note that there is a global install of browserify that's not visible in the source.

7.2 Automate with gulp

Read

gulp - Getting started, to understand task runners.

babelify, a browserify plugin for Babel transforms on the fly. Don't confuse this with gulp-babel , which is a gulp plugin. All browserify plugins end with 'ify'.

, which is a gulp plugin. All browserify plugins end with 'ify'. babelify - Node, how to use browserify purely in Node.

Gulp + Browserify: The Everything Post, look for USING THEM TOGETHER: GULP + BROWSERIFY right at the end of the post.

Write

Install gulp globally.

globally. Install vinyl-source-stream , gulp , babelify and browserify locally.

, , and locally. Write a gulpfile.js , with one task called bundle , which takes in src/App.js as its source, transforms it using presets-react , bundles it, and writes out bundle.js in the static directory.

, with one task called , which takes in as its source, transforms it using , bundles it, and writes out in the directory. Cleanup: Uninstall the global browserify and babel-cli

and Cleanup: remove static/App.js , and any scripts used for manual transform and bundle.

, and any scripts used for manual transform and bundle. Generate bundle.js by typing gulp bundle on the command line.

Ruminate

Look at gulpfile.js closely and understand what each of the pipe steps mean.

closely and understand what each of the pipe steps mean. Here is my diffs for Step 7.2 and complete 7.2 source. Note that there is a global uninstall of browserify, babel-cli that's not visible in the source, as also a global install of gulp.

7.3 watchify

Read

watchify (skip all the command-line stuff, we'll be using it from within gulp only).

Write

Install watchify

Write a new gulp task called watch as per instructions in the watchify readme, except, use vinyl-source-stream (we've already done this in the previous mini-step) instead of createWriteStream .

as per instructions in the watchify readme, except, use (we've already done this in the previous mini-step) instead of . Don't bother about code reuse between this and the bundle task, let's just get it to work.

task, let's just get it to work. Remember to return the browserify object within the task definition.

Ruminate

How can we re-use code between the two tasks? Some ideas in this post by Mitchel Kuijpers.

There is no output (success or errors) from watchify. Why is that? Check if bundle.js is being updated on changes to the app source.

is being updated on changes to the app source. Here is my diffs for Step 7.3 and complete 7.3 source.

7.4 Error Handling

Read

Write

Add an error handler that prints the error object -- find out what are the attributes of the error object.

Change the printing so that only the message and stack trace are printed.

Add another print for successful update.

Optionally, make watch the default gulp task.

Ruminate

Why is it that gulp build prints the errors, whereas gulp watch did not till we added the special error handling?

prints the errors, whereas did not till we added the special error handling? Here is my diffs for Step 7.4 and complete 7.4 source.

7.5 Modularize

Read

Write

Split the single source file into multiple: one for each of the components BugFilter , BugAdd and BugList . Let the file for BugList contain BugTable and BugRow as well.

, and . Let the file for contain and as well. Ensure that the bundle is rebuilt when any of the files change.

Ruminate

Do we have to add all the source files to the watch task?

Take a look at bundle.js , especially towards the end. What's happening?

, especially towards the end. What's happening? Here is my diffs for Step 7.5 and complete 7.5 source.

8. Filtering

Rather than fetch all the records from the server, we'll add a convenience filter that fetches us a set of records based on an input query filter.

8.1 Add Filter to GET API

Read

Write

Modify the GET API to add a filter to the find() call.

call. Let the filter consist of two parameters: priority and status.

The parameters must be fetched from the query string of the request, e.g., /api/bugs?priority=P1&status=Open .

. If either parameter is blank or not specified, the query should not be filtered on that field.

Test by typing the API call in the browser.

Ruminate

8.2 Hardcoded Filter

Read

Write

Refactor the initial data loading in BugList . Separate out the ajax call into a function of its own, loadData() . Call loadData() from componentDidMount handler.

. Separate out the ajax call into a function of its own, . Call from handler. Let loadData() take in a parameter, filter , which is an object containing the filter parameters as attributes, priority and status .

take in a parameter, , which is an object containing the filter parameters as attributes, and . Use the filter as the query string in the ajax call.

Replace the BugFilter placeholder with a button, add a handler in the same class to handle the click.

placeholder with a button, add a handler in the same class to handle the click. Pass a submit handler to BugFilter from BugList , and call the submit handler in the button's click handler, with a hardcoded filter, e.g., {priority: "P1"} .

from , and call the submit handler in the button's click handler, with a hardcoded filter, e.g., . Test the hardcoded filter by clicking on the button.

Ruminate

What if we had stored the bug list data in BugTable instead of BugList ? Would it have been possible to implement the filter's Submit action?

instead of ? Would it have been possible to implement the filter's Submit action? Here is my diffs for Step 8.2 and complete 8.2 source.

8.3 Filter Form

Read

Write

In BugFilter , create a form with controlled components - two dropdowns, one each for priority and status.

, create a form with controlled components - two dropdowns, one each for priority and status. Save the values of these dropdowns in the component's state.

Use the state to create the filter for loadData() .

Ruminate

On a browser refresh, the filter is reset. How can we prevent this?

Here is my diffs for Step 8.3 and complete 8.3 source.

9. Routing

Routing will help us use the Browser's URL bar to remember the filter and also help when we add other views ('pages') in the app.

9.1 React Router

Read

Write

Install React Router.

Create a route for /bugs to show the Bug List. Let's use the default way of using history, that is, Hash History. Let's not use Browser History.

to show the Bug List. Let's use the default way of using history, that is, Hash History. Let's not use Browser History. Create a 404 not found component, use that for any other route.

Redirect / to /bugs

to Test: / , /bugs and any other URL for triggering a 404.

Ruminate

9.2 URL parameters

Read

React Router Introduction - Getting URL Parameters, read about query strings in particular.

Write

Use the URL's query string to set the initial state of the filter in BugFilter .

. Type in a filter manually as a query string (e.g., #/bugs?priority=P1 ) to test that we can pass in a filter via the URL.

) to test that we can pass in a filter via the URL. Note: this.props.location is available only for routed component, i.e., BugFilter . You'll have to pass this down to BugFilter via props.

Ruminate

The initial state of the filter is nicely set, but what about changes to the inputs? The URL and the UI are out of sync on changes in the UI.

Here is my diffs for Step 9.2 and complete 9.2 source.

9.3 Change filter

We'll change the flow such that hitting Apply in the filter changes the URL's query string as well as loads up a new bug list.

Read

Write

Pass a new function, changeFilter to BugFilter instead of loadData , for calling to apply a new filter.

to instead of , for calling to apply a new filter. Let changeFilter sync up the URL as well as load the data with the new filter.

sync up the URL as well as load the data with the new filter. As I write this, the right way to access history and programmatically navigate is by using this.props.history within the component. As the upgrade to 2.0 instructions indicate, we may need to change this to this.props.router methods instead when 2.0 is released and we adopt that.

Ruminate

A browser refresh sets the initial state of the filter UI, but the bug list is not filtered. Why?

Here is my diffs for Step 9.3 and complete 9.3 source.

9.4 Component Lifecycle

What we really need is a single source of truth: the query string. Whenever the query string changes or is initialized, we need to re-load the bug list based on the new filter. The Apply button click should only change the query string.

Read

Write

In BugList , Remove the call to loadData within changeFilter . Let loadData() get the filter from the location's query string.

, Remove the call to within . Let get the filter from the location's query string. Hook up to one of the lifecycle methods to get notified on props change (location is part of props). Reload the data in BugList when this happens, and update the state in BugFilter on props change, to trigger a re-render.

when this happens, and update the state in on props change, to trigger a re-render. Note: If you use componentWillReceiveProps , you will have to skip fetching new data if previous and current query strings are the same, else, there will be an infinite loop on an initial load.

, you will have to skip fetching new data if previous and current query strings are the same, else, there will be an infinite loop on an initial load. Note: If you use componentDidUpdate too, you'll have to do that check. Also, you'll notice that render() will be called twice when the filter changes: once when the props are received, and again when you do the setState() .

too, you'll have to do that check. Also, you'll notice that will be called twice when the filter changes: once when the props are received, and again when you do the . Try both and take your pick. Better still, choose one method for BugList to reload the data, and another for BugFilter to re-render when the filter changes, e.g., when you manually change the URL's query string to a new filter.

Ruminate

Even when there's no change in BugFilter and BugAdd , they're being re-rendered when a new filter is applied. Is that OK?

and , they're being re-rendered when a new filter is applied. Is that OK? Since render() only affects the virtual DOM, it should be OK to let it be called multiple times?

only affects the virtual DOM, it should be OK to let it be called multiple times? Are there any other Lifecycle methods that we can use to optimize this further?

Here is my diffs for Step 9.4 and complete 9.4 source. Note that some changes to the logging have been done to make debugging and understanding the Component Lifecycle easier.

10. Edit page

Now that we have routing all set up, let's build a new view / page that lets us display and update a single bug.

10.1 Single object GET API

Read

Write

Add a GET API of the form /api/bugs/<id> that retrieves a single record.

API of the form that retrieves a single record. Be careful about the case of ObjectId, the method and the export from the mongo driver.

Test by typing the API URL in the browser.

Ruminate

Do we have to convert a string to ObjectId? If you are used to MySQL, you'll probably expect the database to coalesce the string type to an ObjectId.

Here is my diffs for Step 10.1 and complete 10.1 source.

Read

Write

Add a PUT API that modifies an existing record, the request body will contain the new bug record in JSON format.

API that modifies an existing record, the request body will contain the new bug record in JSON format. The response will contain the new bug record that was modified.

Test using curl or equivalent. Note: don't forget to add Content-Type as application/json in the curl request, in case you are using the default bodyParser.

Ruminate

Are we better off using findOneAndReplace instead of the two-step modify and find?

Here is my diffs for Step 10.2 and complete 10.2 source.

10.3 New route and page

Read

Write

Create a new route for a single record like /bugs/<id> which will mount a new component BugEdit .

which will mount a new component . Create a new component BugEdit in a new source file that renders a form with all form elements as controlled components.

in a new source file that renders a form with all form elements as controlled components. Load the initial data using an Ajax call to the GET API.

Submit the form using the PUT API.

Handle changes to the route's parameters: reload the bug when just the id is changed.

Test by manually typing the route, and also by just modifying the bug ID in the URL.

Ruminate

We had a choice of hooking up to either componentWillReceiveProps or componentDidUpdate Lifecycle methods. Which one did you choose, and why?

or Lifecycle methods. Which one did you choose, and why? Here is my diffs for Step 10.3 and complete 10.3 source.

React Router Introduction - With React Router React Router API - Link

Write

Create links from the bug list so that clicking on the bug's Id takes you to the edit page.

Create a link to go back to the bug list from the edit page.

Ruminate

The React Router API for Link has an example with back-ticks to concatenate a string and a variable. Could we have used that method? Why or why not?

has an example with back-ticks to concatenate a string and a variable. Could we have used that method? Why or why not? Here is my diffs for Step 10.4 and complete 10.4 source.

11. Polish the UI

After looking at various UI frameworks and widget libraries, I narrowed down on these two that had good support for React.

Material UI: this seems the most seamlessly integrated with React. I liked the CSS using JS style of this library, which is truly the future of React based UI widget libraries. But there are some bugs with this library. The documentation has some issues too, but I hope soon we'll see the 1.0 version which addresses all this.

React-Bootstrap: Bootstrap has been quite popular, and this re-build for React gives us all the familiarity of Bootstrap. The library seems quite solid.

At this point, let us branch and try Material UI. We'll come back and re-do the UI polish with React-Bootstrap as well. If you like you can skip 11.b entirely, or if you think you fancy Material UI more than React-Bootstrap, you can skip that step instead.

11.b Material UI

11.b.1 Get started

Read

Write

Create a branch, if you are using a revision control tool. If not, save the current state of the project. We'll need this as the base for future, when we use React-Bootstrap as an alternative.

Install material-ui .

. Convert the Apply button in BugFilter to a Material UI RaisedButton

to a Material UI Use the touch / tap event to submit the form.

Ruminate

11.b.2 Filter

Read

Also, as I write this, it appears that SelectField documentation is not up-to-date . If you are havin, read the following.

Write

Enclose the filter in a Card. Remove the default top padding of 16px in Card text, by overriding the style.

Pick the fa-filter icon for the Avatar in the header.

icon for the Avatar in the header. Convert the dropdowns to Select fields.

Note: Using an empty string as the value for the dropdown causes issues. Use '*' or any other indicator as the value for (Any) option.

Ruminate

Why do we need double-braces for inline styles?

Here is my diffs for Step 11.b.2 and complete 11.b.2 source.

11.b.3 Add Form

Read

Write

Convert the Bug Add form to Material UI.

Note: that we cannot use a <form> and named inputs any longer, we have to do it the React way, by saving the form input values in state.

and named inputs any longer, we have to do it the React way, by saving the form input values in state. Use a Card to display enclose form in a collapsible manner.

Use fa-plus icon in the Avatar.

Ruminate

11.b.4 Table

Read

Write

Wrap the table in a Paper .

. Convert the regular table to a Table component and its corresponding children.

component and its corresponding children. Add an App Bar without any icons.

To make the table look nicer: Use a fixed with for all columns except the Title column, let this occupy all remaining space. Let the size of the table rows be 24px tall, using the style property of TableRowColumn . Make the font color red whenever a bug's priority is P1.



Ruminate

Is there a better way to set the row styles so as to not repeat the code for column widths between header and body columns?

Resize the window to check the responsive behavior. How different is this from Bootstrap's Grid system?

Here is my diffs for Step 11.b.4 and complete 11.b.4 source.

11.b.5 Edit Form

Read

Write

Convert the Bug Edit form to Material UI.

Use a Card to wrap the entire form, but not a collapsible Card.

Show success of the submit using a SnackBar .

. Make the link back to the bug list a real link (so that one can right-click and open in new window/tab if they desire).

Ruminate

Could we have avoided hardcoding of the route back to the bug list, and used some other React-Router component to achieve this?

Here is my diffs for Step 11.b.5 and complete 11.b.5 source.

11. React-Bootstrap

After playing around with Material UI, I found that though it seems very nice to do stuff the JS - CSS way, the library itself seems a little buggy and not mature enough. Perhaps when the version 1.0 is released, we can come back to it. In the meantime, let's take React-Bootstrap, which seems the most popular UI framework, for a spin.

11.1 Get Started

###Read

Write

Go back to the branch point, i.e., end of step 10.

Use the CommonJS way of installation.

Convert the filter's button into a primary button.

Ruminate

11.2 Filter

Read

Write

Create a collapsible panel with "Filter" as the heading, and the form components as the contents.

Use a Grid inside the panel and lay the 3 inputs in 3 equal columns in a single row. Note: A fluid Grid looks and works better.

Grid looks and works better. If the button looks misaligned, use a wrapper Input .

Ruminate

11.3 Add Form

Read

Write

Convert the plain Add Bug form to a Bootstrap styled form. Use a vertical form this time, without using a Grid.

Add a non-collapsible panel around this form.

Ruminate

11.4 Table

Write

Write

Add a header-less Panel around the table to give it some margins

Convert the plain table to a striped, condensed, bordered table.

Ruminate

There are two ways of applying table styles - the original Bootstrap CSS way of doing things by applying the Bootstrap classes, or, the React-Bootstrap way of doing things using the Table class. Which one did you pick? Why?

class. Which one did you pick? Why? Here is my diffs for Step 11.4 and complete 11.4 source.

11.5 Edit Form

Read

Write

Convert the input elements in the Edit form to a bootstrap-style form, without grids.

Use a ButtonToolbar to show a Submit button and a Back link, with the link preferably an <a> element so that we can right-click and open in new tab if we want.

to show a Submit button and a Back link, with the link preferably an element so that we can right-click and open in new tab if we want. Wrap the form in a panel with a header. Restrict the width of the page to 600 pixels.

Show a success alert on successful save of edits.

Ruminate

react-router doesn't seem to have an obvious way to get the href string that we can pass to the Button . Fortunately, using Bootstrap styles on <Link> works.

. Fortunately, using Bootstrap styles on works. Could the success alert be a separate component? Where would the visibility state be maintained in this case?

Here is my diffs for Step 11.5 and complete 11.5 source.

12. Onward

At this point, you should be well equipped to go forth and write your own app using the MERN stack. Going forward, you may want to consider a reading a few more things, which I have left out.

12.1 Flux

You may want to add flux pattern to your app, especially if it gets big. The following resources are great ones to get you started on that:

12.2 Packaging

You may also want to consider packing Bootstrap CSS and minifying it as part of your build process, maybe even customize bootstrap as part of that. You may also want to minify the Javascript bundle that's being created. In which case, you should read up on:

12.3 Server side rendering

Server side rendering is really useful if you'd like your pages to be search-engine indexable. A page has to be rendered completely on the server side when a search engine makes a request. Here are some starting points:

12.4 Add-Ons

If you thought we're writing too much code to handle changes to form elements, or not being able to use a hierarchical state you may find these React Add-ons useful.

12.5 ES6

Looking at the libraries we used, significantly, their example documentation, it appears that ES6 is the way to go. Unfortunately, I haven't yet learnt ES6, so I wrote the entire tutorial in plain Javascript. I encourage you to at least get started on ES6.