This project demonstrates various use cases of the useEffect hook in React, styled with Tailwind CSS. It includes a dark mode toggle and tracks user activity such as mouse movement, idle time, and click count, showcasing side effect management in functional components. Comprehensive tests ensure the reliability of all components.
A simple React app that tracks user activity using useEffect for:
- Fetching data from an API
- Subscribing/unsubscribing to browser events
- Managing timers and cleanup logic
- Conditional re-running based on dependencies
The app uses Tailwind CSS for responsive styling and includes a dark mode toggle for switching between light and dark themes. Tests verify component rendering, async behavior, event handling, and state persistence.
src/
β
βββ components/
β βββ MouseTracker.jsx
β βββ MouseTracker.test.jsx
β βββ IdleDetector.jsx
β βββ IdleDetector.test.jsx
β βββ UserInfoLoader.jsx
β βββ UserInfoLoader.test.jsx
β βββ ActivityLogger.jsx
β βββ ActivityLogger.test.jsx
β
βββ App.jsx
βββ App.test.jsx
βββ index.css
βββ setupTests.js
βββ main.jsx
Fetches user info from an API when the component mounts ([] dependency array). Tests verify initial loading state, successful data fetching, and proper rendering of user data.
import React, { useState, useEffect } from 'react';
export default function UserInfoLoader() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
console.log('Fetching user data...');
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, []);
if (loading) return (
<div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
<h3 className="text-lg font-semibold text-black dark:text-white">User Info</h3>
<p className="text-gray-600 dark:text-gray-300">Loading user info...</p>
</div>
);
return (
<div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
<h3 className="text-lg font-semibold text-black dark:text-white">User Info</h3>
<p className="text-black dark:text-white"><strong>Name:</strong> {user.name}</p>
<p className="text-black dark:text-white"><strong>Email:</strong> {user.email}</p>
</div>
);
}Tracks mouse position using event listeners. Demonstrates cleanup. Tests verify initial rendering, mouse position updates, and event listener cleanup.
import React, { useState, useEffect } from 'react';
export default function MouseTracker() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
// Cleanup
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
return (
<div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
<h3 className="text-lg font-semibold text-black dark:text-white">Mouse Tracker</h3>
<p className="text-black dark:text-white">Mouse Position: X: {position.x}, Y: {position.y}</p>
</div>
);
}Detects if the user has been idle for more than 3 seconds. Tests verify initial active state, idle state after 3 seconds, idle reset on user interaction, and timer/listener cleanup.
import React, { useState, useEffect } from 'react';
export default function IdleDetector() {
const [isIdle, setIsIdle] = useState(false);
useEffect(() => {
let timeoutId;
const resetTimer = () => {
setIsIdle(false);
clearTimeout(timeoutId);
timeoutId = setTimeout(() => setIsIdle(true), 3000);
};
window.addEventListener('mousemove', resetTimer);
window.addEventListener('keydown', resetTimer);
resetTimer(); // Initialize
return () => {
clearTimeout(timeoutId);
window.removeEventListener('mousemove', resetTimer);
window.removeEventListener('keydown', resetTimer);
};
}, []);
return (
<div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
<h3 className="text-lg font-semibold text-black dark:text-white">User Status</h3>
<p className="text-black dark:text-white">{isIdle ? 'You are idle.' : 'You are active.'}</p>
</div>
);
}Counts clicks and logs them to the console only when the count changes. Tests verify initial rendering, click counting, and console logging on count changes.
import React, { useState, useEffect } from 'react';
export default function ActivityLogger() {
const [clicks, setClicks] = useState(0);
useEffect(() => {
console.log(`User clicked ${clicks} times`);
}, [clicks]);
const handleClick = () => {
setClicks(prev => prev + 1);
};
return (
<div onClick={handleClick} className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
<h3 className="text-lg font-semibold text-black dark:text-white">Activity Logger</h3>
<p className="text-black dark:text-white">User has clicked {clicks} times.</p>
<p className="text-gray-600 dark:text-gray-300"><em>(Click anywhere in this box)</em></p>
</div>
);
}Combines all components into one view with a dark mode toggle. Tests verify rendering of all components, dark mode toggling, and localStorage persistence.
import React, { useEffect, useState } from 'react';
import UserInfoLoader from './components/UserInfoLoader';
import MouseTracker from './components/MouseTracker';
import ActivityLogger from './components/ActivityLogger';
import IdleDetector from './components/IdleDetector';
export default function App() {
const [isDark, setIsDark] = useState(() => {
return localStorage.getItem('darkMode') === 'true';
});
useEffect(() => {
if (isDark) {
document.documentElement.classList.add('dark');
localStorage.setItem('darkMode', 'true');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('darkMode', 'false');
}
}, [isDark]);
return (
<div className="p-4 bg-white dark:bg-gray-900 text-black dark:text-white min-h-screen">
<h1 className="text-2xl font-bold mb-4">π§ User Activity Tracker</h1>
<button
onClick={() => setIsDark((prev) => !prev)}
className="mb-4 px-4 py-2 bg-gray-200 dark:bg-gray-800 text-black dark:text-white rounded hover:bg-gray-300 dark:hover:bg-gray-700"
>
Toggle Dark Mode
</button>
<hr className="my-4 border-gray-300 dark:border-gray-700" />
<UserInfoLoader />
<hr className="my-4 border-gray-300 dark:border-gray-700" />
<MouseTracker />
<hr className="my-4 border-gray-300 dark:border-gray-700" />
<IdleDetector />
<hr className="my-4 border-gray-300 dark:border-gray-700" />
<ActivityLogger />
</div>
);
}Entry point using React 18+ with createRoot.
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);The project includes comprehensive tests using Jest and React Testing Library to ensure component reliability. Tests are located in *.test.jsx files alongside each component.
- Dependencies: Install testing dependencies:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest identity-obj-proxy
- Jest Configuration (
jest.config.js):module.exports = { moduleNameMapper: { '\\.(css|less|scss|sass)$': 'identity-obj-proxy', }, };
- Test Setup (
src/setupTests.js):import '@testing-library/jest-dom';
| Component | Tests Performed |
|---|---|
UserInfoLoader |
Verifies loading state, async data fetching, and rendering of user name/email |
MouseTracker |
Tests initial rendering, mouse position updates, and event listener cleanup |
IdleDetector |
Tests active/idle states, timer-based idle detection, reset on interaction, cleanup |
ActivityLogger |
Verifies initial rendering, click counting, and console logging on count changes |
App |
Tests rendering of all components, dark mode toggle, and localStorage persistence |
Run the tests with:
npm testTo run the project locally:
-
Create a new Create React App project:
npx create-react-app user-activity-tracker cd user-activity-tracker -
Install Tailwind CSS and testing dependencies:
npm install -D tailwindcss postcss autoprefixer @testing-library/react @testing-library/jest-dom jest identity-obj-proxy npx tailwindcss init
-
Configure
tailwind.config.js:/** @type {import('tailwindcss').Config} */ module.exports = { content: ['./src/**/*.{html,js,jsx,ts,tsx}'], darkMode: 'class', theme: { extend: {}, }, plugins: [], };
-
Set up
src/index.css:@tailwind base; @tailwind components; @tailwind utilities; .dark body { @apply bg-gray-900 text-white; }
-
Configure
postcss.config.js:module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, };
-
Replace
srcfolder contents with the files above (App.jsx,App.test.jsx,main.jsx,components/,setupTests.js). -
Start the dev server:
npm start
-
Open http://localhost:3000 in your browser.
-
Run tests:
npm test
| Component | Use of useEffect |
|---|---|
UserInfoLoader |
Runs once (on mount) to fetch data |
MouseTracker |
Adds/removes event listener; cleanup used |
IdleDetector |
Uses timer and cleanup for debouncing |
ActivityLogger |
Effect runs conditionally when clicks change |
App |
Toggles dark mode and persists state |
Try extending the app by:
- Adding global state with
useReduceror Redux. - Persisting data (e.g., click count or user activity) in
localStorage. - Displaying a list of logged activities in the UI.
- Writing additional tests for edge cases (e.g., API failure in
UserInfoLoader).
- React Docs - useEffect
- Tailwind CSS Docs - Dark Mode
- CRA Docs - Advanced Configuration
- CRA Docs - Deployment
- React Testing Library Docs
- Jest Docs
Feel free to fork, improve, or submit issues and PRs! This project is meant to help developers understand how to effectively use the useEffect hook, Tailwind CSS, and testing in real-world scenarios.
- Dark Mode Not Working:
- Ensure
darkMode: 'class'is set intailwind.config.js. - Verify the
darkclass is added to the<html>tag (not<body>) inApp.jsx. - Check that components use
dark:variant styles (e.g.,dark:bg-gray-900,dark:text-white). - Confirm Tailwind CSS is compiled by checking the CSS file in the browser's DevTools (Network tab, search for
dark:classes).
- Ensure
- PostCSS 8 Compatibility:
- If you encounter PostCSS 8 issues with Create React App, refer to this guide.
- CSS Not Applying:
- Ensure
src/index.csscontains Tailwind directives and is imported inmain.jsx. - Verify the
contentarray intailwind.config.jsincludes your component files (./src/**/*.{html,js,jsx,ts,tsx}).
- Ensure
- WebStorm
@applyIDE Error:- Install the Tailwind CSS plugin:
File > Settings > Plugins > Marketplace > Tailwind CSS. - Set CSS dialect to PostCSS:
File > Settings > Editor > Languages & Frameworks > Style Sheets > CSS > Dialect > PostCSS. - Disable CSS validation:
File > Settings > Editor > Inspections > CSS > Invalid elements(uncheck). - Add to
.idea/workspace.xmlunder<component name="PropertiesComponent">:<property name="css.validate" value="false" /> <property name="files.associations" value="*.css=tailwindcss" />
- Restart WebStorm.
- Install the Tailwind CSS plugin:
- Async
fetchinUserInfoLoader:- Ensure
fetchis mocked in tests usingglobal.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ name: 'John Doe', email: 'john@example.com' }) })). - Use
waitForfrom React Testing Library to wait for async updates.
- Ensure
- Timer Issues in
IdleDetector:- Use
jest.useFakeTimers('modern')and wrapjest.advanceTimersByTimeinactfor timer-based tests.
- Use
- Multiple Text Matches:
- Use
getByRole('heading', { name: /text/i })to target specific elements (e.g.,<h3>) when multiple elements match text.
- Use
- Test Setup:
- Ensure
jest.config.jsandsetupTests.jsare configured as shown above. - Run
npm install --save-dev identity-obj-proxyif CSS imports cause issues.
- Ensure
- Check the browser console for JavaScript or CSS errors.
- Ensure Node.js (v16+ recommended, v20.13.1 compatible) and npm versions are compatible.
- Run
npm run buildand check the output CSS for missing styles.

