A React application that displays user reviews in a card layout. Users can navigate through reviews with previous/next buttons, jump to a random review with "surprise me," and see each reviewer's photo, name, job, and quote. This project is built for learning React fundamentals: state with useState, list indexing, event handlers, and conditional rendering—all without a backend or API.
- Live Demo: https://reviews-display.vercel.app/
- Introduction
- Project Structure
- Technology Stack
- Features & Functionalities
- How to Run & Use
- Environment Variables & .env
- Components & How It Works
- Data & API
- Teaching Walkthrough
- Reusing Components in Other Projects
- Keywords
- Conclusion
- License
- Happy Coding!
This repository is Fundamental Project 3 in a React learning path. It focuses on:
- Managing a single index state to control which review is shown.
- Bounded navigation: wrapping from last → first and first → last (carousel behavior).
- Random selection with simple logic to avoid showing the same review twice in a row.
- Static data in a JavaScript module (no backend or REST API), so you can run it locally with no server or keys.
The UI is a single card: avatar, quote icon, author name, job title, review text, and three controls (prev, next, surprise me). Styling uses plain CSS with custom properties in index.css.
03-reviews/
├── index.html # Entry HTML; root div, meta tags, script to main.jsx
├── package.json # Dependencies (React, Vite, react-icons) and scripts
├── vite.config.js # Vite config; React plugin
├── eslint.config.js # ESLint 9 flat config for React/JSX
├── public/
│ └── vite.svg # Favicon / default asset
├── src/
│ ├── main.jsx # React root: createRoot, renders <App />
│ ├── App.jsx # Main component: state, navigation, review card UI
│ ├── Alternative.jsx # Alternate implementation using modulus (%) for index
│ ├── data.js # Array of review objects (id, name, job, image, text)
│ └── index.css # Global styles and review card layout
└── README.md
- No routes — single page; one view.
- No backend — all data lives in
src/data.js. - Entry point —
index.htmlloadssrc/main.jsx, which mountsAppinto#root.
| Layer | Technology |
|---|---|
| Framework | React 18 |
| Build tool | Vite 4 |
| Language | JavaScript (ES modules) |
| Icons | react-icons (Font Awesome: FaChevronLeft, FaChevronRight, FaQuoteRight) |
| Styling | Vanilla CSS (custom properties, no preprocessor) |
| Linting | ESLint 9 (flat config) + React + React Hooks + React Refresh |
-
Display one review at a time
Shows current reviewer's image, name, job, and quote text. -
Previous / Next
Buttons move to the previous or next review. At the ends, index wraps (last → first, first → last). -
Surprise me
Picks a random index and shows that review. Logic avoids repeating the same review if possible. -
Responsive card layout
Centered card with image, quote icon overlay, and buttons; layout and typography defined inindex.css. -
No API or backend
All data is insrc/data.js; images use external URLs (e.g. course-api.com). No auth or env secrets required to run.
- Node.js (v16+ recommended) and npm.
# Clone the repo (replace with your repo URL)
git clone <repository-url>
cd 03-reviews
# Install dependencies
npm install
# Start development server (e.g. http://localhost:5173)
npm run dev# Production build (output in dist/)
npm run build
# Preview production build locally
npm run preview
# Lint source files
npm run lint
# Lint and auto-fix where possible
npm run lint:fix- Open the dev URL (e.g.
http://localhost:5173) in a browser. - Use < and > to move to previous/next review.
- Click surprise me to show a random review.
This project does not use any environment variables for its current features. All data is in src/data.js and image URLs are hardcoded there. You can run, build, and preview without a .env file.
If you later add a backend or API:
-
Create a
.envfile in the project root (same level aspackage.json). -
Use the
VITE_prefix so Vite exposes them to the client:VITE_API_BASE_URL=https://api.example.com VITE_APP_NAME=Reviews Display
-
Access in code via
import.meta.env:const apiBase = import.meta.env.VITE_API_BASE_URL;
-
Do not commit secrets (API keys, tokens) to the repo. Add
.envto.gitignoreif it contains sensitive data; use.env.exampleto document required variable names only.
For this educational project, no .env setup is required.
- Imports React, ReactDOM,
App, andindex.css. - Creates a root with
ReactDOM.createRoot(document.getElementById('root')). - Renders
<App />inside<React.StrictMode>.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);- State:
const [index, setIndex] = useState(0);— which review (0-based) is shown. - Current review:
const { name, job, image, text } = people[index];from thepeoplearray imported fromdata.js. - Helpers:
checkNumber(number)— keeps index in[0, people.length - 1]by wrapping (e.g. 4 → 0, -1 → 3).nextPerson/prevPerson— increment/decrement index usingcheckNumber.randomPerson—Math.floor(Math.random() * people.length); if it equals current index, add 1 then wrap withcheckNumber.
- UI: One
<main>with an<article class="review">: image container with quote icon, author name, job, text, prev/next buttons, and "surprise me" button. Icons fromreact-icons/fa:FaChevronLeft,FaChevronRight,FaQuoteRight.
Flow: user clicks → handler calls setIndex → React re-renders → people[index] and thus the card content update.
Same UI and behavior as App.jsx, but index wrapping uses the modulus operator instead of checkNumber:
- Next:
(index + 1) % people.length - Previous:
(index - 1 + people.length) % people.length - Random:
randomNumber % people.lengthfor the new index
Useful for teaching how % gives a cyclic 0 … n-1 index.
Exports one array, reviews (imported as people in App/Alternative). Each item:
{
id: 1,
name: 'susan smith',
job: 'web developer',
image: 'https://www.course-api.com/images/people/person-1.jpeg',
text: '...'
}No API calls; this is the only “data layer” in the project.
- Data source:
src/data.js— a static array. No REST API, no GraphQL, no backend. - Images: URLs point to external CDN (e.g.
https://www.course-api.com/images/people/...). No auth. - Routes: None. Single page; no router.
- State: Only the current index in React state; no global store or server state.
To plug in a real API later, you could replace the people import with data from fetch(import.meta.env.VITE_API_BASE_URL + '/reviews') and use useState/useEffect (or a data-fetching library) to hold the list and loading/error state.
Open src/data.js. See the array of objects with id, name, job, image, text. This is the only “database” the app uses.
In App.jsx, a single state holds the position:
const [index, setIndex] = useState(0);
const { name, job, image, text } = people[index];Changing index changes which object is shown. No need to store the whole list in state here.
Two approaches:
- Conditionals (App.jsx):
checkNumberclamps/wraps the index so it never goes out of bounds. - Modulus (Alternative.jsx):
(index + 1) % people.lengthand(index - 1 + people.length) % people.lengthachieve the same wrap in one expression.
Good moment to explain % (remainder) and how it gives a repeating 0 … n-1 cycle.
let randomNumber = Math.floor(Math.random() * people.length);
if (randomNumber === index) randomNumber = index + 1;
setIndex(checkNumber(randomNumber)); // or % people.lengthTeaches: random index, avoiding “same again,” and reusing the same wrapping logic.
Icons are imported from react-icons/fa and rendered as components:
import { FaChevronLeft, FaChevronRight, FaQuoteRight } from 'react-icons/fa';
// ...
<FaChevronLeft /> <FaChevronRight /> <FaQuoteRight />No image files or icon fonts to manage; good for rapid UI and teaching component composition.
You could teach component reuse by extracting the card:
// ReviewCard.jsx
export default function ReviewCard({
name,
job,
image,
text,
onPrev,
onNext,
onRandom,
}) {
return (
<article className="review">{/* ... same JSX using props ... */}</article>
);
}Then in App.jsx: <ReviewCard {...people[index]} onPrev={prevPerson} onNext={nextPerson} onRandom={randomPerson} />. Same behavior, clearer separation of layout and logic.
- Review card UI — Copy the
<article className="review">block (and its CSS fromindex.css) into another React app. Drive it with props:name,job,image,text, and optionalonPrev,onNext,onRandom. You can keep usingreact-iconsor swap to another icon set. - Index + wrap logic — Reuse
checkNumberand the next/prev/random handlers in any carousel or “one-of-n” viewer (tabs, slides, testimonials). Replacepeoplewith your own array. - Data shape — Use the same object shape (
id,name,job,image,text) for other review/testimonial UIs, or extend it (e.g.rating,date) and still use the same navigation pattern. - CSS — The
:rootvariables and.reviewstyles inindex.csscan be copied or adapted; change--primary-*and spacing to match your design system.
React, Vite, useState, carousel, reviews, testimonials, index state, modulus, wrap-around navigation, random selection, react-icons, single-page app, static data, no backend, no API, educational project, fundamental project, JavaScript, CSS custom properties, ESLint.
This project is a minimal React app that teaches:
- State: one number (index) controlling what’s on screen.
- Event handlers: buttons updating that index.
- List indexing and wrapping: conditionals or modulus for prev/next.
- Random choice:
Math.random()and bounds. - Composition: one main component reading from a data module and rendering a card with icons.
It uses no backend, no API, and no environment variables, so it’s easy to clone and run for learning or as a starting point for a reviews/testimonials section in a larger site.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
This is an open-source project — feel free to use, enhance, and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.
Enjoy building and learning! 🚀
Thank you! 😊
