Before we begin, a basic understanding of JavaScript and object-oriented programming is required. I'll try to be as thorough as possible, but I won't go through the basics of JavaScript again.
- User data is retrieved via an API, then formatted and displayed.
To get started, clone this GitHub repository. It includes a simple React app that shows a list of ten employees. Each employee has a first and last name, as well as an email address, a photo, and the date of registration. The employee data is fetched in a JSON file when the React application is launched. The file public/users.json is located in the public folder.
This React application's framework is quite standard. The following are the three primary folders:
- Both conventional components, such as the Date and Email components, and layout components, such as the Header and the Main wrapper, are found in the components folder.
- The Home component is located in the pages folder. We'll have a lot of page components in a bigger project.
- Both the JSON mock file and the randomuser API are accessed through the services folder.
The pages/Home component, as well as the React Query setup, are used in the App.js file.
The data synchronization aspect of the project is done with React-query. This library is based on the react-apollo library and enables for declarative creation of HTTP requests. Although I do not believe the documentation is always clear, it is really useful and readable.
You must wrap your entire React application in the QueryClientProvider component to make it work:
function App() { return ( < QueryClientProvider client={queryClient}> < HomePage /> < /QueryClientProvider> ) }
Then, as in the case of the pages/Home component, you can utilise the useQuery hooks:
const Page = () => { const { isLoading, error, data } = useQuery( 'users', () => get(), { refetchOnWindowFocus: false } ) if (isLoading) returnLoading...if (error) returnAn error occurs...return ( < Body> < Main> < Header> < PageTitle text='Students' /> < /Header> { data.map(user =>) } < /Main> < /Body> ) }
The data is presented in the UserCard component once the promise is resolved. This component is supported by a number of additional components. For example, the Image component shows the user's photo, the Name component shows the user's name and the Email component shows the user's email address. Drilling props are used to transfer data from top to bottom components.
const Component = ({ user }) => ( < Wrapper> < UserImage firstName={user.first_name} lastName={user.last_name} picture={user.picture} /> < UserName firstName={user.first_name} lastName={user.last_name} /> < UserJoinedDate date={user.registered_date} /> < UserEmail email={user.email} /> < /Wrapper> )
For the time being, our programme relies on the JSON file in the public folder, which works perfectly. But now it's time to make things a little more complicated: instead of using the JSON file, we'll use the Random User Generator API. Refresh the application after changing getMockData to getApiData in the get method of the services/Api/index.js file.
async function getMockData() { return await fetch('http://localhost:3000/users.json') .then(res => res.json()) .then(({ data }) => data) } async function getApiData() { return await fetch('https://randomuser.me/api/?results=10') .then(res => res.json()) .then(({ results}) => results) } async function get() { return await getMockData() // change this to getApiData } export default get
The application is now broken, and you'll need to modify all of the props to make it function again. It could be acceptable in a one-page application, and you could do it, but picture having to do it in a ten- or twenty-page application: it won't be pleasant at all. You'll learn nothing and possibly introduce some bugs. The function pattern comes very handily in this situation.
- Using the Constructor Pattern, create a model for our user data.
The function pattern is frequently the first design pattern I teach new developers. It's a wonderful introduction to design patterns because it's directly applicable and doesn't rely on abstraction. It's simple to grasp, can be done on the front end, and is also simple to utilize.
When I started learning Java and later Php, I first heard about it. You may have already learned this notion if you are familiar with these languages. Do you know what the names POJO and POPO mean? POJO and POPO stand for Plain Old Java Object and Plain Old PHP Object, respectively. We also refer to them as Entities. We can use them to encapsulate and store data most of the time.
Here's an example of how the POPO interface can be used to generate an object's plan:
interface UserInterface { public function getName(); public function setName($name); } class User implements UserInterface { public $firstName; public $lastName; public function getName() { // ... } public function setName($value) { // ... } }
Things aren't always the same in JavaScript as they are in other programming languages. This is due to the fact that JavaScript is a prototypical object-oriented language rather than a class-based language, and it is also not a completely object-oriented language. Enumerations and interfaces, for example, do not exist in JavaScript. For example, we can imitate enumerations with Object. freeze, but this is not the same as using the enum keyword.
Returning to the function pattern, there are two ways to implement it: using a function or a class/prototype. Because you construct a prototype when you use the class keyword in the back, the terms class and prototype are interchangeable.
Here's an example with a class:
class User { constructor(firstName, lastName, age) { this._firstName = firstName this._lastName = lastName this._age = age } get firstName() { return this._firstName } get lastName() { return this._lastName } get age() { return this._age } displayUserInfo() { console.log(`Here are the information I have on this user: ${this._firstName}, ${this._lastName}, ${this._age}`) } } const MyFirstUser = new User('Thomas', 'Dimnet', 33) const MySecondUser = new User('Alexandra', 'Corbelli', 30) MyFirstUser.displayUserInfo() MySecondUser.displayUserInfo()
And below it with a function:
function User(firstName, lastName, age) { this._firstName = firstName this._lastName = lastName this._age = age this.firstName = function() { return this._firstName } this.lastName = function() { return this._lastName } this.age = function() { return this._age } this.displayUserInfo = function() { console.log(`Here are the information I have on this user: ${this._firstName}, ${this._lastName}, ${this._age}`) } } const MyFirstUser = new User('Thomas', 'Dimnet', 33) const MySecondUser = new User('Alexandra', 'Corbelli', 30) MyFirstUser.displayUserInfo() MySecondUser.displayUserInfo()
I like to work with the class keyword most of the time since I believe it is more legible and clear. We already know what the class's getters and setters are after a closer look. Feel free to use any of these, but I'll be using the class version for the rest of the blog.
One of my favourite aspects of the function Object pattern is its ability to store both raw and parsed data. Assume you receive a timestamp date from an API and need to display it in two formats: "YYYY-MM-DD" and "DD-MM-YYYYY." Here's an example of how you can use the function Object pattern.
import moment from "moment" class Movie { constructor(date) { this._date = date } get date() { return this._date } get dateV1 { return moment(this._date).format("YYYY-MM-DD") } get dateV2 { return moment(this._date).format("DD-MM-YYYY") } }
By the way, the function pattern isn't just for formatting objects: you can use it for any type of object creation. Many jQuery effects, for example, employ the function Object pattern.
Before we begin implementing the solution, keep in mind the main disadvantage of this pattern: it can be memory-intensive. Although our computers and phones now have a lot of memory, it's always important to remember that our software and applications need to be optimized. I recommend that you only use functions when they are required.
Change from mocked data to API data without a hitch.
You can now switch to the with-constructor-pattern branch from the current one. There are two function Object patterns in this branch: src/models/MockedUser.js and src/models/ApiUser.js. The first employs hard-coded JSON data, while the second employs data from the Random User Generator API. The data displayed is currently coming from a JSON file.
The MockedUser object looks like this:
import moment from "moment" class User { constructor(data) { this._id = data.id this._firstName = data.first_name this._lastName = data.last_name this._email = data.email this._picture = data.picture this._registeredDate = data.registered_date } get id() { return this._id } get fullName() { return `${this._firstName} ${this._lastName}` } get email() { return this._email } get picture() { return this._picture } get registeredDate() { return moment(this._registeredDate).format('MM/DD/YY') } } export default User
This is a simple JavaScript class that contains all of the user's necessary properties: an email, a photo, and a registration date. Rather than using raw JSON data, we now use this template throughout our code. It provides us with a single source of truth. If we want to add a new property or if the data changes, such as the last login, we can do so here.
Despite the addition of these two new objects, the only change to the code is in src/pages/Home/index.js:
This is a simple JavaScript class that contains all of the user's necessary properties: an email, a photo, and a registration date. Rather than using raw JSON data, we now use this template throughout our code. It provides us with a single source of truth. If we want to add a new property or if the data changes, such as the last login, we can do so here.
Despite the addition of these two new objects, the only change to the code is in src/pages/Home/index.js:
{ data .map(user => new MockedUser(user)) // this is where we do the change .map(user =>) }
We now use the data stored in the MockedUser object instead of the raw JSON data. Assume we want to use the data from the RandomUser API. We only need to make two changes. To begin, modify the get function in
src/services/Api/index.js. We'll now use actual data.
async function get() { return await getApiData() // Instead of getMockData }
Then, in your browser, replace the MockedUser function with the ApiUser function :
With only two changes, you can now use API data instead of JSON data and keep the project running! Furthermore, you understand what properties are required for user data and can easily add new ones or modify existing ones! I hope you enjoy this blog about JavaScript design patterns. If you're reading it for the first time, I'm delighted to have been your guide. Please feel free to ask any questions you may have. Airo Global Software will be your digital partner.
E-mail id: [email protected]
Author - Johnson Augustine
Chief Technical Director and Programmer
Founder: Airo Global Software Inc
LinkedIn Profile:
www.linkedin.com/in/johnsontaugustine/