🔥 As much as I know, I have twice to learn
Are you tired of React tutorials that leave you with boring, useless projects? Struggling to stay motivated while your peers are out partying? Good. While they sleep, you build. This intensive 90-minute tutorial is for the night owls, the grinders, the future developers who understand that the path to success isn't comfortable.
Let's build something employers actually want to see: a professional news application with real-world API integration, clean routing, and state management. If you can follow along at 3AM, you've got what it takes.
📋 What We're Building
A professional-grade News App that:
- Fetches real data from a public News API
- Implements proper routing with React Router
- Features search functionality
- Displays article details
- Uses modern React patterns and hooks
⏱️ Jump to Section:
- 00:00 – 00:10 | Setup & Environment
- 00:10 – 00:20 | File Structure & Routing
- 00:20 – 00:30 | Building the Layout
- 00:30 – 00:45 | Fetching & Displaying Articles
- 00:45 – 00:55 | Article Details Page
- 00:55 – 01:10 | Code Review & Concepts
- 01:10 – 01:30 | Extensions & Advanced Features
Setup & Environment (00:00 – 00:10)
First, let's set up our project environment. Open your terminal and run:
npx create-react-app react-news-app
cd react-news-app
npm install react-router-dom
Next, we need an API key. Go to newsapi.org, sign up for a free account, and get your API key.
Create a .env
file in your project root folder with:
REACT_APP_NEWS_API_KEY=your_api_key_here
⚠️ Security Note: In a real-world application, you'd handle API keys on the server side. For this tutorial, we're using client-side for simplicity.
Quick Check: Did your project folder structure get created correctly? Run ls
or dir
to confirm all files are in place.
File Structure & Routing (00:10 – 00:20)
Now, let's set up our file structure and basic routes. Create these folders and files:
src/
├── pages/
│ ├── Home.js
│ └── ArticleDetails.js
├── Layout.js
└── App.js (update this file)
Open App.js
and replace its contents with:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Layout from './Layout';
import Home from './pages/Home';
import ArticleDetails from './pages/ArticleDetails';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="article/:index" element={<ArticleDetails />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
Understanding the Routes:
-
<BrowserRouter>
: Provides routing capabilities to our app -
<Routes>
: Contains all our route definitions -
<Route path="/" element={<Layout />}>
: Our parent route with the shared layout -
<Route index element={<Home />}>
: The default route (homepage) -
<Route path="article/:index" element={<ArticleDetails />}>
: Dynamic route for article details
What's that :index
thing? This is a URL parameter. When a user visits /article/5
, the value 5
will be accessible in the ArticleDetails component using useParams()
. We'll explore this more in a later section.
Building the Layout (00:20 – 00:30)
Now, let's create our Layout.js
file with navigation and an <Outlet />
to display nested routes:
import { Link, Outlet } from 'react-router-dom';
function Layout() {
return (
<div style={{
maxWidth: '800px',
margin: '0 auto',
padding: '20px',
fontFamily: 'Arial, sans-serif'
}}>
<header>
<h1>React News App</h1>
<nav>
<Link to="/" style={{
textDecoration: 'none',
color: '#0066cc',
fontWeight: 'bold'
}}>Home</Link>
</nav>
</header>
<hr />
<main>
<Outlet />
</main>
</div>
);
}
export default Layout;
Understanding <Outlet />
:
Think of <Outlet />
as a placeholder where child routes will be rendered. In our case:
- When user visits
/
→ Home component renders inside<Outlet />
- When user visits
/article/5
→ ArticleDetails component renders inside<Outlet />
This pattern allows us to maintain consistent UI elements (header, navigation, footer) across different pages without duplicating code.
Example Scenario:
- User clicks "Home" link → URL changes to
/
- React Router finds matching route in
App.js
- Layout component renders first
- Inside Layout,
<Outlet />
renders the Home component
Fetching & Displaying Articles (00:30 – 00:45)
Now for the core of our app: fetching and displaying news articles. Create Home.js
with:
import { useState, useEffect } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
function Home() {
const [articles, setArticles] = useState([]);
const [loading, setLoading] = useState(true);
const [searchParams, setSearchParams] = useSearchParams();
const [query, setQuery] = useState(searchParams.get('q') || 'technology');
const apiKey = process.env.REACT_APP_NEWS_API_KEY;
useEffect(() => {
setLoading(true);
fetch(`https://newsapi.org/v2/everything?q=${query}&apiKey=${apiKey}`)
.then(res => res.json())
.then(data => {
setArticles(data.articles || []);
setLoading(false);
})
.catch(err => {
console.error("Error fetching news:", err);
setLoading(false);
});
}, [query, apiKey]);
const handleSearch = (e) => {
e.preventDefault();
setSearchParams({ q: query });
};
return (
<div>
<form onSubmit={handleSearch} style={{ marginBottom: '20px' }}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
style={{ padding: '8px', width: '70%' }}
placeholder="Search for news..."
/>
<button
type="submit"
style={{
padding: '8px 16px',
backgroundColor: '#0066cc',
color: 'white',
border: 'none',
marginLeft: '10px'
}}
>
Search
</button>
</form>
{loading ? (
<p>Loading articles...</p>
) : (
<div>
{articles.length === 0 ? (
<p>No articles found. Try another search term.</p>
) : (
articles.map((article, index) => (
<div
key={index}
style={{
marginBottom: '20px',
padding: '15px',
borderRadius: '5px',
boxShadow: '0 2px 5px rgba(0,0,0,0.1)'
}}
>
<h3>
<Link
to={`/article/${index}`}
state={{ articles }}
style={{ color: '#0066cc', textDecoration: 'none' }}
>
{article.title}
</Link>
</h3>
<p style={{ color: '#666', fontSize: '0.9em' }}>
{article.source.name} | {new Date(article.publishedAt).toLocaleDateString()}
</p>
<p>{article.description?.slice(0, 150)}...</p>
</div>
))
)}
</div>
)}
</div>
);
}
export default Home;
Understanding useSearchParams
:
useSearchParams
is a hook from React Router that gives us access to URL query parameters (everything after the ?
in a URL).
Let's break down how we're using it:
const [searchParams, setSearchParams] = useSearchParams();
const [query, setQuery] = useState(searchParams.get('q') || 'technology');
-
searchParams
works like a Map object - we useget('q')
to read the value of theq
parameter -
setSearchParams
lets us update the URL query parameters - If no
q
parameter exists in URL, we default to "technology"
Example:
- When user visits
/
→ query defaults to "technology" - When user searches for "bitcoin" → URL becomes
/?q=bitcoin
- If user refreshes page or shares URL, the search is preserved
The Search Flow:
const handleSearch = (e) => {
e.preventDefault();
setSearchParams({ q: query });
};
- User types "climate change" in the input field
- User clicks "Search" button
-
handleSearch
prevents default form submission -
setSearchParams({ q: query })
updates URL to/?q=climate%20change
- URL change triggers our
useEffect
hook - We fetch new articles matching "climate change"
This pattern creates bookmarkable search pages and maintains a clean user experience.
Article Details Page (00:45 – 00:55)
Now, let's create ArticleDetails.js
to show the full article when clicked:
import { useParams, useLocation, Link } from 'react-router-dom';
function ArticleDetails() {
const { index } = useParams();
const location = useLocation();
const articles = location.state?.articles || [];
const article = articles[index];
if (!article) {
return (
<div>
<p>Article not found or expired. Please go back to search.</p>
<Link to="/" style={{ color: '#0066cc' }}>Back to Home</Link>
</div>
);
}
return (
<div>
<div style={{ marginBottom: '20px' }}>
<Link to="/" style={{ color: '#0066cc', textDecoration: 'none' }}>
← Back to Articles
</Link>
</div>
<h2>{article.title}</h2>
<div style={{ color: '#666', margin: '10px 0' }}>
<p>Source: {article.source.name}</p>
<p>Published: {new Date(article.publishedAt).toLocaleString()}</p>
{article.author && <p>Author: {article.author}</p>}
</div>
{article.urlToImage && (
<img
src={article.urlToImage}
alt={article.title}
style={{ maxWidth: '100%', marginBottom: '20px' }}
/>
)}
<p>{article.content || article.description}</p>
<a
href={article.url}
target="_blank"
rel="noreferrer"
style={{
display: 'inline-block',
marginTop: '20px',
padding: '10px 15px',
backgroundColor: '#0066cc',
color: 'white',
textDecoration: 'none',
borderRadius: '4px'
}}
>
Read Full Article
</a>
</div>
);
}
export default ArticleDetails;
Understanding useParams
:
useParams
is a React Router hook that gives us access to dynamic parameters in the URL path.
const { index } = useParams();
When a user visits /article/5
:
- React Router matches the path pattern
article/:index
-
useParams()
returns{ index: "5" }
- We destructure to get the
index
value - We use this value to find the corresponding article in our articles array
The Data Transfer Method:
Notice how we passed data between pages:
// In Home.js
<Link to={`/article/${index}`} state={{ articles }}>
{article.title}
</Link>
// In ArticleDetails.js
const location = useLocation();
const articles = location.state?.articles || [];
We're using React Router's state
property to pass the articles array to the details page. This avoids unnecessary API calls when navigating between pages.
Important Notes:
- This approach is good for teaching but has limitations in production apps
- If user refreshes the page,
location.state
will be lost - In a real app, you might use a state management library (Redux, Context API) or make a new API call using article IDs
Code Review & Concepts (00:55 – 01:10)
Let's review what we've built and understand the key React concepts we've used:
1. Project Architecture
- Created a multi-page application with React Router
- Used nested routes with a shared layout
- Implemented URL parameters and query parameters
2. Data Flow
API Request → State Storage → Component Display → Navigation with State
3. React Hooks Used
-
useState
: Managing local component state (articles, query) -
useEffect
: Side effects like API calls -
useSearchParams
: Reading and updating URL query parameters -
useParams
: Accessing URL path parameters -
useLocation
: Accessing route state (passed from navigation)
4. Key Concepts Explained
Controlled Inputs:
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
The input value is controlled by React state, ensuring UI and state stay in sync.
Effect Dependencies:
useEffect(() => {
// Fetch articles
}, [query, apiKey]);
The effect runs when query
or apiKey
changes, preventing unnecessary API calls.
Conditional Rendering:
{loading ? (
<p>Loading articles...</p>
) : (
// Render articles
)}
Different UI is displayed based on application state.
Optional Chaining:
article.description?.slice(0, 150)
Safely access properties that might be undefined without causing errors.
Extensions & Advanced Features (01:10 – 01:30)
Now that you have a working application, here are some real-world enhancements you can add:
1. Add Categories
// Add this to Home.js
const categories = ['technology', 'business', 'health', 'sports', 'entertainment'];
// Add this to your JSX
<div style={{ marginBottom: '20px' }}>
{categories.map(cat => (
<button
key={cat}
onClick={() => {
setQuery(cat);
setSearchParams({ q: cat });
}}
style={{
margin: '0 5px 5px 0',
padding: '5px 10px',
background: query === cat ? '#0066cc' : '#f0f0f0',
color: query === cat ? 'white' : 'black',
border: 'none',
borderRadius: '3px'
}}
>
{cat.charAt(0).toUpperCase() + cat.slice(1)}
</button>
))}
</div>
2. Add Pagination
// Add these to state in Home.js
const [page, setPage] = useState(parseInt(searchParams.get('page')) || 1);
const [totalResults, setTotalResults] = useState(0);
// Update your fetch call
fetch(`https://newsapi.org/v2/everything?q=${query}&page=${page}&apiKey=${apiKey}`)
.then(res => res.json())
.then(data => {
setArticles(data.articles || []);
setTotalResults(data.totalResults || 0);
setLoading(false);
});
// Add pagination controls
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '20px' }}>
<button
onClick={() => {
const newPage = page - 1;
setPage(newPage);
setSearchParams({ q: query, page: newPage });
}}
disabled={page <= 1}
style={{ opacity: page <= 1 ? 0.5 : 1 }}
>
Previous
</button>
<span>Page {page}</span>
<button
onClick={() => {
const newPage = page + 1;
setPage(newPage);
setSearchParams({ q: query, page: newPage });
}}
disabled={articles.length < 20}
style={{ opacity: articles.length < 20 ? 0.5 : 1 }}
>
Next
</button>
</div>
3. Save Recent Searches
// Add to Home.js
const [recentSearches, setRecentSearches] = useState(
JSON.parse(localStorage.getItem('recentSearches')) || []
);
// Update handleSearch
const handleSearch = (e) => {
e.preventDefault();
// Save search to recent searches
if (query.trim() && !recentSearches.includes(query)) {
const updatedSearches = [query, ...recentSearches.slice(0, 4)];
setRecentSearches(updatedSearches);
localStorage.setItem('recentSearches', JSON.stringify(updatedSearches));
}
setSearchParams({ q: query });
};
// Add this under your search form
{recentSearches.length > 0 && (
<div style={{ marginBottom: '20px' }}>
<p style={{ fontSize: '0.9em', color: '#666' }}>Recent searches:</p>
<div>
{recentSearches.map(term => (
<button
key={term}
onClick={() => {
setQuery(term);
setSearchParams({ q: term });
}}
style={{
margin: '0 5px 5px 0',
padding: '3px 8px',
background: '#f0f0f0',
border: 'none',
borderRadius: '3px',
fontSize: '0.8em'
}}
>
{term}
</button>
))}
</div>
</div>
)}
Conclusion
Congratulations, 3AM warrior! You've just built a real-world React application that:
- Uses proper routing with React Router
- Fetches data from an external API
- Implements search functionality with URL persistence
- Shows detailed views for individual articles
- Uses modern React patterns and hooks
What's Next?
- Add authentication to save favorite articles
- Implement a dark mode theme toggle
- Create a backend proxy to hide your API key
- Add unit tests with React Testing Library
Remember: while others slept, you built. While they scrolled social media, you coded. This mentality is what separates successful developers from the rest. Keep this hunger for learning and building, and you'll go far.
Now go get some sleep—you've earned it!
with love ❤💜