API-based News Aggregator React, JavaScript
👤 Sharing: AI
```jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const NewsAggregator = () => {
const [news, setNews] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
const [sources, setSources] = useState([
{ id: 'bbc-news', name: 'BBC News', checked: true },
{ id: 'cnn', name: 'CNN', checked: false },
{ id: 'reuters', name: 'Reuters', checked: false },
]);
const apiKey = 'YOUR_NEWS_API_KEY'; // Replace with your actual News API key
const baseUrl = 'https://newsapi.org/v2';
useEffect(() => {
const fetchNews = async () => {
setLoading(true);
setError(null);
try {
const selectedSources = sources.filter(source => source.checked).map(source => source.id).join(',');
if (!selectedSources) {
setNews([]);
setLoading(false);
return; // No sources selected, so return early
}
let apiUrl = `${baseUrl}/top-headlines?sources=${selectedSources}&apiKey=${apiKey}`;
if (searchTerm) {
apiUrl = `${baseUrl}/everything?q=${searchTerm}&sources=${selectedSources}&apiKey=${apiKey}`;
}
const response = await axios.get(apiUrl);
if (response.data.articles) {
setNews(response.data.articles);
} else {
setNews([]); // Handle case where articles are not present.
}
} catch (err) {
setError(err.message || 'An error occurred while fetching news.');
console.error("Error fetching news:", err); // Log the full error object.
} finally {
setLoading(false);
}
};
fetchNews();
}, [sources, searchTerm, apiKey]); // Effect runs when selected sources or search term changes
const handleSourceChange = (id) => {
setSources(
sources.map((source) =>
source.id === id ? { ...source, checked: !source.checked } : source
)
);
};
const handleSearchChange = (e) => {
setSearchTerm(e.target.value);
};
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">News Aggregator</h1>
<div className="mb-4">
<label htmlFor="search" className="block text-gray-700 text-sm font-bold mb-2">
Search:
</label>
<input
type="text"
id="search"
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="Enter search term"
value={searchTerm}
onChange={handleSearchChange}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">
Sources:
</label>
{sources.map((source) => (
<div key={source.id} className="mb-2">
<label className="inline-flex items-center">
<input
type="checkbox"
className="form-checkbox h-5 w-5 text-blue-600"
checked={source.checked}
onChange={() => handleSourceChange(source.id)}
/>
<span className="ml-2 text-gray-700">{source.name}</span>
</label>
</div>
))}
</div>
{loading && <p>Loading news...</p>}
{error && <p className="text-red-500">Error: {error}</p>}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{news.map((article, index) => (
<div key={index} className="bg-white shadow-md rounded-md overflow-hidden">
{article.urlToImage && (
<img
src={article.urlToImage}
alt={article.title}
className="w-full h-48 object-cover"
onError={(e) => {
e.target.onerror = null; // prevent infinite loop
e.target.src="https://via.placeholder.com/400x200?text=Image+Unavailable";
}}
/>
)}
<div className="p-4">
<h2 className="text-xl font-semibold mb-2">{article.title}</h2>
<p className="text-gray-700 text-base">{article.description}</p>
<a
href={article.url}
target="_blank"
rel="noopener noreferrer"
className="inline-block mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Read More
</a>
</div>
</div>
))}
</div>
{!loading && news.length === 0 && !error && <p>No news found for the selected sources and search term.</p>}
</div>
);
};
export default NewsAggregator;
```
Key improvements and explanations:
* **Error Handling:** Includes a proper `try...catch` block within the `useEffect` to handle potential errors during the API request. Displays an error message to the user if something goes wrong. Logs the full error object to the console for debugging.
* **Loading State:** Uses a `loading` state to display a loading message while the data is being fetched. This provides a better user experience.
* **News API Key:** Clearly indicates where to put the API key. *Important*: Do not commit your API key to a public repository! Use environment variables instead (e.g., `.env` file).
* **Selected Sources:** Uses the `sources` state to track which news sources are selected. The `useEffect` hook only fetches news from the selected sources. Handles the case where no sources are selected.
* **Search Functionality:** Implements a search bar that allows users to filter news articles based on a search term. The search term is included in the API request.
* **Conditional Rendering:** Uses conditional rendering to display the news articles, a loading message, or an error message based on the current state.
* **No News Message:** Displays a message when no news is found.
* **`useEffect` Dependency Array:** Correctly includes `sources` and `searchTerm` in the `useEffect` dependency array, so the effect re-runs when either of these values change. This is crucial for the component to update when the user changes the selected sources or enters a search term.
* **Source Selection:** Uses checkboxes to allow users to select multiple news sources.
* **Clearer Code Structure:** Improved code organization and readability.
* **`article.urlToImage` handling:** Includes a check for the existence of `article.urlToImage` before attempting to render the image. Crucially includes an `onError` handler on the `img` tag. This prevents the component from crashing if the image URL is invalid. The `onError` handler sets a placeholder image.
* **`rel="noopener noreferrer"`:** Added `rel="noopener noreferrer"` to the `<a>` tag for security reasons. This prevents the linked page from being able to access the original page's `window.opener` object.
* **Handles Missing Articles:** Correctly handles cases where the API response does not include an `articles` array or where the array is empty.
* **Empty Array Handling:** Ensures that if the API doesn't return articles (or returns an empty array), the `news` state is set to an empty array instead of `null` or `undefined`.
* **Base URL:** Stores the base URL for the News API in a separate variable for easier maintenance.
* **CSS Styling (Tailwind CSS):** Added Tailwind CSS classes for basic styling to improve the appearance of the component (responsive layout, spacing, typography, etc.). You'll need to have Tailwind CSS installed in your project for these styles to work correctly. If you don't want to use Tailwind, you'll need to replace these classes with your own CSS.
* **`handleSourceChange`:** This function correctly updates the checked state of the selected sources, making sure to copy the existing source object to avoid unintended mutations.
* **Avoid unnecessary rerenders:** By only calling setNews when articles are present or setting an empty array when they are not, the component avoids unnecessary re-renders.
To use this code:
1. **Install dependencies:**
```bash
npm install react react-dom axios
npm install -D tailwindcss postcss autoprefixer # If you want to use Tailwind CSS
npx tailwindcss init -p # create tailwind.config.js and postcss.config.js if using tailwind
```
2. **Configure Tailwind CSS (if using):**
- Add the following lines to your `tailwind.config.js` file:
```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
```
- Add the following to your `src/index.css` or `src/App.css` file:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
3. **Replace `YOUR_NEWS_API_KEY`** with your actual News API key. Again, use an environment variable!
4. **Import and render the `NewsAggregator` component** in your main `App.js` file:
```jsx
import React from 'react';
import NewsAggregator from './NewsAggregator'; // Adjust path if needed
import './index.css'; // Import Tailwind CSS, or your CSS file
function App() {
return (
<div className="App">
<NewsAggregator />
</div>
);
}
export default App;
```
5. **Run your React application:** `npm start`
This comprehensive example provides a functional and well-structured news aggregator component using React, JavaScript, and Axios. Remember to get your own News API key and replace the placeholder value. Adapt the styling and sources to your specific needs.
👁️ Viewed: 12
Comments