≡ Menu

page load speed

Understanding how users interact with your website is crucial for improving user experience and achieving business goals. One fundamental metric is “time on page” – how long a user spends actively engaging with a specific part of your site.

In this tutorial, we’ll build a robust React component that accurately measures the duration a user spends on a webpage. We’ll leverage React’s powerful useEffect hook to manage the timer, handle component lifecycles, and even account for users switching tabs!

Why Measure Time on Page?

  • Engagement Insights: Longer times can indicate higher engagement with your content.
  • Content Optimization: Identify which pages capture user attention and which might need improvement.
  • User Behavior Analysis: Correlate time on page with conversions or other user actions.
  • Analytics Integration: Send valuable data to your analytics platforms (e.g., Google Analytics, Mixpanel) or your own backend.

The Core Idea

Our React component will primarily rely on these concepts:

  1. Start Time: Record the precise moment the user lands on the page (when our component mounts).
  2. Duration Calculation: Continuously update a state variable to show the live time spent.
  3. End Time/Cleanup: When the user leaves the page (component unmounts), calculate the total time and trigger a callback.
  4. Activity Tracking: Account for users switching tabs or minimizing the browser to ensure accurate “active” time.

Let’s dive into the code!

Step 1: Set Up Your React Project (If You Haven’t Already)

If you don’t have a React project ready, you can quickly create one using Vite or Create React App

# Using Vite (recommended for new projects)
npm create vite@latest my-time-tracker-app -- --template react
cd my-time-tracker-app
npm install
npm run dev

# Or using Create React App
npx create-react-app my-time-tracker-app
cd my-time-tracker-app
npm start

Step 2: Create the TimeOnPageTracker Component

Inside your src folder, create a new file named TimeOnPageTracker.jsx (or .tsx if you’re using TypeScript).

// src/TimeOnPageTracker.jsx
import React, { useState, useEffect, useRef } from 'react';

