Ctrl+k
hero

Coding Concepts Made Practical

Engage with practical coding tasks to improve data processing and management skills

Todo list

// Fetch Data
// Fetch data for recent SpaceX launches from https://api.spacexdata.com/v4/launches. This API provides a wealth of data on each launch, including the mission name, launch date, rocket name, and success status.

// // Display Data
// Render the launches on a LaunchesPage component. Each launch should be displayed in its own component (LaunchCard) with at least the following details:
    // Mission name
    // Rocket name
    // Launch date
    // Launch success status

// // Filtering
// Include a filter by launch success (successful or failed launches).
// Add a date range filter so the user can filter launches within a specific timeframe.

// // Sorting
// Add a sorting option to order the launches by launch date (ascending and descending).
// Optionally, allow sorting by mission name (alphabetically).

// // Pagination (Bonus):
// Implement pagination to make the app scalable as the list of launches grows. Aim for 10 items per page.

// Error Handling and Loading States:
// Display a loading spinner or skeleton while data is being fetched.
// Show an error message if the data fails to load.
Let's Roll

Fetch Data

"use server"

import { launchProps, rocketName } from "@/types"
import axios from "axios"

export const fetchData = async (): Promise<launchProps[]> => {
    try {
        //Fetch all launches
        const launchesRes = await axios.get<launchProps[]>("API")
        const launchData = launchesRes.data;

        //Fetch rocket names
        const launchesWithRocketNames = await Promise.all(
            launchData.map(async (data) => {
                try {
                    const rocketRes = await axios.get<rocketName>('API/{data.rocket}')
                    return {
                    ...data,
                    rocketName: rocketRes.data.name,
                    company: rocketRes.data.company,
                    country: rocketRes.data.country
                    }
                } catch (error) {
                    console.error("Error fetching data", error)
                    return {
                    ...data,
                    rocketName: "Unknown",
                    company: "Unknown",
                    country: "Unknown"
                    }
                }
            })
        )
        return launchesWithRocketNames
    } catch (error) {
        console.error("Error fetching data", error)

        return []
    }
}

Display kan

Display Data

Filter kan
Mission NameRocket NameLaunch DateStatus
"use client"

import React, { useEffect, useState } from 'react'
import { launchProps } from '@/types'

const LaunchData = () => {
    const [launch, setIsLaunch] = useState<launchProps[]>([])
    const [loading, setIsLoading] = useState(false)

    useEffect(() => {
        const launchData = async () => {
            setIsLoading(true)
            const data = await fetchData()
            setIsLaunch(data)
            setIsLoading(false)
        };

        launchData()
    }, [])

    return (
        ..mapping
    )
}
To display the launch data, I structured the component to prioritize efficient data handling and a smooth user experience. I use two separate state variables—one for the data itself launch and another for loading status loading to manage rendering only when necessary, reducing unnecessary re-renders. By placing the data-fetching function inside useEffect and calling it only once with an empty dependency array, the component fetches data only on mount, minimizing API calls. I also manage loading state by toggling loading to true before the fetch and false afterward, allowing users to see a clear loading indicator as data is retrieved. This approach efficiently manages state, optimizes data fetching, and provides a responsive UI.

Filtering Data

Sorting kan
Mission NameRocket NameLaunch DateStatus
No data found
"use client"

import React, { useEffect, useState } from 'react'

const Filtering = () => {
    const [loading, setIsLoading] = useState(false)
    const [launch, setIsLaunch] = useState<launchProps[]>([])
    const [date, setDate] = React.useState<Date>()
    const [statusFilter, setStatusFilter] = useState<"success" | "failure" | "unknown" | null>(null);
    const [filteredLaunches, setFilteredLaunches] = useState<launchProps[]>([]);

    useEffect(() => {
        const filteredData = launch.filter((data) => {
            if (statusFilter === "success") return data.success === true;
            if (statusFilter === "failure") return data.success === false;
            if (statusFilter === "unknown") return data.success === null;
            return true;
        }).filter((data) => {
            if (!date) return true;
            const launchDate = formatDate(data.date_utc)
            return launchDate === formatDate(date)
        })

        setFilteredLaunches(filteredData)
    }, [statusFilter, launch, date])

    return (
        ...filteredLaunches.map
    )
}
For filtering the data, I structured the component to be highly responsive and focused on efficient filtering based on the user's inputs. I maintain states for statusFilter, date, and filteredLaunches, each allowing flexible and intuitive filtering options. The useEffect hook monitors changes in statusFilter, launch, and date, which triggers the filtering process only when these values update. This keeps the component efficient, applying filters in real time without extra rendering cycles.To filter by status, I use a conditional check on statusFilter to ensure only launches matching the selected success status (or null for unknowns) are included. Then, a second filter checks for a specific date match if a date has been selected. By combining these two filter steps, I can manage complex filtering conditions without overwhelming the component's performance. The filteredLaunches state is updated only when the data or filters change, making the UI responsive and able to scale smoothly with different data sizes.

