DAY 34: Mastered React Router & Build a GitHub User Finder App | ReactJS

Ritik KumarRitik Kumar
13 min read

πŸš€ Introduction

Welcome to Day 34 of my Web Development Journey!
After building a solid foundation with HTML, CSS, and JavaScript, I’ve been diving into ReactJS β€” a powerful library for creating dynamic, interactive user interfaces.

Over the past few days, I focused on mastering React Router, exploring routing concepts like route configuration, Link vs NavLink, dynamic routes, and the useParams hook. Alongside, I built a GitHub User Finder App, which really boosted my confidence with React.

To stay consistent and share my learnings, I’m documenting this journey publicly. Whether you’re just starting with React or need a refresher, I hope this blog offers something valuable!

Feel free to follow me on Twitter for real-time updates and coding insights!

πŸ“… Here’s What I Covered Over the Last 3 Days:

Day 31:

  • Deep-dived into React Router
  • Explored route configurations and outlets
  • Learned about dynamic routing
  • Compared Link vs NavLink
  • Practiced using the useParams hook

Day 32:

  • Started building the GitHub User Finder App
  • Created UI components
  • Used useReducer for state management

Day 33:

  • Fetched data from the GitHub API
  • Managed state with dispatch and actions
  • Rendered user data dynamically on the UI
  • Implemented error handling and loading states

Let’s dive into these concepts and the app in more detail below πŸ‘‡


1. React Router:

Here, I’ll explain the core concepts of React Router that I used in my project, including route configuration, outlets, Link vs NavLink, dynamic routing, and the useParams hook. Below is an overview of how these pieces work together in my app.

1. React Router Setup & Route Configuration:

React Router helps in creating client-side routing in React apps, enabling navigation without full page reloads.

In my project, I used the createBrowserRouter API to define the route configuration:

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,       // Shared layout component with header/footer
    children: [
      { index: true, element: <Home /> },          // Default route for "/"
      { path: "about", element: <About /> },       // "/about"
      { path: "contact", element: <Contact /> },   // "/contact"
      { path: "user/:id", element: <User /> },     // Dynamic route with user id
      { path: "github", element: <Github /> },     // "/github"
    ],
  },
]);

createRoot(document.getElementById("root")).render(
  <RouterProvider router={router} />
);
  • The path defines the URL segment.
  • The element is the React component to render for that route.
  • Nested children routes allow creating a nested UI structure.

2. Outlets: Nested Route Rendering

The Layout component wraps all pages and includes common UI elements like header and footer.

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

const Layout = () => {
  return (
    <>
      <Header />
      <Outlet />    {/* Renders the matched child route component here */}
      <Footer />
    </>
  );
};

export default Layout;
  • The <Outlet /> component is a placeholder where the child routes get rendered inside the parent layout.
  • This allows reusing the same header/footer and switching the main content based on the route.
  • Link is used to navigate between routes without refreshing the page.
  • NavLink is similar to Link but adds styling attributes when the link is active, which is useful for navigation menus to highlight the current page.
<NavLink
  to="/about"
  className={({ isActive }) =>
    `... ${isActive ? "text-orange-700" : "text-gray-700"} ...`
  }
>
  About
</NavLink>

Here, the className function receives an isActive boolean that indicates if the link matches the current URL.

  • If isActive is true, the link text will have the text-orange-700 class (highlighted color).
  • If isActive is false, it gets text-gray-700 (normal color).

This dynamic styling makes it easy to visually distinguish the active navigation link.

4. Dynamic Routing with URL Parameters:

Routes like /user/:id include dynamic segments.

{
  path: "user/:id",
  element: <User />,
}
  • The :id is a URL parameter that can represent any user ID.
  • React Router parses this and passes it to the component via the useParams() hook.

5. Using useParams Hook to Access Route Params:

In the User component, I accessed the dynamic id parameter like this:

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

const User = () => {
  const { id } = useParams();    // Extract "id" from the URL params

  return (
    <h1 className="bg-teal-700 py-8 text-4xl text-center text-white font-semibold">
      User : {id}
    </h1>
  );
};

export default User;
  • useParams() returns an object of key-value pairs representing the dynamic segments.
  • This allows components to render content dynamically based on the route.

Final Thoughts:

  • React Router is a powerful library that simplifies navigation and routing in React applications.
  • Using route configuration with nested routes and the <Outlet /> component helps create clean, reusable layouts with shared UI elements like headers and footers.
  • The distinction between Link and NavLink is useful for building navigation menus that clearly indicate the active page with dynamic styling.
  • Dynamic routing with URL parameters (like /user/:id) combined with the useParams hook enables highly flexible and dynamic page rendering based on route data.
  • Mastering these concepts makes building scalable and user-friendly React apps much easier and more maintainable.