const TimeOnPageTracker = ({ onTimeSpent }) => {
  const [startTime, setStartTime] = useState(null);
  const [duration, setDuration] = useState(0); // In seconds
  const intervalRef = useRef(null); // To store the interval ID for cleanup

  // Effect 1: Initialize timer and handle component unmount
  useEffect(() => {
    // Record the initial start time when the component mounts
    const initialStartTime = Date.now();
    setStartTime(initialStartTime);
    console.log('Page entered at:', new Date(initialStartTime).toLocaleTimeString());

    // Start an interval to update the duration every second
    intervalRef.current = setInterval(() => {
      setDuration(Math.floor((Date.now() - initialStartTime) / 1000));
    }, 1000);

    // Cleanup function: This runs when the component unmounts (user leaves the page)
    return () => {
      clearInterval(intervalRef.current); // Clear the interval to prevent memory leaks
      const endTime = Date.now();
      const finalDuration = Math.floor((endTime - initialStartTime) / 1000); // Calculate final duration in seconds
      console.log('Page left at:', new Date(endTime).toLocaleTimeString());
      console.log('Total time spent on page:', finalDuration, 'seconds');

      // Call the onTimeSpent prop to send the final duration to the parent
      if (onTimeSpent && typeof onTimeSpent === 'function') {
        onTimeSpent(finalDuration);
      }
    };
  }, [onTimeSpent]); // `onTimeSpent` is a dependency to ensure the cleanup function gets the latest version if it ever changes (though usually it's stable)

  // Effect 2: Handle page visibility changes (tab switching, minimizing)
  useEffect(() => {
    const handleVisibilityChange = () => {
      if (document.hidden) {
        // User switched tab or minimized browser - pause the timer
        console.log('User is inactive (tab hidden)');
        clearInterval(intervalRef.current);
      } else {
        // User returned to the tab - resume the timer
        console.log('User is active (tab visible)');
        if (startTime) { // Ensure startTime is set from the first effect
          // Adjust startTime to ensure continuous duration calculation
          // We set startTime to what it *should have been* to get the current duration
          setStartTime(Date.now() - (duration * 1000));
          // Restart the interval
          intervalRef.current = setInterval(() => {
            setDuration(Math.floor((Date.now() - startTime) / 1000));
          }, 1000);
        }
      }
    };

    // Add event listener for visibility changes
    document.addEventListener('visibilitychange', handleVisibilityChange);

    // Cleanup for this effect: remove the event listener
    return () => {
      document.removeEventListener('visibilitychange', handleVisibilityChange);
    };
  }, [startTime, duration]); // Dependencies for this effect

  // Helper to format the duration into a human-readable string
  const formatDuration = (totalSeconds) => {
    const hours = Math.floor(totalSeconds / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const seconds = totalSeconds % 60;

    return `${hours > 0 ? `${hours}h ` : ''}${minutes > 0 ? `${minutes}m ` : ''}${seconds}s`;
  };

  return (
    <div style={{ padding: '20px', border: '1px solid #ccc', margin: '20px', borderRadius: '8px', backgroundColor: '#f9f9f9' }}>
      <h3>️ Time Tracker</h3>
      <p>Time spent on this page: <strong>{formatDuration(duration)}</strong></p>
      <p style={{ fontSize: '0.8em', color: '#666' }}>
        (This timer updates live. The final time is logged when you leave or close this tab.)
      </p>
    </div>
  );
};

export default TimeOnPageTracker;

Code Breakdown:

  1. useState Hooks:
    • startTime: Stores the timestamp (milliseconds since epoch) when the component first rendered.
    • duration: Stores the live, continuously updated time spent in seconds.
  2. useRef(null) for intervalRef:
    • useRef is essential here because setInterval returns an ID that we need to store. If we stored it directly in useState, updating it would cause unnecessary re-renders. useRef allows us to hold a mutable value that persists across renders without triggering them.
  3. useEffect (Main Timer and Unmount Logic):
    • Mounting: When the component mounts, Date.now() captures the initialStartTime. A setInterval is set up to run every second, updating the duration state.
    • Unmounting (the return function): This is the cleanup phase.
      • clearInterval(intervalRef.current): Crucial! This stops the setInterval loop, preventing memory leaks and ensuring the timer doesn’t run after the component is gone.
      • The finalDuration is calculated.
      • onTimeSpent(finalDuration): This prop is a callback function passed from the parent. It’s where you’d typically send the time data to your analytics or backend.
  4. useEffect (Visibility Change):
    • This secondary useEffect listens for the browser’s visibilitychange event.
    • document.hidden: When true, the user has switched tabs or minimized the browser. We clearInterval to pause the timer.
    • When false, the user has returned. We restart the timer. Importantly, we adjust startTime: setStartTime(Date.now() - (duration * 1000)); This “rewinds” the startTime so that when the timer resumes, the duration continues from where it left off, rather than jumping up due to the time spent while inactive.
  5. onTimeSpent Prop:
    • This prop makes our TimeOnPageTracker component reusable. It’s a function that the parent component provides to receive the final time spent.

Step 3: Integrate into Your App.jsx

Now, let’s use our TimeOnPageTracker component in your main application file (e.g., App.jsx).

// src/App.jsx
import React from 'react';
import TimeOnPageTracker from './TimeOnPageTracker'; // Import our new component
import './App.css'; // Assuming you have some basic CSS

function App() {
  const handleTimeSpent = (timeInSeconds) => {
    // This function will be called by TimeOnPageTracker when the user leaves the page.
    // Here, you would typically send this data to your analytics service or backend.
    console.log(`User spent ${timeInSeconds} seconds on the page (from App.js callback).`);

    // Example of sending to an analytics service (replace with your actual implementation)
    // myAnalyticsService.track('time_on_page', { duration: timeInSeconds });

    // Or send to your backend API
    // fetch('/api/log-time', {
    //   method: 'POST',
    //   headers: { 'Content-Type': 'application/json' },
    //   body: JSON.stringify({ path: window.location.pathname, duration: timeInSeconds })
    // });
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>Welcome to My Awesome Website!</h1>
        <p>This is some content to keep you engaged.</p>
        <TimeOnPageTracker onTimeSpent={handleTimeSpent} />
      </header>

      <main style={{ padding: '20px' }}>
        <h2>Explore More Content</h2>
        <p>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
          incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
          nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
          Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
          eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
          sunt in culpa qui officia deserunt mollit anim id est laborum.
        </p>
        {/* Add some tall content to enable scrolling and test more interaction */}
        <div style={{ height: '800px', backgroundColor: '#e0e0e0', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '20px 0' }}>
          <p>More content here...</p>
        </div>
        <p>Thanks for visiting!</p>
      </main>
    </div>
  );
}

export default App;

Testing Your Tracker

  1. Run your React application (npm run dev or npm start).
  2. Open your browser’s developer console.
  3. Observe the live Time spent on this page update.
  4. Test 1: Tab Switching: Switch to another browser tab. You should see “User is inactive” in the console. Switch back, and “User is active” should appear, and the timer should resume accurately.
  5. Test 2: Page Navigation/Closure: Navigate to a different URL, close the tab, or close the browser. You should see the “Total time spent on page” message in your console, triggered by the onTimeSpent callback.

Important Considerations and Limitations

  • Browser Tab Closure/Crash: While the useEffect cleanup function generally works well for unmounting, there are edge cases (e.g., browser crashes, sudden power loss) where the beforeunload event (which triggers useEffect cleanup) might not fire reliably. For mission-critical analytics, consider periodic “heartbeat” pings to your server.

 

  • User Inactivity: This component accurately tracks active time (when the tab is visible). It doesn’t track if a user is simply idle on the page (e.g., walked away from their computer). For deeper activity tracking, you’d need to monitor mouse movements, keyboard presses, and scroll events, which can add complexity and overhead.

 

  • Single-Page Applications (SPAs): If your React application uses client-side routing (which most do), this component tracks the time on the specific React component instance where it’s mounted. If you want to track time on different “pages” (routes) within your SPA, you’d typically place the TimeOnPageTracker component inside each route’s main component, allowing it to mount and unmount with route changes. Alternatively, you could pass the current route as a prop to the tracker and reset its internal state when the route changes.

Conclusion

You now have a powerful and flexible React component to measure how long users spend on your webpages.

This data is invaluable for understanding user engagement and making data-driven decisions to improve your website.

Remember to integrate the onTimeSpent callback with your preferred analytics solution to truly leverage this information!

{ 0 comments }

Adding a search bar to your website can significantly improve user experience, allowing visitors to quickly find the content they’re looking for.

In this tutorial, we’ll walk through the process of creating a simple yet effective search bar in React, complete with input handling and dynamic filtering.

What We’ll Build

We’ll create a React component that features:

  • An input field for user queries.
  • State management to store the search term.
  • A list of items that will be filtered based on the search term.
  • Dynamic rendering of filtered results.

Prerequisites

Before we begin, make sure you have:

  • Node.js and npm (or yarn) installed on your machine.
  • A basic understanding of React fundamentals (components, props, state, hooks).

Step 1: Set Up Your React Project (if you don’t have one)

If you don’t have an existing React project, you can quickly create one using

Create React App:

npx create-react-app react-search-bar-tutorial
cd react-search-bar-tutorial
npm start

This will create a new React project and open it in your browser.

Step 2: Create the SearchBar Component

Inside your src folder, create a new file called SearchBar.js. This will house our main search bar logic.

// src/SearchBar.js
import React, { useState } from 'react';
import './SearchBar.css'; // We'll create this CSS file later

function SearchBar({ data }) {
  const [searchTerm, setSearchTerm] = useState('');
  const [filteredData, setFilteredData] = useState(data);

  const handleSearchChange = (event) => {
    const term = event.target.value;
    setSearchTerm(term);

    // Filter the data based on the search term
    const results = data.filter((item) =>
      item.toLowerCase().includes(term.toLowerCase())
    );
    setFilteredData(results);
  };

  return (
    <div className="search-container">
      <input
        type="text"
        placeholder="Search..."
        value={searchTerm}
        onChange={handleSearchChange}
        className="search-input"
      />
      <div className="search-results">
        {filteredData.length > 0 ? (
          <ul>
            {filteredData.map((item, index) => (
              <li key={index}>{item}</li>
            ))}
          </ul>
        ) : (
          <p>No results found.</p>
        )}
      </div>
    </div>
  );
}

export default SearchBar;

Code Breakdown:

  • useState(''): This hook initializes searchTerm to an empty string. This state variable will hold the current value of our input field.
  • useState(data): This hook initializes filteredData with the data prop passed to the component. This will be the list of items we display after filtering.
  • handleSearchChange: This function is called every time the input value changes.
    • It updates searchTerm with the new input value.
    • It then filters the original data array. item.toLowerCase().includes(term.toLowerCase()) performs a case-insensitive search.
    • Finally, setFilteredData updates the displayed results.
  • The return statement renders an input field and a div to display the filteredData. We conditionally render a message if no results are found.

Step 3: Add Some Basic Styling

Create a file named SearchBar.css in the src folder:

/* src/SearchBar.css */
.search-container {
  margin: 20px;
  text-align: center;
}

.search-input {
  padding: 10px;
  width: 300px;
  border: 1px solid #ccc;
  border-radius: 5px;
  font-size: 16px;
}

.search-results {
  margin-top: 20px;
  border: 1px solid #eee;
  padding: 15px;
  border-radius: 5px;
  max-width: 300px;
  margin-left: auto;
  margin-right: auto;
  background-color: #f9f9f9;
  max-height: 200px; /* Limit height for scrollability */
  overflow-y: auto; /* Enable vertical scrolling */
}

.search-results ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.search-results li {
  padding: 8px 0;
  border-bottom: 1px solid #eee;
  text-align: left;
}

.search-results li:last-child {
  border-bottom: none;
}

Feel free to customize these styles to match your website’s design.

Step 4: Integrate SearchBar into App.js

Now, let’s use our SearchBar component in the main App.js file.

// src/App.js
import React from 'react';
import SearchBar from './SearchBar';
import './App.css'; // Optional: for general app styling

function App() {
  // Example data to search through
  const myData = [
    'Apple',
    'Banana',
    'Cherry',
    'Date',
    'Elderberry',
    'Fig',
    'Grape',
    'Honeydew',
    'Kiwi',
    'Lemon',
    'Mango',
    'Nectarine',
    'Orange',
    'Pineapple',
    'Quince',
    'Raspberry',
    'Strawberry',
    'Tangerine',
    'Ugli fruit',
    'Vanilla bean',
    'Watermelon',
    'Xigua', // Chinese watermelon
    'Yellow passion fruit',
    'Zucchini'
  ];

  return (
    <div className="App">
      <h1>My Awesome Search App</h1>
      <SearchBar data={myData} />
    </div>
  );
}

export default App;

Here, we define a sample myData array, which will be the list of items our search bar filters. We then pass this array as a data prop to our SearchBar component.

Step 5: Run Your Application

Save all your files and go back to your terminal. If your development server isn’t already running, start it:

npm start

Your browser should open (or refresh) and display the search bar. Try typing into the input field, and you’ll see the list of fruits filter dynamically!

Conclusion and Next Steps

Congratulations! You’ve successfully built a dynamic search bar in React. This basic structure is highly adaptable and can be extended for more complex use cases.

Here are some ideas for further improvements:

  • Debouncing: For performance, especially with large datasets or API calls, implement debouncing to delay the search filtering until the user stops typing for a short period.
  • API Integration: Instead of a local array, fetch data from an API and then filter it.
  • Advanced Filtering: Add more sophisticated filtering options, like filtering by multiple criteria or categories.
  • Search Suggestions: Implement a feature that suggests common search terms as the user types.
  • Accessibility: Ensure your search bar is accessible to users with disabilities by adding ARIA attributes.
  • Styling Libraries: Use a CSS framework like Bootstrap, Material-UI, or Tailwind CSS for more robust styling.

A search bar is a fundamental feature for many applications, and by mastering its creation in React, you’ve added a valuable tool to your development toolkit.

Have any questions about this code? If so leave them in the comments section below!

{ 0 comments }