DEV Community

Cover image for ๐Ÿ’ป A Technical Interview Project: Building a Lending-as-a-Service Application ๐Ÿฆ
Olasunkanmi Balogun
Olasunkanmi Balogun

Posted on • Edited on

๐Ÿ’ป A Technical Interview Project: Building a Lending-as-a-Service Application ๐Ÿฆ

I recently completed a technical interview with a lending-as-a-service company. The interview required me to build four pages with TypeScript, React, and SCSS for styling.

Although, unfortunately I didn't obtain the position, but since it's a large project (which I personally think is gross for the first stage of a technical interview) I'm committed to sharing what I learned. In this article, I'll walk you through the features I implemented and how I implemented them. I hope this article will be helpful to other frontend developers who are preparing for technical interviews.

PS: I'm still open for a frontend developer role. If you're hiring for a frontend role, please reach out to me. I'd love to learn more about the opportunity.

Note that, this article will focus on the features implemented, with little to no discussion of the styling.

TL;DR

The following is a brief overview of the pages and features implemented in the project:

  • Login page: I used Firebase for authentication and implemented protected routes with React Router, even though it wasn't required.

  • List of users page: This page pulls data from a mock API with 500 records using Axios and Axios Interceptors.

  • User details page: This page was required to use local storage or indexedDB to store and retrieve user details. I chose to use localStorage.

The end product of the pages implemented should look like this:

Log In Page

Dashboard Page

User Details Page

This article shall be a complete overhaul from the ground up, with no stone unturned. I will start with the login page, and discuss how the user is navigated to the dashboard. From there, I will also explore the features implemented in the users page, and finally, we shall reach the user details page, where the user journey ends.

When you are ready, let's dive in.

Ready GIF

How I Set My Project Up

I Set Up My React + TypeScript Project With Vite

To kickstart my project swiftly, I opted for Vite as my project initializer, and I highly recommend you do the same if you haven't been using Vite. Initializing a TypeScript + React project with Vite is effortless with just one command:

npm create vite@latest interview-project -- --template react-ts
Enter fullscreen mode Exit fullscreen mode

How I Set Up Firebase For Authentication

I decided to utilize Firebase for authentication for this app although it wasn't a requirement.

The fact that I also wanted to implement a protected route functionality for all authenticated pages also played a role in my decision. This feature will be discussed in a later section.

In this section, I will briefly guide you through the process of how I set it up.

Generally, to use Firebase, you need to create and register your project on the platform, which I did.

Following this, I installed Firebase in my project and created a components folder. Inside this folder, I created a file named firebaseConfig.ts to store my configuration details and establish a connection between my app and Firebase like this:

import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";

const firebaseConfig = {
  //firebase config
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);// connect firebase
export const auth = getAuth(app)
Enter fullscreen mode Exit fullscreen mode

If you are new to Firebase and are interested in learning more about it, check out the official documentation here.

File Structure

I established a pages directory to contain the various pages of the application.

Within the previously created components directory, I created subdirectories with names corresponding to the folders within the pages directory. This organizational structure allows for convenient grouping of relevant components within each page-specific folder.

Furthermore, I added a styles folder in the src directory to centralize all the styles for my pages. Additionally, an utils folder was created within the src directory to store files responsible for handling API requests.

I had adopted this file structure during my time at my previous job.

In the end, the folder structure looked like this:

- project
  - src
    - components    
      - dashboard
      - common
      - user-details
      - users     
      - login
      - firebaseConfig.ts
    - pages
      - Login.tsx
      - Users.tsx
      - User-details.tsx
    - styles
    - utils
      - request-adapter.ts
      - requests.ts
    - main.tsx
  - public
  - package.json
Enter fullscreen mode Exit fullscreen mode

I'll now walk you through each page and files if necessary in the next section.

Page Routing In The main.tsx File

The powerful React Router was employed for seamless page routing, not like there are numerous alternatives available lol. If you are new to React Router or uncertain about its benefits, I recommend checking out its documentation here for further insights.

Once React Router is installed, I implemented the routes in the main.tsx file as seen in the code snippet below.