Sorting Data

Pagination
Mission NameRocket NameLaunch DateStatus
No data found
"use client"

import React, { useEffect, useState } from 'react'

const Sorting = () => {
    const [launch, setIsLaunch] = useState<launchProps[]>([])
    const [date, setDate] = React.useState<Date>()
    const [statusFilter, setStatusFilter] = useState<"success" | "failure" | "unknown" | null>(null);
    const [filteredLaunches, setFilteredLaunches] = useState<launchProps[]>([]);
    const [sorted, setSorted] = useState<"dateAsc" | "dateDesc" | "nameAsc" | "nameDesc" | null>(null)

    ...previous useEffect code

    const sortedData = useMemo(() => {
        return sorted === "nameAsc"
            ? [...filteredLaunches].sort((a, b) => a.name.localeCompare(b.name))
            : sorted === "nameDesc"
                ? [...filteredLaunches].sort((a, b) => b.name.localeCompare(a.name))
                : sorted === "dateAsc"
                    ? [...filteredLaunches].sort((a, b) => new Date(a.date_utc).getTime() - new Date(b.date_utc).getTime())
                    : sorted === "dateDesc"
                        ? [...filteredLaunches].sort((a, b) => new Date(b.date_utc).getTime() - new Date(a.date_utc).getTime())
                        : filteredLaunches;
    }, [sorted, filteredLaunches]);

    return (
        ...sortedData.map
    )
}
In this sorting component, I use useMemo instead of useEffect to optimize performance by ensuring that sorting only recalculates when necessary. useMemo caches the sorted result based on its dependencies, meaning it only recalculates when either sorted or filteredLaunches changes. This makes it highly efficient, as the sorted data is stored in memory and avoids re-sorting on every render, which is ideal for large data sets where frequent re-sorting could hurt performance.Using useEffect here would be less efficient because it would trigger the sorting as a side effect and require an additional state to store the sorted result, potentially leading to extra re-renders. With useMemo, the sorted data is computed and returned directly within the component, allowing it to render the sorted data without additional state management. This approach keeps the component fast and minimizes unnecessary calculations, ensuring a smooth user experience even with frequent changes in sorting order.

Pagination

Search kan
Mission NameRocket NameLaunch DateStatus
No data found
"use client"

import React, { useEffect, useState } from 'react'

const PaginationComponent = () => {
    const [sorted, setSorted] = useState<"dateAsc" | "dateDesc" | "nameAsc" | "nameDesc" | null>(null)
    const [page, setPage] = useState(1);

    ...previous code

    const itemsPerPage = 10;
    const totalPages = Math.ceil(sortedData.length / itemsPerPage);

    const onPageChange = (newPage: number) => {
        setPage(newPage);
    };

    const paginatedData = useMemo(() => {
        const startIndex = (page - 1) * itemsPerPage;
        const endIndex = startIndex + itemsPerPage;
        return sortedData.slice(startIndex, endIndex);
    }, [page, sortedData]);

    const hasPrevPage = page > 1;
    const hasNextPage = page < totalPages;

    return (
        ...paginatedData.map

        <PaginationControl
            hasNextPage={hasNextPage}
            hasPrevPage={hasPrevPage}
            totalPages={totalPages}
            onPageChange={onPageChange}
            page={page}
        />
    )
}
In PaginationComponent, I handle the main pagination logic, breaking down data into manageable pages. First, I define itemsPerPage to limit the number of items shown per page and calculate totalPages to know the full range of available pages based on the length of sortedData. The paginatedData is computed using useMemo, slicing sortedData based on the current page and itemsPerPage so that the correct subset of items is rendered for each page.By setting the page state, I can track the user's current page, and onPageChange lets me update this state easily when the user clicks to navigate. I also define hasPrevPage and hasNextPage to check if pagination controls should enable or disable for the previous or next page, respectively. This component then renders the correct page of data paginatedData.map along with the PaginationControl component, which manages the actual pagination buttons.
"use client"

import React, { useEffect, useState } from 'react'

const PaginationControl = ({ ...} : PaginationControlProps) => {
    return (
        <Pagination className='mt-2 flex md:justify-end'>
            ...
            <PaginationPrevious
                onClick={() => {
                    if (hasPrevPage) onPageChange(page - 1)
                }}
                className={'select-none {hasPrevPage ? 'opacity-100 cursor-pointer' : 'opacity-0'} transition max-md:hidden'}
            />
            <PaginationNext
                onClick={() => {
                    if (hasNextPage) onPageChange(page + 1)
                }}
                className={'select-none {hasNextPage ? 'opacity-100 cursor-pointer' : 'opacity-0'} transition max-md:hidden'}
            />
        </Pagination>
    )
}
PaginationControl is a separate component focused on the navigation controls themselves, keeping the UI for pagination clean and modular. It renders buttons for moving to the previous or next page, using PaginationPrevious and PaginationNext elements. Each button checks if it should be active or not-hasPrevPage and hasNextPage determine if there's a previous or next page, respectively.