Web development with React (overview)
Disclaimer: This is an old unpublished article that I decided to publish anyway.
This is a broad overview about developing a React app from a Windows environment. If you are coming from a non-web, non-JavaScript world, or more precisely from a .NET world (like myself), this article might be helpful to you.
I will assume you already know what React is for.
Prerequisites
- A code editor — Visual Studio Code (Insiders) it’s free, open source and runs everywhere.
- A browser — Chrome and Firefox have tools for React developers.
Every code samples will be located in the directory C:\www\.
A first React app
This is the “Hello World” example for a React app. This first app simply displays some text inside a minimal HTML page.
- Create a new directory 1-FirstReactApp inside the www directory.
- Open Visual Studio Code (Insiders) from this directory.
- Create a new HTML page index.html in Visual Studio Code using the New File button.
PS C:\www> mkdir 1-FirstReactApp
PS C:\www> cd .\1-FirstReactApp\
PS C:\www\1-FirstReactApp> code-insiders .
Let’s take a look at the index HTML page:
In this first sample:
- Line 5–6 — we add the React libraries as external script files
- Line 13 — we render a React element with the React virtual DOM inside the
<div>
on line 10.
Here is the result:
Calling the method React.createElement()
every time we need to add an HTML tag will make the source code unreadable. That’s why React recommends using another language called JSX — a statically-typed, object-oriented language. It is meant to be transformed into the JavaScript language.
JSX allows us to write (or mix) HTML code within our JavaScript code.
As stated in the React documentation :
JSX is not a requirement for using React. Using React without JSX is especially convenient when you don’t want to set up compilation in your build environment.
To transform JSX code to JavaScript code, React uses Babel — a JavaScript compiler.
The following sample is exactly the same as the previous sample but uses JSX and Babel (see line 7 and 14).
unpkg.com allows us to include libraries into our HTML page without downloading and referencing locally the packages.
That’s it for this first page.
Next, we’ll host our page in a local web server. Since this article is about a .NET environment, let’s use what the ASP.NET ecosystem has to offer.
Prerequisites
- A web server for ASP.NET Core — Kestrel
- Web application framework & tools for .NET — .NET Core
- .NET Core command-line tools — .NET Core CLI
- A JavaScript runtime — Node.js
The server
Every website are hosted on a web server. Since we are from a .NET world, let’s integrate the new index.html file within an ASP.NET Core application and host it with Kestrel — the web server that is included by default in ASP.NET Core new project templates.
These are the steps needed to set-up a new empty ASP.NET Core Web application using the command-line tool.
- Open a command prompt.
- Create a new directory 2-ReactApp inside the www directory.
- Create a new empty ASP.NET Core Web application (and restore its packages)
- Open Visual Studio Code (Insiders) from this directory.
PS C:\www> mkdir 2-ReactApp
PS C:\www> cd .\2-ReactApp\
PS C:\www\2-ReactApp> dotnet new web
PS C:\www\2-ReactApp> dotnet restore
PS C:\www\2-ReactApp> code-insiders .
We will copy the index.html from the 1-FirstReactApp sample in the wwwroot directory.
You should have this structure in Visual Studio Code:
Next, let’s modify the Program.cs and Startup.cs files in order to display our index.html page.
To display our HTML page, we need to add a new package to the project:
PS> dotnet add package Microsoft.AspNetCore.StaticFiles
Afterward, we add in the Startup.cs file this following line at the end of the Configure()
method: app.UseStaticFiles()
.
Now that the web server is in place, we can test again our website. To start the web server that hosts the ASP.NET Core Web application, we can use the dotnet build
and dotnet run
command…
… and navigate to http://localhost:5000/index.html to see the result.
The server — integration with Node.js
Since React is a JavaScript library, the source code is of course written in JavaScript.
In a React application, we write all the code inside JavaScript files — API calls, HTML code rendered by React DOM, authentication, …
An ASP.NET Core Web application cannot use as-is these JavaScript files without being called inside an HTML page (that’s not what we want for now, we are still on the server side). To make things work, we need Node.js — the JavaScript runtime.
Node.js is not either usable as-is in an ASP.NET Core Web application. We need another tool for that: the JavaScriptServices for ASP.NET.
These services allow us to execute and retrieve the result of JavaScript code running inside Node.js from ASP.NET directly.
First, let’s add the reference of the NuGet package:
PS> dotnet add package Microsoft.AspNetCore.NodeServices
Afterward, we modify the Startup.cs file again:
- Line 14 — we add the Node services to the services collection usable by our ASP.NET Core Web application.
- Line 26–32 — we handle the HTTP requests with
app.Run()
and we use the Node Services to call a scriptrenderer.js
.
The renderer.js file encapsulates a function inside a Node.js module and returns a basic Hello World text.
- Line 3 — the
callback
is used to return data to the ASP.NET.
If we run again the sample with dotnet run
we have Hello world displayed.
The server — adding the React library (1)
I briefly mentioned Node.js modules. React can also be used as a module. To import module we use the keyword import
.
Let’s edit the renderer.js file and use the React library:
import React from "react";module.exports = function (callback) {
let result = "<div>Hello world !</div>";
callback(/* error */ null, result);
};
This code is obviously not working; if we run the sample, it crashes with the following exception:
The import
keyword is not known inside the script. That’s why we need bundlers (like webpack) that will gather all the dependencies and include them inside a generated script. (and we did not yet add the React library to the project)
Let’s install all the needed dependencies.
To retrieve packages for Node.js, we use NPM — the package manager for JavaScript.
NPM is identical to NuGet; both are package manager.
NPM need to be initialized with the following command :
PS> npm init
A new file package.json is created.
Prerequisites
- a compiler to transform our JSX code to “normal” JavaScript code — babel
- a bundler that packs together libraries, modules, assets into one or more bundles — webpack
The bundler
These are the command-lines that install all these dependencies:
PS> npm install --save-dev webpack
PS> npm install --global webpack
If everything has succeeded correctly, you now see a new directory named node_modules with the libraries into it.
Let’s set up webpack to compile our code to an understandable JavaScript code by ASP.NET.
We will need to execute the webpack command (and quite frequently). That’s why the webpack package is installed twice at different location (global and dev).
Webpack by default search for a JavaScript file named webpack.config.js inside the current directory that defines its configuration.
Let’s create it at the root of the folder:
- Line 4: the entry points where webpack starts processing the bundle.
- Line 5–7: the output path and filename of the generated bundle.
Then we can execute the webpack command:
PS> webpack
The bundle.js file generated by webpack (without the previously import statement — for clarity) looks like this:
- Line 73–76 contains our renderer.js code
Now, in the Startup.cs file, instead of calling the renderer.js script we can the newly generated bundle.js file.
If we run again our sample, we should no get the previous exception. Indeed we are not getting one from JavaScript but now one from the ASP.NET framework.
When webpack bundled our code, the required export
for the JavaScriptServices doesn’t exist anymore. Here are the new versions of the Startup.cs and renderer.js files to make webpack correctly exports the function in renderer.js file.
Now everything is working as before.
Let’s try again to add the React library into our application and hopefully succeed this time.
Prerequisites
- the ReactJs library — react and react-dom
The server — adding the React library (2)
First, we need to install the React libraries — React and React DOM:
PS> npm install --save react react-dom
Next, we will update the renderer.js file and import one more time the React libraries.
The
import
keyword is identical to theusing
keyword in C#.
We get the exact same page as before:
Prerequisites
- a compiler — Babel
The JavaScript compiler
As a reminder, Babel is used to converting JSX syntax into a browser-compatible JavaScript code.
Let’s install Babel using NPM. There is a command-line tool called babel-cli. We don’t need this package since we will integrate Babel with Webpack.
PS> npm install --save-dev babel-loader babel-core
We should now be able to write again HTML tags in our JavaScript code. For that, we will rename the renderer.js file to renderer.jsx (optional but useful for syntax highlighting) and update the “Hello world” (line 5).
let element = <div>Hello world !</div>;
Webpack uses loaders to transform one file into another. In the case of Babel, we can use the babel-loader to parse our .jsx files — see line 9–12.
Unfortunately, if we run the command webpack
it’s not working yet.
Babel as-is cannot compile JSX. We need to add more functionalities to Babel. One way is through presets — let’s add the “default preset” and the “React preset”:
PS> npm install --save-dev babel-preset-env babel-preset-react
See line 15–17:
After the completion of the command webpack
, a new bundle.js file is created inside the wwwroot/dist directory.
The website shows exactly the same page as before:
It’s time to write something slightly more complex than Hello World.
The React web app — a minimal architecture
As a reminder, we did not write any client/browser code yet. We’re still on the server side.
Every website starts with a home/index page. Let’s replace the Hello World line with a basic but complete HTML page.
- Create a new app folder (with containers and components folders).
- Extract the webpack configuration for the renderer.js to a specific file.
- Create new React components for the app (shell.jsx, server.jsx, browser.jsx, app.jsx).
- Create a new webpack configuration file for the current app.
- Create a more complex App component (app.jsx).
Create new directories and files
Here is the new structure of the project:
- app folder — contains all the JavaScript code of the web app.
- containers folder — contains the React “root” components, usually used when navigating to an URL (it’s like the Views in a .NET project).
- components folder — contains the React components (it’s like the UserControls in a .NET projects).
- shell.jsx —defines the default HTML page (server-side).
- server.jsx — defines the entry point of the React web app (used by the renderer.jsx file).
- app.jsx — this is the content of the React web app
- webpack.renderer.config.js — the same webpack configuration file that we have for now.
- webpack.config.js — defines the new webpack configuration for the React web app (we need another one because we will add more configurations that don’t apply to the renderer.jsx file).
Extract the webpack configuration for the renderer.jsx to a specific file
To make the webpack configuration looks simpler/organized and because we’ll need a more complex webpack configuration for the other files, I extracted the one needed to process the renderer.jsx file into a specific file called webpack.renderer.config.js
.
We can either write separate files and run them through the webpack command separately or merge them inside the default webpack.config.js
. I choose to do the latter (I don’t care yet about the webpack command line execution time), and that’s what the following code snippets do.
Create new React components for the app
In the renderer.jsx file, we still display a simple Hello World div. Let’s remove this line and create two React components: the HTML default structure and the content.
Here is a part of the explanation about components from the React documentation:
React components implement a
render()
method that takes input data and returns what to display.
The HTML structure of the web app is the usual code. See the following app/containers/shell.jsx
file:
On Line 2 and Line 14, we import and use another component that will render the content.
That component App is the following:
Finally, we need to update the renderer.jsx
file and display the new Shell component. I decided to export the call to the Shell component inside another app/server.jsx
file because we’ll need to write more code in there to handle additional functionalities.
About the import
statements (Line 3 of server.jsx and Line 1 of renderer.jsx), webpack by default understand only .js extensions. This happens if we import the shell without the .jsx extension.
If we want to import module without the file extensions, we need to specify explicitly which extensions we need in the webpack configuration file.
resolve: {
extensions: [ '.js', '.jsx' ]
},
Create a new webpack configuration file for the current app
After creating the new files (server, shell, browser), we need to use them with webpack. Here is the new configuration:
- Line 7–9 — we configure how webpack will find modules by searching not only for
.js
files but.jsx
files too. - Line 10–12 — we define another entry point for the
app/browser.jsx
file. - Line 15 — we tell webpack how to name files (
wwwroot/dist/app.bundle.js
whereapp
is the name of the entry). - Line 34 — we tell webpack to process both configurations (renderer and app).
If we execute the webpack
command, it generates 2 files inside the www/dist
folder.
You can use webpack with a few additional arguments like
--progress
and--display-error-details
to respectively display a progress indicator and to have understandable error messages.
I could copy here, again, the screenshot of Hello world but I’ll let your imagination do the work. The page is exactly the same.
Create the a more complex App component — app.jsx
The best way to see the difference between the server code and browser code is to make the content dynamic (we haven’t written anything about the browser or client-side yet).
The server is obviously not dynamic; it only generates HTML to send it later to the browser of the clients.
Let’s update the App component code to display the current date and time (this is the best way to visualize the difference between the server and the client).
If we run webpack
and start the website with dotnet run
, we have a static page (the timer is not working).
This is obviously normal because:
- the server returns static content;
- we didn’t include the
app.bundle.js
file to our HTML; - the server catches all requests and it calls the renderer.js for each request made (even if the request is for a JavaScript file or an image).
Well, that’s it for the server.
To sum up what we’ve done so far:
- We render the React web app as a string, directly from the server and send it back to the browser of the client — that’s call Server Side Rendering (SSR).
- We configure webpack to bundle our multiple dependencies into two files
wwwroot/dist/renderer.bundle.js
andwwwroot/dist/app.bundle.js
.
The client (browser) — app.bundle.js
Here we’ll set the focus on the client-side.
The bundle file wwwroot/dist/app.bundle.js
is built using the app/browser.jsx
file. The entry “app” of the webpack configuration file uses browser.jsx
to generate the bundle.
Here is the code of browser.jsx
:
The Lines 5–7 are the same lines we wrote at the very beginning to render into the page the React web app.
Also earlier in this post, we served static files to the clients. With the server side in mind, let’s add again that service to the ASP.NET Core pipeline and display the wwwroot/index.html
file.
In the Startup.cs
file, we can add again the middleware needed to serve static files (Line 29):
This is the new wwwroot/index.html
file that uses the wwwroot/dist/app.bundle.js
:
That’s it for the client-side. We now have an index.html
page that renders our React web app and this times, it’s dynamic.
If we build and start again the website, we now have a working timer. Let’s navigate to http://localhost:5000/index.html.
But, we lost the Server Side Rendering functionality. Whether you put the app.useStaticFiles()
before or after the app.Run()
you will have the client side or the server side.
The next step is to use a requests router that will serve static content only when needed (images, CSS files, JavaScript files, …).
- Line 29–39 — we declare the router. For each request, we extract the path behind the root domain name and save it into the
query
key. - Line 56–76 — we retrieve the value of the
query
key and if the path is/
orindex.html
we route to the server side renderer otherwise we serve the file using the static middleware.
To continue …
Template
All this code can be created automatically using the ASP.NET SPA Template in Visual Studio.
Here is a screenshot of the generated project: