50.003 - Frontend Development using React.js¶
Learning Outcomes¶
By the end of this unit, you should be able to
- Explain React Components
- Explain React States
- Explain React Lifecycle
- Explain React Effect
- Develop a frontend app using React JS
Issues with the ExpressJS + AJAX approach¶
- routes / controllers for view rendering and AJAX calls are intertwined
- client side JS in the views are not so reusable and not so testable
Front End Web App¶
A front end web app detachs the view components from a monolith web app.
For example, the following diagram shows that a react.js
app runs separately from
the express.js
web app.
In this settings, the express.js
becomes a backend API server. The views are now hosted in the react.js
app in a separate host address and port. When the client first accesses a page, it visits the react.js
app for the desired web page. The React.js
app renders the web page by making an API call to the backend server. When it has the JSON data, it inserts the data into the result rendered. In addition, it bundles all the client side JS codes and other static content and sends them to the client browser as the response.
The bundle JS codes becomes the client app which runs in the client browser. In the subsequent requests (initiated by UI control), the AJAX calls are made directly from the client browser through the client app. This frees up some of the processing time from the web server as more codes are executed within the client browser.
Furthermore, the client side JS codes are hosted and executed (at least during the first request) on the react.js
app server. This allows JS codes to be better modularized as a separate project, thus testing can be conducted systematically.
Building a ReactJS App¶
To initiate the project, we need to execute
We find a project folder with the following content.
src/main.jsx
is the main function (application entry point).
From the above we noted a few points,
- We are using ES6 syntax
- We mix JavaScript syntax with HTML (XHTML) syntax (as well as CSS). The syntax is called JSX. Using JSX is not mandatory but it is recommended. You can still use pure JS and React syntax to create browser components.
main.jsx
is creating a page by referencing to theroot
element and render theApp
element inside.- the
App
element is imported fromsrc/App.jsx
.
Let's take a look at the App.jsx
To run the app, we run
which will start the reach app at https://localhost:5173
. To stop it, we press control-C in the terminal.
As we are going to run both react server and express server simultanously, we need to run the react app in a specific port, let's say 5000
.
In the project root folder, we update the file vite.config.js
Re-run npm run dev
, it will re-start the react web app at https://localhost:5000
.
App.jsx¶
Let's modify src/App.jsx
to see how the web app change.
As we save the file, the browser with the web app running will automatically refresh. We see
(Can you modify the code so it appears similar to the above screenshot?) If we check the source of the HTML page in the browser,
Nothing from the above reflects the change that we made in App.jsx
.
If we follow the link /src/Apx.jsx
in the browser, we find the following
code snippet in the JavaScript file.
React JS Compilation¶
React frameworks focus in building applications which can be targeted for multiple platforms, i.e. web browser, windows application, mobile app and etc. It takes a single stack of source languages, i.e. JSX and CSS, compiles them into a single output. In this case, since we are using React JS to build a web app, the output will be in a single JavaScript file.
Image credits https://nextjs.org/learn/foundations/how-nextjs-works/compiling
Vite Preview¶
Vite offers a two stage compilation process for Reac.js projects.
When a project is run using npm run dev
, the JSX sources are structured in a similar folder hierachy as we arrange them in the project. This is for the ease for debugging.
When we want to deploy the app to production, exposing the structure of the project might result in information exposure.
Hence we would use the following command to generate a bundle
which will trigger a full compile and bundle process and generate a ./dist
folder containing the bundled code.
Then we could preview it using
Note: we could also change the preview port by modifying the vite.config.js
file.
Components¶
Unlike Web Application developed using other frameworks, e.g. express.js, React JS develops applications with a single page in mind. Component
is the building block of React app. Component
in react can be written using JSX or pure React syntax React.createElement
. We will use JSX syntax in this module. With the control of basic components, the React JS app is able to dynamically update and replace components and sub-components within the same page in the event of user interaction and data update.
In our running example, function App
constructs a component which is used by the main.jsx
at the root level. Function App
returns the static HTML code as result.
Let's make some change to App.jsx
as follows,
In the above we import and include an Echo
component from ./src/Echo.jsx
, which is defined as as follows,
After reloading the app in the browser, we have
Top Down design process (static)¶
From the earlier example we see a hierachical structures in the components and elements used in our app.
- index.js
- App
- div
- h1
- Echo
- div
- text
When we design the UI using React JS we should follow and exted the same structure.
Suppose we would like to build a frontend app to interact with the express.js app by submitting new messages and displaying the list of submitted messages. By breaking down the UI components we might have the following
By following the UI structural breakdown, we modify the Echo.jsx
as follows,
In the above version of Echo.jsx
, we define two sub components, namely NewMessageBar
and MessageList
. Note that both functions take some objects as the arguments, these objects arguments are referred as props
in React's terminologies. Refer here for more details about props
in react.
NewMessageBar()
takes an object with two attributes,message
andonSubmitClick
.message
stores the text value in the text field andonSubmitClick
is a callback when theSubmit
button is clicked. The function returns a text input field and a buttonSubmit
.MessageList()
takes an object with an attribute,messages
which is a list of message objects. It returns an HTML table whose rows are constructed by mapping each message in the list to a row of the table.Echo()
, we hard code the data (which is supposed to be returned by the AJAX call to the backend, will be implemented later), and pass them toMessageList
component during the component construction.
Note that we might re-write the component definition using class instead of function. Notice the difference on how props are accessed here.
It is up to you to use either class
or function
syntax in creating React components.
Props¶
As shown in the earlier example, props are the objects arguments being passed to the constructor of a React Component or function argument enclosed with curly brackets. Props carried data and information passed down from the use-site, (or the parent component).
Make it interactive (dynamic)¶
Now we need to make the echo app functional. First let's make the button click to respond to the text entered in the input text field.
We modify NewMessageBar
as follows,
We introduce a new props attribtue onMessageChange
which is used as the handler for the text field onChange
event. Without this, the text field is read-only.
Next we modify the Echo
function as follows,
and import the following:
State¶
The useState(initialState)
is one of React Hooks which allows you to have a getter and setter without writing a class. This function returns two values (in the previous example, the two values are captured in msgTxt
and setMsgTxt
). The first value is the current state which in the first render will have the initialState
value. The second "value" is the setter function that lets you update the state and trigger a re-render phase of React lifecycle (lifecycle will be discussed later).
In addition, we define a function handleSubmitClick
which is used as the event handler of the Submit
button click. Now we can interact with the app by entering the text in the message text field and pressing the button to trigger a pop up.
Interfacing with the backend app¶
Lastly we need to remove the hard-coded data and make use the data returned by the backend API end-point. Adjust the Echo
function as follows:
The changes we made include
- remove the hard coded messages.
- introduce a new state
[messages, setMessages]
. - define a function
submitNewMessage()
which make the HTTP POST api request to the backend app and update the message. - update the body of
handleSubmitClick()
to incorporatesubmitNewMessage()
.
When the button is clicked, a sequence of operations were carried out in the following order.
handleSubmitClick()
invokessubmitNewMessage()
submitNewMessage()
make the api call- when the api call returns the result, the body text is extracted and parsed as JSON.
setMessages()
update the messages state, which triggers the re-rendering of theEcho()
component and all of its children (NewMessageBar
andMessageList
).
Next, we can reuse our previous AJAX demo project my_mysql_app
as the backend of our react frontend. In separate terminal, run npm start
for my_mysql_app
for the backend and npm start
for the react app (make sure that they run in separate port, 3001 for react and 3000 for express if you follow this note).
We will encounter an error of the api Call. By checking the console in the browser, we find that it is related to the Cross Origin Resource Sharing (CORS) restriction, which prevents the JS from React app to pull data from an exernal app.
To lift the restriction, we modify the router code at the backend app, i.e. my_mysql_app
.
For all the AJAX call end-points to be accessed by our react app (which is running on port 3001), we add the following statement before the res.send()
is called.
Lastly, let's make the app to displays all the existing messages when it is firstly loaded. Naively, we might added the following function definitions and function call statements before the return
statement in the Echo()
function.
With this fix, our App seems to get into an infinite loop. This is because initMessages()
is now part of the render phase routine of the Echo
component. When the Echo
is rendered, the setMessages
is invoked, which causes the component to re-render.
Life Cycle of React Components¶
In order to fix this issue, we need to understand the life cycle of React Component.
image credits https://levelup.gitconnected.com/componentdidmakesense-react-lifecycle-explanation-393dcb19e459
When a React Component is spawned, server methods are called at the server end (i.e. when our react app is hosted). After which, it calls componentDidMount
, then goes into a kind of loop driven by the changes of states or props of react component. Each change of states or props will re-renders the component in the browser. The loop goes on until it unmount, (i.e. the component is destroyed).
To access each of the stages of the React Component, let's rewrite the Echo
component in class style
In the above version, we refactor the useState()
parts by using the inherited attribute of the Component
class: state
and setState()
. In the above example, we put msgTxt
and messages
in this.state
and write setMsgTxt
and setMessages
methods using this.setState
. Since functions are different from method internally, we need to turn the props arguments into lambdas, e.g. onMessageChange={(s) => this.setMsgTxt(s)}
instead of onMessageChange={setMsgTxt}
. Finally we override the componentDidMount
and componentDidUpdate
methods.
Exercise (Non Graded)¶
Add some log messages to the constructor
, componentDidUpdate
, componentDidMount
and render
.
Run the program to observe the order of the log messages being printed. Can you identify which message is printed from which part of the life cycle?
As suggested by the life cycle, it is better to initilize the state outside the loop, i.e. componentDidMount
.
Hence we change the componentDidMount
as follows, and introduce a new function to retrieve all existing messages from the API and update the state.
With this change, the App behaves as what we want.
Effect¶
Some of us might argue, rewriting the component in the class form allows us to access the different stage of the life-cycle. The code could be too verbose.
To achieve the same result in the function-form component, we need to use the useEffect
hook.
Recall the earlier version in function-form.
The statement calling useEffect
defines a call-back to be called every time
the component re-renders. We can restrict the call-back to be triggered only when certain states have updated, e.g.
will be triggered whenever msgTxt
state changes.
If the 2nd argument is an empty list []
. the call-back will only be triggered when the component is mounted. Hence to achieve what we want, i.e. to get all the existing messages and assign them to the state messages
, we need to include the following in the body of the Echo
function.
We could think of useEffect
with only 1 argument is behaving like componentDidUpdate
. useEffect
with a 2nd argument as a non empty list is behaving like componentDidUpdate
with conditional update (depending on whether the given state has changed). When useEffect
is used with a 2nd argument as an empty list it is behaving like componentDidMount
.
Further Reading¶
- Building a React App from Scracth
https://react.dev/learn/build-a-react-app-from-scratch
- Thinking in React
https://react.dev/learn/thinking-in-react
- React JS and Express JS
https://www.freecodecamp.org/news/create-a-react-frontend-a-node-express-backend-and-connect-them-together-c5798926047c/
- React JS life cycle
https://levelup.gitconnected.com/componentdidmakesense-react-lifecycle-explanation-393dcb19e459
- Multi page React
https://www.geeksforgeeks.org/how-to-create-a-multi-page-website-using-react-js/
- Multi page with state
https://www.microverse.org/blog/how-to-get-set-up-with-react-redux-in-your-next-multi-page-project
- React JS and Express JS with Login and Token authentication
https://www.digitalocean.com/community/tutorials/how-to-add-login-authentication-to-react-applications