I had not used React Router in a while due to the fact that I have been writing a lot of Nextjs these days. As a result, I was taken aback by the new syntax when revisiting it. If you're in a similar situation where you haven't kept up with the updates, note that an updated version, 6.4, has been released with fresh syntax and features. I recommend referring to the documentation to stay updated and informed.

import React from 'react'
import ReactDOM from 'react-dom/client'
import './App.scss';
import {
  createBrowserRouter,
  RouterProvider,
} from "react-router-dom";
import Login from './pages/Login-Auth.tsx';
import Users from './pages/Users.tsx';
import UserDetails from './pages/User-details.tsx';

const router = createBrowserRouter([

  {
    path: "/",
    element: <Login/>
  },

  {
    path: "/dashboard/users",
    element: <Users/>
  },

  {
    path: "/dashboard/users/:id",
    element: <UserDetails/>
  },
]);

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <RouterProvider router={router}/>
  </React.StrictMode>,
)

Enter fullscreen mode Exit fullscreen mode

How I Implemented Features

Starting Off With Authentication In The Log In Page ๐Ÿš€

Log In Page

For the sign-in method, Firebase enables you to use different sign-in providers, ranging from email and password provider to Google provider and so on... For the authentication of this app, I employed the email and password provider since the email and password input fields were provided and required.

Here's how I implemented the feature:

import { createUserWithEmailAndPassword, signInWithEmailAndPassword } from "firebase/auth";
import {  useNavigate } from 'react-router-dom'
import { auth } from '../components/login-auth/firebaseConfig';

export default function Login() {

  const navigate = useNavigate()
  const [ email, setEmail ] = useState<string>('');
  const [ password, setPassword ] = useState<string>('')
  const [ signUp, setSignUp ] = useState<boolean>(true)
  const [ helperText, setHelperText ] = useState<string>('')
  const [ hide, setHide ] = useState<boolean>(false)

  const handleSignUp = (e: React.FormEvent<HTMLButtonElement> ) => {
    e.preventDefault()
    createUserWithEmailAndPassword(auth, email, password)
     .then(() => {
      setHelperText('Congrats!, you can now Sign In')
      setTimeout(() => setHelperText(''), 2000)
      setSignUp(!signUp)
      setError(false)
     }) 
   .catch((error) => {
    const errorCode = error.code;
    const errorMessage = error.message;
    setError(true)
    console.log(errorCode, errorMessage)
   });
 }

  const handleSignIn = (e: React.FormEvent<HTMLButtonElement>) => {
    e.preventDefault()
    signInWithEmailAndPassword(auth, email, password)
    .then(() => {
      setError(false)
      navigate('/dashboard/users')
      sessionStorage.setItem('auth', JSON.stringify(auth))
     })
   .catch((error) => {
   const errorCode = error.code;
   (errorCode === 'auth/user-not-found!') ?
    setHelperText('Email is not registered!') :
    setHelperText('Invalid password')
    setError(true)
    setTimeout(() => setHelperText(''), 2000)
  });
}
}

return (
       <form>
            <div>
            <h1>Welcome!</h1>
            <p>Enter details to {signUp ? "login" : 'sign up'}.</p>   
            <div>
            <p className={`${error ? 'error' : 'success'}`}>{helperText}</p>
              <div>
              <Input type='email' placeholder={'Email'} value={email} onChange={setEmail}/>
              </div>
              <div>
              <Input type={hide ? "password" : 'text'} placeholder={'Password'} value={password} onChange={setPassword}/>
              <span onClick={() => setHide(!hide)}>{hide ? "Show": "Hide"}</span>
              </div>
            </div>
              <p>Forgot Password?</p>
              <p>Don't have an account? <span onClick={() => setSignUp(!signUp)}>Sign Up</span></p>
               <button type='submit' onClick={!signUp ? handleSignUp : handleSignIn}>           
                 signUp ? ' Log In' : ' Sign Up'
               </button>
            </div>
          </form>
)
Enter fullscreen mode Exit fullscreen mode

I implemented two functions in the code block above: handleSignIn and handleSignUp.

As their names imply, the handleSignin function handles the sign in process, while the latter handles that of the sign up. If the requirements of each function are met, the code in their .then block runs, if not, the error is caught in the .catch block.

