This training workshop will cover the fundamentals of React. React.js is a an open-source JavaScript library for creating user interfaces with a focus on the UI and has become the tool of choice for easily building dynamic user interfaces.
- Laptop
- Node/NPM (https://nodejs.org/en/download/)
- Chrome/Edge/Firefox
- Chrome Extension - React Developer Tools (https://goo.gl/g16a7N)
- Text editor (https://code.visualstudio.com/)
Make sure you have Node 8.10 or later on your local development machine.
node -v
If you donโt, install the latest version: https://nodejs.org/en/.
Verify that everything works by running:
git clone https://github.com/feedzai/first-react-app-workshop
cd first-react-app-workshop
npm install
npm start
Open http://localhost:3000 to view it in the browser.
If there are any questions, feel free to email me! joao.dias@feedzai.com
Let's go to the src
folder and check all the sub-folders there.
assets
- This is where all the global styles arecomponents
- This is where all the react components livepages
- Like thecomponents
folder, but for the app pagesservices
- This is were the special functions that connect to firebase liveutils
- This is were we setup Firebase
Now let's go to the public
folder:
- This is a static folder, were we host our
index.html
file.- Notice that we have a little special loader between the
#root
folder. - This is removed once React kicks-in, but it helps out to showcase some content before that happens.
- The favicon is an SVG file that adapts itself to dark and light modes.
- Notice that we have a little special loader between the
First we need to make the single-page app understand our url structure. Each different url equals a different page. Some urls even support special placeholder characters so that they can be rendered dinamically. To do that, let's go to the App.js
component.
// src/App.js
function App() {
return (
<div className="app" role="application">
<Router>
<Routes>
{/*<Route path="/chat/:person" element={<ConversationPage />} />
<Route path="/chat" element={<ChatsPage />} />
<Route path="/" element={<HomePage />} />*/}
</Routes>
</Router>
</div>
);
}
Now we need to display the each page in between the Router
component. So, let's uncomment the code and have a look at it.
Each Route
component comes from the react-router
library. They define each page and accept one of our components to display. We can also define a path, which is the section of the url that comes after the domain.
Now, the /chat
and /
paths are pretty self-explanatory. But the /chat/:person
is a litte different. The :person
bit is sort of a template that we can use later on the app.
<Router>
<Routes>
<Route path="/chat/:person" element={<ConversationPage />} />
<Route path="/chat" element={<ChatsPage />} />
<Route path="/" element={<HomePage />} />
</Routes>
</Router>
After uncommenting the code we should now be able to see the new rendered pages.
Show, there are no cards showing, right?
That's ok, we're going to take care of that now.
To do that, let's first import a special hook from react, called useEffect
.
// src/CardsList/index.js
import React, { useEffect, useState } from "react";
This useEffect
hook enables you to perform any type of side-effects on a function component.
So, each time a component is mounted, re-renders or unmounts, we can run code inside these hooks.
These hooks accept two arguments:
- a callback function
- an array of dependencies
We want to subscribe to the "people"
collection from our firebase firestore.
And we can do that once the component mounts, since that subscription will be alive through out the whole lifecycle of the component.
So, let's use the getPeople
services function that we've built for you to abstract the part of fetching the data from the server. This function accepts a callback function, so we will use the allUsers
array to update our component's state.
There's already a function to do that - setUsers
.
// src/CardsList/index.js
useEffect(() => {
// Use the `getPeople` function and update the users state
// once the server has responded with a list.
const unsubscribe = getPeople((allUsers) => setUsers(allUsers));
return () => {
// Since we're simulating a server response with a delay,
// we also need to clear our timeout, or else the timeout
// would continue to be kept in memory
clearTimeout();
// We also need to remove the firebase subscription
unsubscribe();
};
}, []);
Still no cards? That's fine, there's one little bit left.
To render multiple items in React, we pass an array of React elements. The most common way to build that array is to map over your array of data. Letโs do that in the renderList
function.
/**
* Render an array of `Card` components
*
* @returns {JSX.Element[]}
*/
function renderList() {
// Go through each user and return a `Card` component with the necessary props
// 1. Convert the distance in meters to kilometers;
// 2. Don't forget the key
const listOfUsers = users.map((person) => {
const convertedDistance = (person.distance / 1000).toFixed(1);
return <Card key={person.name} distance={convertedDistance} person={person} />;
});
return listOfUsers;
}
Now the cards should appear! ๐ Play with them, swiping left and right.
Let's also show the bottom buttons by uncommenting the code comment on the SwipeButtons
, SpecialModal
components and handleOnClickButtons
and handleOnCloseModal
functions.
Click on any of the bottom buttons, and a funny modal should appear! ๐
Now, notice on the footer bar, there's a button on the right. That's the messages icon and, if you click on it, it will lead you to the chats page.
So...let's click on it!
Wait, there are no messages? ๐ฑ
Again, it's fine. It's because we haven't fetched the list of messages from firebase.
To do that, let's go to Chats
component and do the useEffect
bit again. This time we use the getConversations
function
// src/components/Chats/index.js
useEffect(() => {
const unsubscribe = getConversations((allMessages) => setMessages(allMessages));
// When the component unmounts, remove the messages subscription and the fake timeout
return () => {
unsubscribe();
};
}, []);
Now that we have an array of messages, let's render them out on the screen!
To do that, we will render them by using the renderMessages
function:
function renderMessages() {
const list = messages.map((msg) => {
return (
<Chat
key={msg.id}
id={msg.id}
name={msg.name}
message={msg.messages[msg.messages.length - 1].message}
timestamp={msg.timestamp}
profilePic={msg.profilePicture}
/>
);
});
return list;
}
So, basically, for every message, a Chat
component.
That's great, we should now have a list of messages showing up.
We're almost there to have our basic app running! Now the only thing we need to do is, on each click on each message, have some sort of way to send over to the conversation screen some unique identifier that we can use to pick that message out from the full list of messages.
Well, we have!
Notice that every entry on the messages
array as an id
field.
We can use that and pass it to the element that receives our click - The Link
component.
The prop is state
and it accepts an object.
<Link className={styles.chat} to={`/chat/${nameForUrl}`} state={{ id }}>
<div className={styles.container}>
<Avatar className={styles.image} alt={name} src={profilePic} />
<div className={styles.details}>
<h2 className={styles.title}>{name}</h2>
<p className={styles.message}>{message}</p>
</div>
<time className={styles.timestamp}>{convertedTimestamp}</time>
</div>
</Link>
Now, a click on the Link
component will lead us to the Conversation
page.
We can also have a look at the browser's url bar and see that the :person
template is now being replaced with the users name from the clicked link.
We now have to reach over to react-router
and use a special hook (yes, we can create our own custom hooks with react) to get the location
properties of that page, including the state
.
import { useLocation } from "react-router";
...
// Inside the component, at the top
const location = useLocation();
So, we have access to the location. We still need to fetch the conversations again, right? It's like we did before.
There are a couple of differences right now:
- We only want to show one conversation
- We have access to the clicked id
So, we need to pick the only conversation that matters. We can do that using the useEffect
hook, the getConversation
function and the find
method for the callback allMessages
object.
useEffect(() => {
const unsubscribe = getConversations((allMessages) => {
// Using the `find` method we can pick the message we want
// by comparing the source id with our location state id.
// If if matches, that's our guy!
const conversation = allMessages.find((message) => message.id === location.state.id);
// We only update the state if we have an actually usable result
if (conversation) {
setChat(conversation);
}
});
// When the component unmounts, remove the messages subscription
return () => {
unsubscribe();
};
}, [location.state.id]);
And that's it! We've covered a lot of ground today. There's way more things to learn about React. More hooks, more design patterns, more ways to improve our performance, etc.
But, there's one more thing...
As your app grows, you can catch a lot of bugs with typechecking. For some applications, you can use JavaScript extensions like Flow or TypeScript to typecheck your whole application. But even if you donโt use those, React has some built-in typechecking abilities. To run typechecking on the props for a component, you can assign the special propTypes property.
Import the PropTypes
and use them on our components.
import PropTypes from "prop-types";
You're at the end of your journey, and you've accomplished a lot. Congrats, You are awesome!
You can learn more in the Create React App documentation.
To learn React, check out the React documentation.