My First React App: The Stuff No Tutorial Taught Me
Finished a React tutorial but feel lost? This guide covers the real-world challenges no tutorial teaches: state management, project structure, deployment, and more.
Elena Petrova
A senior front-end developer passionate about making web development accessible for everyone.
You did it. You battled through the tutorials, you’ve built a dozen to-do list apps, and you finally understand what a “hook” is (mostly). You feel the power of React flowing through your fingertips. Then you start your first real project, and a wave of cold panic sets in. Suddenly, the clean, simple world of the tutorial shatters.
Sound familiar? This post is for you. It’s about all the messy, crucial, and ultimately empowering stuff that no tutorial taught me when I built my first real React app.
Beyond create-react-app: The Configuration Abyss
Every React journey begins with that magical command: npx create-react-app my-app
. It’s a beautiful, self-contained universe where everything just works. You have hot-reloading, optimized builds, and testing set up out of the box. It’s perfect... until it isn't.
The moment you need to do something slightly custom—like adding support for a specific CSS preprocessor setup or modifying the underlying Webpack configuration—you hit a wall. The official advice is to npm run eject
.
Ejecting is a one-way ticket. It unpacks all the hidden scripts and configuration files into your project. On one hand, you get total control. On the other, you’re now responsible for maintaining dozens of dependencies and complex config files. For a beginner, this is terrifying.
The Real-World Solution
Before you eject, know your alternatives:
- Modern Build Tools: Tools like Vite are now the preferred starting point for many developers. They offer the same batteries-included experience as Create React App (CRA) but are significantly faster and often have simpler configuration.
- Configuration Overrides: Libraries like CRACO (Create React App Configuration Override) let you tap into the CRA configuration and tweak it without ejecting. It’s a great middle ground.
The lesson: You don’t need to be a Webpack guru from day one, but know that the pristine garden of create-react-app
has fences, and there are modern, less-destructive ways to jump over them.
State Management Hell: When useState Isn't Enough
Tutorials teach you useState
. It’s simple, elegant, and perfect for managing the state of a single component, like a counter or a form input. So, you start building your app, and you use useState
everywhere.
Then you need the logged-in user’s name in the navbar, their profile page, and a welcome message on the dashboard. How do you get it there? You start passing props down from your main App
component. This is called “prop drilling,” and it’s a path to madness. You’ll find yourself passing props through five, six, or even ten layers of components that don’t even need them.
Choosing Your Weapon for State
When prop drilling becomes painful, it’s time to adopt a proper state management strategy. Here are the main contenders:
- React Context API: Built into React, the Context API lets you create a “provider” that makes state available to any child component that needs it, no matter how deep it is. It’s perfect for low-frequency updates like theme (dark/light mode) or user authentication status.
- Dedicated State Libraries: For complex application state that changes often (think a shopping cart, filters on a search page, or real-time data), a dedicated library is your best bet. Redux Toolkit is the modern standard for robust, predictable state. For something simpler, libraries like Zustand or Jotai offer a minimal API with powerful features.
Comparison: State Management Approaches
Feature | React Context API | Redux Toolkit | Zustand |
---|---|---|---|
Best For | Low-frequency updates (theme, auth) | Complex, large-scale application state | Simple to complex global state |
Boilerplate | Low | Medium (but much better than old Redux) | Minimal |
Learning Curve | Easy | Moderate | Very Easy |
Rerenders | Can cause extra rerenders if not optimized | Highly optimized with selectors | Optimized by default |
Where Does Everything Go? Architecting a Scalable Project
Most tutorials leave you with a src
folder containing App.js
, index.js
, and maybe a single components
folder. This is fine for a to-do list, but in a real application with dozens or hundreds of components, it becomes an unnavigable mess.
A scalable folder structure is one of the most important things you can establish early on. Here are two popular approaches:
- Type-Based Structure: You group files by their type. This is intuitive at first.
/src
/components
/hooks
/pages
/utils
/services
- Feature-Based Structure: You group files related to the same feature. This scales incredibly well because it keeps related logic co-located.
/src
/components (for truly shared, reusable components like buttons)
/features
/authentication
- Login.jsx
- useAuth.js
- authSlice.js
/dashboard
- Dashboard.jsx
- Chart.jsx
- useDashboardData.js
/lib (for shared utilities)
The lesson: Don't just dump files into src/
. Start with a feature-based structure. Your future self will thank you when you can easily find and work on all the code related to a single piece of functionality.
The useEffect Minefield: Side Effects and Infinite Loops
The useEffect
hook is the Swiss Army knife of React, used for data fetching, subscriptions, and any other “side effect.” Tutorials often show this simple pattern:
useEffect(() => {
fetchData();
}, []); // The empty array means "run once on mount"
This seems straightforward, but useEffect
is a minefield of common bugs.
The Dependency Array is Everything
That second argument, the dependency array, is the source of most useEffect
problems.
- Stale Data: If your effect uses a prop or state value but you forget to include it in the dependency array, the effect will run with the initial value and never update when the value changes.
- Infinite Loops: If you include a function, object, or array that is re-created on every render in the dependency array, your effect will run in an infinite loop, crashing your app. You can solve this by wrapping functions in
useCallback
or moving them outside the component.
The Forgotten Cleanup Function
What if your effect sets up a subscription or a timer? If the component unmounts, that subscription or timer keeps running in the background, leading to memory leaks and bugs. The useEffect
hook has a built-in mechanism for this: the cleanup function.
useEffect(() => {
const timerId = setTimeout(() => {
console.log("Timer fired!");
}, 1000);
// This function runs when the component unmounts
return () => {
clearTimeout(timerId);
};
}, []);
Always ask yourself: “Does my effect need cleaning up?” If it does, return a cleanup function.
Secrets Are For Servers: Handling Environment Variables
In a tutorial, you might see this: const API_KEY = 'supersecretkey123';
. In a real project, this is a massive security vulnerability. Committing API keys, tokens, or other secrets to a public Git repository is a recipe for disaster.
The correct way to handle this is with environment variables. Create a file named .env.local
in your project's root directory. Crucially, add .env.local
to your .gitignore
file!
Inside .env.local
, you can define your secrets. For apps made with Create React App or Vite, they must be prefixed:
# .env.local
REACT_APP_API_KEY=supersecretkey123
VITE_API_KEY=supersecretkey123
You can then access this in your code as process.env.REACT_APP_API_KEY
. The build tool replaces this with the actual value during the build process, keeping it out of your source code.
From localhost:3000 to the World: Demystifying Deployment
Your app works perfectly on localhost:3000
. Now what? Tutorials often end here, but deployment is a critical final step. Running npm run build
generates a build
(or dist
) folder with static HTML, CSS, and JavaScript files. This folder is your entire app.
Modern platforms like Vercel and Netlify make deployment incredibly simple:
- Push your code to GitHub.
- Connect your GitHub account to Vercel/Netlify.
- Point it to your repository.
The platform will automatically detect it's a React app, run the build command, and deploy the contents of your build folder. But there’s one final “gotcha.”
The Client-Side Routing Problem
Your app works. You click a link to /about
and it loads. But if you refresh the page at yourapp.com/about
, you get a 404 Not Found error. Why? Because your host is looking for a file named about
, which doesn't exist. All of your app's logic lives in index.html
, and React Router handles the rest on the client side.
The solution is a rewrite rule. You need to tell your hosting provider to redirect all page requests to your index.html
file. For Netlify, you create a _redirects
file in your public
folder with this line:
/* /index.html 200
Vercel handles this automatically for most frameworks, but you can configure it with a vercel.json
file if needed.
Final Takeaways: Your Real-World React Checklist
Building a real app is a different game. The tutorials give you the pieces, but you have to learn how to assemble them. Here’s what to remember:
- Plan Your State: Before you write a single line of code, think about where your state will live. Don’t default to
useState
for everything.- Structure for the Future: A good folder structure is your best friend. A feature-based approach will save you headaches down the line.
- Respect
useEffect
: Master the dependency array and always add a cleanup function for subscriptions or timers. Use an ESLint plugin to help you.- Never Commit Secrets: Use
.env
files for all secrets and ensure they are in your.gitignore
.- Learn Your Deployment Platform: Understand the basics of how your app is built and served, especially how to handle client-side routing.
The gap between the tutorial and the real world can feel huge, but every one of these challenges is a learning opportunity. Embrace the mess, solve the problems, and you'll become a much stronger developer for it. Happy coding!