Note that once the code in the .then block in the handleSignIn function runs, an auth object is saved in the sessionStorage of the browser. This will be used when implementing the protected routes feature. More details on this in the authenticated pages section.

I introduced the signUp state to regulate which of the functions executes in the proper scenario. When a user hits the sign up text in the form beneath the Forgot Password text, the signUp state is updated. The highlighted code below demonstrates how this is done:

 <p>Don't have an account? <span onClick={() => setSignUp(!signUp)}>Sign Up</span></p>
Enter fullscreen mode Exit fullscreen mode

As I mentioned in earlier, this state is used to toggle the functions that get invoked when the button in the form element is clicked. That is, if a user is new and just wants to sign up, the handleSignUp function is triggered, otherwise, if it is an already existing user who wants to log in, the log in function is triggered.

This also controls the text that renders in the button.

You can see how this is implemented in the highlighted code below:

   <button type='submit' onClick={!signUp ? handleSignUp : handleSignIn}>           
                 signUp ? ' Log In' : ' Sign Up'
  </button>
Enter fullscreen mode Exit fullscreen mode

The purpose of the signUp state wasn't limited to just the function and text of the button as it also updates the text below the Welcome header:

<p>Enter details to {signUp ? "login" : 'sign up'}.</p>   
Enter fullscreen mode Exit fullscreen mode

See video below for an illustration of this scenario.

gif illustration of text renders

On To The Authenticated Pages ๐Ÿšถ

Before I started off with the authenticated pages - the user and the user-details page; I thought the implementation of the protected routes feature seemed to make sense after the authentication procedure was finished.

As mentioned earlier, upon successful sign-in, an auth object is stored in the user's browser sessionStorage temporarily. I chose sessionStorage for this purpose because it retains data only for the duration of the session, deleting it when the browser is closed.

With the auth variable now appropriately stored in sessionStorage, I proceeded to implement the protected route feature. To accomplish this, I created a Page.tsx file within the common folder of the dashboard, as indicated in the file structure section, and implemented the feature as follows:

import Navbar from "./Navbar";
import Sidebar from "./Sidebar";
import { useState } from 'react';
import { Navigate } from "react-router-dom";

type PageProps = {
   children: React.ReactNode,
}