2. Github User Finder App:

After learning React hooks like useReducer and handling side effects with useEffect, I wanted to build a practical app that consumes real-world APIs and manages complex state elegantly. This led me to create a GitHub User Finder β€” a sleek tool that lets users search for GitHub profiles and instantly view their details and repositories.

Project Overview:

This GitHub User Finder App allows users to:

  • Search for any GitHub username and fetch profile data dynamically
  • View detailed user info including avatar, bio, location, company, blog, and social links
  • See real-time follower, following, and repository statistics
  • Browse the latest public repositories with metadata like stars, forks, language, and creation date
  • Handle loading states and error messages (e.g., user not found or network errors) gracefully
  • Use environment variables to securely store and use GitHub API tokens
  • Enjoy a responsive and clean UI built with React and Tailwind CSS

The app is built entirely with React functional components and uses useReducer for state management along with multiple useEffect hooks for API calls.

Key Features & Functionalities:

  1. GitHub API Integration

    • User Search: Fetches user profiles via GitHub API
    • Repository Fetching: Automatically retrieves user repositories
    • Authentication: Uses GitHub token for API access
  2. State Management

    • Reducer Pattern: Centralized state management with actions
    • Loading States: Proper loading indicators during API calls
    • Error Handling: Comprehensive error management for various scenarios
  3. User Experience

    • Responsive Design: Mobile-first approach with Tailwind CSS
    • Keyboard Support: Enter key triggers search
    • Real-time Updates: Immediate UI updates based on API responses
  4. Data Display

    • Profile Information: Avatar, bio, location, join date
    • Social Links: Company, blog, Twitter integration
    • Statistics: Followers, following, repository count
    • Repository Details: Name, description, language, stars, forks, creation date
  5. Performance Optimizations

    • Limited Repository Display: Shows only 6 most recent repositories
    • Conditional Rendering: Components render based on data availability
    • Efficient State Updates: Batch updates using SET_ALL action

This project demonstrates a well-structured React application with proper separation of concerns, efficient state management, and a clean component hierarchy for building a GitHub user search and profile viewing application.

React Concepts Applied:

  • useReducer β€” to manage complex state including query, search term, user data, loading state, error messages, and repos.
  • useEffect β€” two separate effects: one watches for searchTerm changes to fetch user data, the other watches userData to fetch repositories.
  • Environment Variables β€” for storing the GitHub token securely (import.meta.env.VITE_GITHUB_TOKEN).
  • Component Composition β€” breaking the UI into reusable components like SearchUser, UserDetails, ProfileStats, RepositoryList, and RepositoryCard.
  • Conditional Rendering β€” to handle loading screens, errors, and initial empty states.
  • Utility Functions β€” like formatDate to format dates neatly for UI.

Folder Structure:

src/
β”œβ”€β”€ App.jsx
β”œβ”€β”€ index.css 
β”œβ”€β”€ main.jsx 
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ Button.jsx 
β”‚   β”œβ”€β”€ ProfileStats.jsx 
β”‚   β”œβ”€β”€ RepositoryCard.jsx 
β”‚   β”œβ”€β”€ RepositoryList.jsx 
β”‚   β”œβ”€β”€ SearchUser.jsx 
β”‚   β”œβ”€β”€ StatsCard.jsx 
β”‚   └── UserDetails.jsx 
└── utils/
    └── formatDate.js

Code Walkthrough:

1. Main Entry & State Management (App.jsx)

The app uses useReducer to handle complex state transitions related to the input query, search term, loading state, error messages, user data, and repository lists.

The ACTIONS object defines all possible action types to update specific parts of the state. The initialState sets the default values for all these properties.

The reducer function listens for dispatched actions and updates the state accordingly:

  • For example, SET_QUERY updates the search input value,
  • SET_SEARCH_TERM triggers the actual search,
  • SET_USER_DATA stores fetched user profile data,
  • SET_ERROR handles error messages,
  • SET_LOADING manages loading indicators,
  • SET_REPOS saves the list of repositories,
  • SET_ALL allows batch updates of multiple state properties at once.

This pattern centralizes and organizes state changes in a predictable way, making state management more maintainable as the app grows.

export const ACTIONS = {
  SET_QUERY: "set_query",
  SET_USER_DATA: "set_user_data",
  SET_SEARCH_TERM: "set_search_term",
  SET_ERROR: "set_error",
  SET_LOADING: "set_loading",
  SET_REPOS: "set_repos",
  SET_ALL: "set_all",
};

const initialState = {
  query: "",
  userData: null,
  searchTerm: "",
  error: "",
  loading: false,
  repos: [],
};

const reducer = (state, action) => {
  switch (action.type) {
    case ACTIONS.SET_QUERY:
      return { ...state, query: action.query };
    case ACTIONS.SET_SEARCH_TERM:
      return { ...state, searchTerm: action.searchTerm };
    case ACTIONS.SET_USER_DATA:
      return { ...state, userData: action.data };
    case ACTIONS.SET_ERROR:
      return { ...state, error: action.message };
    case ACTIONS.SET_LOADING:
      return { ...state, loading: action.loading };
    case ACTIONS.SET_REPOS:
      return { ...state, repos: action.repos };
    case ACTIONS.SET_ALL:
      return { ...state, ...action.payload };

    default:
      return state;
  }
};

2. Fetching User Data & Repositories

Two useEffect hooks orchestrate data fetching:

  1. The first useEffect listens for changes in the searchTerm state. When a new search term is set, it triggers a fetch request to get the GitHub user’s profile data.

    useEffect(() => {
     if (!state.searchTerm.trim()) return;
     if (!token) {
       dispatch({
         type: ACTIONS.SET_ALL,
         payload: { error: "GitHub token missing", query: "" },
       });
       return;
     }
     dispatch({
       type: ACTIONS.SET_ALL,
       payload: { loading: true, error: "", repos: [] },
     });
    
     fetch(`https://api.github.com/users/${state.searchTerm}`, {
       headers: {
         Authorization: `Bearer ${token}`,
       },
     })
       .then((res) => {
         return res.json();
       })
       .then((data) => {
         if (data?.message === "Not Found") {
           dispatch({
             type: ACTIONS.SET_ALL,
             payload: {
               error: "No User Found",
               query: "",
               loading: false,
             },
           });
           return;
         }
    
         dispatch({
           type: ACTIONS.SET_ALL,
           payload: {
             userData: data,
             query: "",
             error: "",
             loading: false,
           },
         });
       })
       .catch((err) => {
         dispatch({
           type: ACTIONS.SET_ALL,
           payload: {
             error: "Network Error",
             query: "",
             loading: false,
           },
         });
       });
    }, [state.searchTerm]);
    
    • Before fetching, it checks if the search term is empty or if the GitHub token is missing and handles these cases with appropriate error messages.
    • While fetching, it sets the loading state to show a spinner or loading text.
    • On success, it updates the state with the user data and clears any previous errors.
    • If the user is not found, it sets an error message accordingly.
    • Any network errors during fetch also update the state with an error message.

The token is included in the API request headers for authentication.

  1. The second useEffect runs when userData becomes available after a successful user fetch. It then fetches the user’s repositories separately from the URL provided in userData.repos_url.

    useEffect(() => {
     if (!state.userData) return;
     fetch(state.userData.repos_url)
       .then((res) => res.json())
       .then((data) => {
         if (!Array.isArray(data)) {
           dispatch({
             type: ACTIONS.SET_ALL,
             payload: { error: "Repos not available", repos: [] },
           });
           return;
         }
    
         dispatch({
           type: ACTIONS.SET_ALL,
           payload: { repos: data, error: "" },
         });
       })
       .catch((err) => {
         console.log(err);
         dispatch({ type: ACTIONS.SET_ERROR, message: "Repos Not Found" });
       });
    }, [state.userData]);
    
    • It validates the repository data to ensure it’s an array before updating the state.
    • If repositories aren’t available or an error occurs, it sets an error message related to repo fetching.

3. Search Input (SearchUser.jsx)

This component manages the user input for the GitHub username search. It updates the query state as the user types and triggers the search when the user either clicks the search button or presses the Enter key.

By dispatching the SET_SEARCH_TERM action with the current query, it updates the searchTerm in the global state, which in turn initiates the data fetching process in the main component.

const SearchUser = ({ dispatch, state }) => {
  const handleSearch = () => {
    if (!state.query.trim()) return;
    dispatch({ type: ACTIONS.SET_SEARCH_TERM, searchTerm: state.query });
  };
  // input field and search button UI 
};

4. User Details (UserDetails.jsx)

This component displays detailed information about the GitHub user, including their avatar, name, bio, location, and formatted join date. It also shows the user's company, blog domain, and Twitter link if available. A button is provided to directly visit the user's GitHub profile.