export default function Page ({children }: PageProps) {

    const authFromSessionStorage = sessionStorage.getItem('auth')

    return (
        <>
        {  
        (!authFromSessionStorage) ? 
        <Navigate to='/' replace={true}/> :
        (
        <>
        <Navbar/>
        <Sidebar/>
        <div>
            {children}
        </div>
        </>
    )} // navigate to log in page if user isn't authenticated
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Since the Page component will wrap around all authenticated pages, which share the navbar and sidebar components, I decided to include those components within it. This explains their inclusion in the code block. Here's an example of how I wrapped this component around an authenticated page component:

// users.tsx

export default function Users() {

    return (
     <Page>
      <div>
      // rest of the page content here
      </div>
     </Page>
    )
}

Enter fullscreen mode Exit fullscreen mode

Following this implementation, when an unauthenticated user attempts to access an authenticated page, they will be redirected to the sign-in page to authenticate themselves.

Navigating To The Users' Page ๐Ÿ‘ฅ

Users page

This page requires candidates to pull data from a mock API and populate it in form of a table as seen in the image above.

After pulling the data, candidates are to also implement a feature to paginate the data gotten from the API just below the table:

Dashboard footer

Since the number of users gotten from this API is 100, I decided to paginate the data in multiples of 20, 50, and 100. At the end the feature looks like this:

gif dropdown ilustration

Moving on, in the request-adapter.ts file created in the utils folder ( see folder structure section ), I created an instance of Axios with Axios.create and assigned it to a variable request. This instance also includes a base URL and timeout configuration. I also added a response interceptor to handle successful responses and errors, finally the instance is exported for use in other parts of the application:

import axios from "axios";

const request = axios.create({
    baseURL: "https://6270020422c706a0ae70b72c.mockapi.io/lendsqr/api/v1/",
    timeout: 30000,
  });

  request.interceptors.response.use(
    (response) => {
      return response;
    },
    ({ response }) => {
      return Promise.reject(response);
    }
  );

  export default request;
Enter fullscreen mode Exit fullscreen mode

Following this, the request variable is imported from the request-adapter module.

Then, a getUsers function which it uses the request instance to make a GET request to the /users endpoint is defined.

import request from "./request-adapter"

export const getUsers = () => request.get('/users')
Enter fullscreen mode Exit fullscreen mode

By calling the getUsers, the Axios instance will send a GET request to the complete URL formed by combining the base URL from the instance's configuration and the /users endpoint.

The getUsers function is exported, making it available for other modules to import and use. This function can now be called in the users' page where it will initiate a GET request to retrieve the users' data from the specified endpoint.

Next, I implemented the markup for the table that displays the users. I created a Table.tsx file in the users folder situated in the components folder for this purpose.

Here's how the markup looks like:


type TableProps<T extends object> = {
    users: T
} // users is a generic of type object just as in he Users component

export default function Table<T extends object>({ users }: TableProps<T[]>) {
    return (

        <section>
        <table>
         <thead>
         <tr>
           <th>
             <div>
            <p>Organization</p> 
             </div>
           </th>
           <th>
           <div>
            <p>Username</p> 

            </div>    
           </th>
           <th>
           <div>
            <p>Email</p> 
           </div>
           </th>
           <th>
             <div>
            <p>Phone Number</p> 

             </div>
           </th>
           <th>
             <div>
            <p>Date Joined</p> 
             </div>
           </th>
           <th>
             <div>
             <p>Status</p>    
             </div>
           </th>
         </tr>
         </thead>
         <tbody>
         // list of users will be here
         </tbody>
        </table>
        </section>
}
Enter fullscreen mode Exit fullscreen mode

Notice the custom type TableProp defined. This type is a generic type whose parameter extends an object and assigned to the users property, this ensures that the users property accepts any type of object. After the users endpoint is called, the body of the table will be implemented and populated.

After implementing the Table component, I created another component in the users folder for the pagination feature just below the table and named it PaginatedItems.tsx. The pagination feature was not just implemented in this component though, I also made the call to the users endpoint and rendered the Table component inside this component. I will explain why.

This was done mostly because I used react-paginate library to implement the pagination feature. This library needs to have access with the data you want to paginate directly - the users data gotten from the API, in order to interact with it.

I'll now walk you through the pagination and Table implementation step-by-step.

Firstly, I defined the component and fetched the data from the API:

import { useState, useEffect, useCallback } from "react";
import { getUsers } from "../../../utils/requests";
import { AxiosResponse } from "axios"

export default function PaginatedItems<T extends object>() {

  const [ users, setUsers ] = useState<T[]>([]);
  const fetchData = useCallback(() => {
     getUsers()
    .then(({data} : AxiosResponse<T[]>) => { 
      setUsers(data)  
    })
    .catch(err => console.log(err))
  }, []);

    useEffect(() => {
        fetchData()
      }, [])
  return (
     // code markup here
  )
}
Enter fullscreen mode Exit fullscreen mode

I then went on to develop the pagination feature after implementing and obtaining the component. The npm website's instruction of how to use the react-paginate module was sufficient for its integration. The following code snippet was included in the component:

import { useState, useEffect, useCallback } from "react";
import { getUsers } from "../../../utils/requests";
import { AxiosResponse } from "axios"

type DropdownProps = {  
    setItemsPerPage: React.Dispatch<React.SetStateAction<number>>
}

export function DropdownFilter({setItemsPerPage } : DropdownProps) {

    return (
        <div>
          <p onClick={() => setItemsPerPage(20)}>20</p>   
          <p onClick={() => setItemsPerPage(50)}>50</p>   
          <p onClick={() => setItemsPerPage(100)}>100</p>   
        </div>
    )
}
export default function PaginatedItems<T extends object>() {

  const [ currentItems, setCurrentItems ] = useState<T[]>([]);
  const [ itemsPerPage, setItemsPerPage ] = useState<number>(50);
  const [ pageCount, setPageCount ] = useState<number>(0);
  const [ itemOffset, setItemOffset ] = useState<number>(0);

      useEffect(() => {
      if(users){
          const endOffset = itemOffset + itemsPerPage;
          setCurrentItems(users.slice(itemOffset, endOffset));
          setPageCount(Math.ceil(users.length / itemsPerPage));
      }
    }, [users, itemOffset, itemsPerPage]);

    // Invoke when user clicks to request another page.
    const handlePageClick = (event: { selected : number}) => { //since we just need the selected property
     if(currentItems) {
         const newOffset = event.selected * itemsPerPage % users.length;
         setItemOffset(newOffset);
     }
    };
return (
      <>
      {
      (currentItems.length > 0) && (
        <>
          <Table users={currentItems} />

          <div>

            <div>
            <p>Showing</p>
            <div 
            onClick={() => setShowDropdown(!showDropdown)}
            >
             <p>{itemsPerPage}</p> 
             <img src={dropdown} alt='dropdown icon'/>
             <DropdownFilter 
             setItemsPerPage={setItemsPerPage}
             className={showDropdown ? 'active' : ''}/>
            </div>
            <p>out of {users.length}</p>
            </div>

           <ReactPaginate
            nextLabel={<Button src={right}/>}
            onPageChange={handlePageClick}
            pageRangeDisplayed={3}
            marginPagesDisplayed={2}
            pageCount={pageCount}
            previousLabel={<Button src={left}/>}
            pageClassName="page-item"
            pageLinkClassName="page-link"
            previousClassName="page-item"
            previousLinkClassName="page-link"
            nextClassName="page-item"
            nextLinkClassName="page-link"
            breakLabel="..."
            breakClassName="page-item"
            breakLinkClassName="page-link"
            containerClassName="pagination"
            activeClassName="active"
            renderOnZeroPageCount={null}
            />
          </div>
        </>
      )}
      </>
    )}
Enter fullscreen mode Exit fullscreen mode

The code provided shows the implementation of a paginated table component. The PaginatedItems component manages the state for displaying a specific number of items per page and calculating the necessary pagination parameters.

The DropdownFilter component renders a dropdown menu with options for setting the number of items per page. When an option is clicked, the setItemsPerPage function is called to update the itemsPerPage state in the PaginatedItems component.

Inside the PaginatedItems component, the useEffect hook is used to update the currentItems state and calculate the pageCount whenever the users, itemOffset, or itemsPerPage dependencies change. The currentItems state is updated by slicing the users array based on the current itemOffset and itemsPerPage values.

The handlePageClick function is invoked when the user clicks on a page in the pagination component (ReactPaginate). It calculates the new itemOffset based on the selected page and updates the state accordingly.

The PaginatedItems component also renders the Table component with the currentItems as the users prop, showing the table with the paginated data.

Additionally, there are UI elements for displaying the current page's range and the total number of users. The ReactPaginate component handles the pagination rendering and interaction based on the provided parameters.

After passing the data to the Table component, I populated the body of the table with the data.

import { useState } from "react";

type TableProps<T extends object> = {
  users: T;
}; // users is a generic of type object just as in he Users component

type User = {
  [key: string]: any;
}; //use index signature for type checking individual objects in the users array.

export default function Table<T extends object>({ users }: TableProps<T[]>) {
  const [activeId, setActiveId] = useState<string>("");

  return (
    <section>
      <table>
        <thead>
          <tr>
            <th>
              <div>
                <p>Organization</p>
              </div>
            </th>
            <th>
              <div>
                <p>Username</p>
              </div>
            </th>
            <th>
              <div>
                <p>Email</p>
              </div>
            </th>
            <th>
              <div>
                <p>Phone Number</p>
              </div>
            </th>
            <th>
              <div>
                <p>Date Joined</p>
              </div>
            </th>
            <th>
              <div>
                <p>Status</p>
              </div>
            </th>
          </tr>
        </thead>
        <tbody>
          {users.map((user: User) => {
            const { id, orgName, userName, email, phoneNumber, createdAt } =
              user;
            const date = new Date(createdAt);
            const formattedDate = date.toLocaleString("en-NG", {
              month: "short",
              day: "numeric",
              year: "numeric",
              hour: "numeric",
              minute: "numeric",
              hour12: true,
            });

            return (
              <tr key={id} className="tr-body">
                <td>{orgName}</td>
                <td>{userName}</td>
                <td>{email}</td>
                <td>{phoneNumber}</td>
                <td>{formattedDate}</td>
                <td>Inactive</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </section>
  );
}
Enter fullscreen mode Exit fullscreen mode

Next, the table needs a dropdown for each user, it will be triggered on clicking the ellipsis on the extreme right of the table. The dropdown should look like:

Table dropdown

Clicking the view details text should navigate to the user details page. I created a Dropdown.tsx file in the users folder of the component folder where I implemented the functionality like this:

import { Link } from "react-router-dom";

type DropdownProps = {
  id: string;
  toggleDropdown: {
    activeId: string;
    setActiveId: React.Dispatch<React.SetStateAction<string>>;
  };
};

export default function Dropdown({ id, toggleDropdown }: DropdownProps) {
  const { activeId, setActiveId } = toggleDropdown;

  return (
    <ul
      onMouseEnter={() => setActiveId(id)}
      onMouseLeave={() => setActiveId("")}
      className={`${id === activeId ? "show" : ""} dropdown`}
    >
      <Link
        style={{
          textDecoration: "none",
        }}
        to={`/dashboard/users/${id}`}
      >
        <li>
          <img src={viewDetails} alt="view icon" />
          <p>View Details</p>
        </li>
      </Link>
      <li>
        <img src={blacklist} alt="view icon" />
        <p>Blacklist User</p>
      </li>
      <li>
        <img src={activate} alt="view icon" />
        <p>Activate User</p>
      </li>
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

Inside the component, an unordered list (<ul>) is rendered. The className of the <ul> element is conditionally set based on whether the id matches the activeId. If they match, the show class ( which makes the dropdown visible ) is added to the element, otherwise it remains empty.

The <ul> element has two event handlers: onMouseEnter and onMouseLeave. When the mouse enters the element, the setActiveId function is called with the id as the argument, setting it as the activeId. When the mouse leaves the element, the setActiveId function is called with an empty string, clearing the activeId.

Inside the <ul> element, there are three list items (<li>) representing the dropdown options. The first list item contains a Link component that wraps the content. The Link component is used to create a clickable link that navigates to a specific URL. The to prop of the Link component is set to /dashboard/users/${id}, which dynamically generates the URL based on the id prop. This means that clicking on this option will navigate to the user details page for the corresponding id.

This component is then imported to the table component and included it in the last element of the table like this:

<td>
  <div>
    <p className="status">Inactive</p>
    <img
      src={ellipsis}
      alt="ellipsis icon"
      onMouseEnter={() => setActiveId(id)}
    />
    <Dropdown id={id} toggleDropdown={{ activeId, setActiveId }} />
  </div>
</td>;
Enter fullscreen mode Exit fullscreen mode

The end product of the dropdown looks like this:

gif illustration of dropdown

Finally, I rendered the PaginatedItems component in the users component created in the page folder. Ofcourse, wrapped around the page component initially discussed.

import PaginatedItems from "../components/dashboard/users/PaginatedItems"

export default function Users() {

    return (
     <Page>
      <PaginatedItems/>
     </Page>
    )
}
Enter fullscreen mode Exit fullscreen mode

If you have been with me up to this point, congratulations on surviving this wild ride through the User page. Grab yourself a virtual cookie and take a well-deserved break! But hold on tight because in the next section, we'll be diving into the thrilling world of each user details, where we'll uncover secrets, unravel mysteries, and navigate through the intricacies of individual user profiles.

Journey's End: The User Details Page

User Details Page

Fortunately, unlike the users page, the user page only requires implementing the data fetching and UI populating functions. However, there is an interesting twist. Once a user's information has been accessed, it needs to be saved in either the indexedDB or localStorage. The purpose of this storage is to avoid unnecessary network queries. If the user details data is already present in the localStorage, we can simply retrieve it from there instead of making additional network requests.

To get started, I defined the route to the endpoint that calls a user's details in the requests.ts file, located in the utils folder, similar to how I did it for the users endpoint:

export const getUserById = (id: string) => request.get(`/users/${id}`);
Enter fullscreen mode Exit fullscreen mode

This endpoint requires including the id of each user in its query. Fortunately, with React Router, we can extract this id from the URL once we have navigated to the user details page. Stay tuned for more exciting details!

Moving on, I created a file in the pages folder and named it User-details.tsx where I imported the previously defined endpoint for it to be called with the id. This id will be gotten from the URL with useParams provided by react-router and passed to the API route as seen below:

import Page from "../components/dashboard/common/Page";
import { useParams } from "react-router-dom";
import { getUserById } from "../utils/requests";
import { useEffect, useState } from 'react';

export default function UserDetails():JSX.Element {

 const [ user, setUser ] = useState<{[key: string]: any}>();

 const { id } = useParams(); //get id from URL

  return (
  //markup here
  )
}
Enter fullscreen mode Exit fullscreen mode

Following this, I implemented the functionality to fetch and save to localStorage in the fetchUserDetails function included in the code snippet below:

import Page from "../components/dashboard/common/Page";
import { useParams } from "react-router-dom";
import { getUserById } from "../utils/requests";
import { useEffect, useState } from "react";

export default function UserDetails(): JSX.Element {
  const [user, setUser] = useState<{ [key: string]: any }>();
  const { id } = useParams();

  const fetchUserDetails = (): void => {
    const usersFromLocalStorage = localStorage.getItem("users");
    if (usersFromLocalStorage) {
      const users: { [key: string]: any }[] = JSON.parse(usersFromLocalStorage);
      const user = users.find((u: { [key: string]: any }) => u.id === id);
      if (user) {
        setUser(user);
      } else {
        getUserById(id!)
          .then(({ data }: AxiosResponse<{ [key: string]: any }>) => {
            users.push(data);
            localStorage.setItem("users", JSON.stringify(users));
            setUser(data);
          })
          .catch((err) => console.log(err));
      }
    } else {
      getUserById(id!)
        .then(({ data }: AxiosResponse<{ [key: string]: any }>) => {
          const users = [data];
          localStorage.setItem("users", JSON.stringify(users));
          setUser(data);
        })
        .catch((err) => console.log(err));
    }
  };

  useEffect(() => {
    fetchUserDetails();
  }, [id]);

  return (
  <Page>
  // UI population code here
  </Page>
  )
}
Enter fullscreen mode Exit fullscreen mode

In the UserDetails component, the fetchUserDetails function is responsible for fetching and setting the user details. It first checks if the users property exists in the localStorage. If it does, it searches for a user with a matching id and sets the user state accordingly.

If no matching user is found, a fresh API call is made using getUserById and the retrieved data is added to the users array in the localStorage. The user state is then set with the fetched data.

In the case where the users property does not exist in the localStorage, a fresh API call is made to retrieve the user details. The retrieved data is stored in the users array in the localStorage, and the user state is set with the fetched data.

After the fetchUserDetails function is executed, the UI is populated with the relevant user details.

And thus, our adventurous quest through the intricate realm of user details comes to a triumphant end.

Conclusion

Well, despite my best efforts, one might think they would at least get a chance to proceed to the next stage, right? ๐Ÿ˜€ However, to my surprise, I received a rejection without the interviewer even signing into the app. Imagine finding out this tidbit from my Firebase Console! Life has a way of throwing unexpected twists in our coding journeys.

But fear not, my dear readers, for our shared adventure has not been in vain. I extend my deepest appreciation for your unwavering support and for joining me through the lines of code, the challenges, and the triumphs. ๐Ÿ‘Š Together, we have explored the intricacies of building an admin dashboard, overcoming obstacles along the way.

If you're curious to see the fruits of our labor, you can find the live site here, ready to be explored. For those interested in diving into the code, it awaits you on GitHub here.

Now, as I embark on new opportunities, I want to let you know that I'm still open to exploring frontend developer roles. If you or anyone you know is seeking a dedicated and passionate developer, please reach out to me via email at olasunkanmiibalogun@gmail.com. I'm eager to learn more about exciting opportunities that lie ahead.

Thank you once again for being part of this coding journey. Until we meet again in the realm of knowledge and exploration, may your code be bug-free and your programming endeavors be filled with joy and success. Happy coding!

Top comments (0)