The component uses helper subcomponents for cleaner organization:

  • ProfileStats: Displays the count of followers, following, and public repositories.
  • RepositoryList: Shows up to 6 of the user’s latest repositories with details like name, description, language, stars, forks, and creation date.

5. Repository List:

The RepositoryList component is responsible for displaying the user's repositories. It renders a heading and then displays up to 6 of the most recent repositories in a responsive grid layout.

Each repository is passed as props to the RepositoryCard component, which shows details such as:

  • Repository name
  • Description (or a fallback message if none)
  • Star count
  • Primary language
  • Fork count
  • Creation date (formatted)

This component helps keep the repository display concise and organized for better user experience.

const RepositoryList = ({ repos }) => {
  return (
    <div className="">
      <h2 className="mt-2 border-b-2 border-[#323232] pb-4 text-xl font-bold">
        Latest Repositories
      </h2>
      <div className="mt-4 grid gap-4 md:grid-cols-2">
        {repos.slice(0, 6).map((repo) => {
          return (
            <RepositoryCard
              key={repo.id}
              name={repo.name}
              desc={repo.description}
              star={repo.stargazers_count}
              lang={repo.language}
              fork={repo.forks_count}
              date={formatDate(repo.created_at)}
            />
          );
        })}
      </div>
    </div>
  );
};

6. Repository Cards (RepositoryCard.jsx)

Each repository card presents key information about a repository in a clean, organized layout. It displays:

  • Repository name with a code-branch icon
  • Description, or a fallback message if no description is provided
  • Primary programming language with a colored circle icon
  • Star count with a star icon
  • Fork count with a fork icon
  • Creation date with a clock icon

Icons alongside text improve clarity and visual appeal, making it easy for users to scan repository details quickly.

7. Utility (formatDate.js)

This utility function formats date strings received from the GitHub API into a more readable, short date format. For example, it converts "2023-01-01T00:00:00Z" into "Jan 1, 2023", improving the display of dates throughout the app.

export const formatDate = (dateStr) => {
  const date = new Date(dateStr);

  const formattedDate = date.toLocaleDateString("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
  });

  return formattedDate;
};

UI & UX Details:

  • Dark-themed UI with purple accents, styled using Tailwind CSS
  • Clear loading indicators and user-friendly error messages
  • Responsive design optimized for both desktop and mobile devices
  • Smooth user experience with instant updates and intuitive navigation

GitHub Repository:

You can check out the full source code of this project here:
GitHub - Github User Finder App

Final Thoughts:

This GitHub User Finder project gave me hands-on experience with:

  • Managing complex state transitions cleanly with useReducer.
  • Coordinating multiple API calls with dependent data.
  • Handling loading and error states comprehensively.
  • Building modular, reusable React components with clear responsibilities.
  • Securely using environment variables for sensitive tokens.
  • Applying real-world API integration in React apps with responsive and accessible UI.

The app serves as a practical example of combining React hooks, API consumption, and thoughtful UI design to build a useful developer tool.


3. What’s Next:

I’m excited to keep growing and sharing along the way! Here’s what’s coming up:

  • Posting new blog updates every 3 days to share what I’m learning and building.
  • Diving deeper into Data Structures & Algorithms with Java β€” check out my ongoing DSA Journey Blog for detailed walkthroughs and solutions.
  • Sharing regular progress and insights on X (Twitter) β€” feel free to follow me there and join the conversation!

Thanks for being part of this journey!


4. Conclusion:

In this blog of my web dev journey, I explored two important parts of modern React development:

  • React Router: I demonstrated how to set up client-side routing with nested routes, dynamic URL parameters, and navigation links that reflect the active page. Understanding these concepts makes it easier to build scalable React apps with clean, maintainable navigation structures.

  • GitHub User Finder App: I built a practical application consuming GitHub's API using React hooks like useReducer and useEffect. This project showcased how to manage complex state transitions, coordinate multiple API calls, handle loading and error states gracefully, and create a responsive, user-friendly interface with Tailwind CSS.

Together, these topics reinforce key React patterns and best practices, from routing to state management to API integration, helping to develop solid skills for building real-world web applications.

Feel free to check out the full source code on GitHub and try building on top of this foundation to create even more powerful React projects!

Thanks for reading, and feel free to connect or follow along as I continue building and learning.

1
Subscribe to my newsletter

Read articles from Ritik Kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ritik Kumar
Ritik Kumar

πŸ‘¨β€πŸ’» Aspiring Software Developer | MERN Stack Developer.πŸš€ Documenting my journey in Full-Stack Development & DSA with Java.πŸ“˜ Focused on writing clean code, building real-world projects, and continuous learning.