Build a Simple Next.js Interactive Web-Based Feedback Widget

Written by

in

In the digital age, gathering user feedback is crucial for the success of any website or application. It helps you understand what users like, dislike, and what improvements are needed. While there are numerous third-party feedback tools available, building your own offers greater control over the data, customization options, and learning opportunities. This tutorial will guide you through creating a simple, yet functional, interactive feedback widget using Next.js, a popular React framework for building web applications.

Why Build a Feedback Widget?

Integrating a feedback widget directly into your application provides several advantages:

  • Direct User Input: Collect feedback directly within the context of the user experience.
  • Customization: Tailor the widget to match your brand and specific needs.
  • Data Ownership: Own and control your user feedback data.
  • Learning Opportunity: Enhance your Next.js skills by building a practical project.

This tutorial will cover the essential steps to build a feedback widget, from setting up the Next.js project to implementing the user interface and handling form submissions.

Prerequisites

Before you begin, ensure you have the following:

  • Node.js and npm (or yarn) installed on your system.
  • Basic understanding of JavaScript and React.
  • A code editor (e.g., VS Code, Sublime Text).

Setting Up Your Next.js Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app feedback-widget

This command creates a new Next.js project named “feedback-widget”. Navigate into the project directory:

cd feedback-widget

Now, start the development server:

npm run dev

You should see a message indicating that the development server is running, typically on `http://localhost:3000`. Open this address in your browser to see the default Next.js welcome page.

Creating the Feedback Widget Component

Next, let’s create a new component for our feedback widget. Create a new file named `FeedbackWidget.js` inside the `components` directory (if the directory doesn’t exist, create it):

// components/FeedbackWidget.js
import React, { useState } from 'react';

function FeedbackWidget() {
  const [isFormVisible, setIsFormVisible] = useState(false);
  const [feedback, setFeedback] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    // Add your logic to submit the feedback here
    console.log('Feedback submitted:', feedback);
    setFeedback('');
    setIsFormVisible(false);
  };

  return (
    <div style={{
      position: 'fixed',
      bottom: '20px',
      right: '20px',
      zIndex: 1000, // Ensure it's on top of other content
    }}>
      <button
        onClick={() => setIsFormVisible(!isFormVisible)}
        style={{
          backgroundColor: '#4CAF50',
          color: 'white',
          padding: '10px 15px',
          border: 'none',
          borderRadius: '5px',
          cursor: 'pointer',
        }}
      >
        {isFormVisible ? 'Close Feedback' : 'Give Feedback'}
      </button>
      {isFormVisible && (
        <div style={{
          backgroundColor: 'white',
          padding: '20px',
          borderRadius: '5px',
          boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
          marginTop: '10px',
        }}>
          <h3>We value your feedback!</h3>
          <form onSubmit={handleSubmit}>
            <textarea
              value={feedback}
              onChange={(e) => setFeedback(e.target.value)}
              placeholder="Tell us what you think..."
              style={{
                width: '100%',
                padding: '10px',
                marginBottom: '10px',
                borderRadius: '5px',
                border: '1px solid #ccc',
              }}
            />
            <button type="submit"
              style={{
                backgroundColor: '#008CBA',
                color: 'white',
                padding: '10px 15px',
                border: 'none',
                borderRadius: '5px',
                cursor: 'pointer',
              }}
            >Submit Feedback</button>
          </form>
        </div>
      )}
    </div>
  );
}

export default FeedbackWidget;

Let’s break down this code:

  • `useState` Hook: We use `useState` to manage the visibility of the feedback form (`isFormVisible`) and the feedback text itself (`feedback`).
  • `handleSubmit` Function: This function is called when the form is submitted. Currently, it logs the feedback to the console. You’ll replace this with your actual submission logic later. It also resets the form and closes it.
  • Conditional Rendering: The feedback form is only rendered if `isFormVisible` is true.
  • Basic Styling: Inline styles are used for simplicity. In a real-world application, you’d likely use CSS modules, styled-components, or a similar styling solution.

Integrating the Widget into Your Application

Now, let’s integrate this widget into your main application. Open `pages/index.js` and import and render the `FeedbackWidget` component:

// pages/index.js
import Head from 'next/head';
import FeedbackWidget from '../components/FeedbackWidget';

export default function Home() {
  return (
    <div>
      <Head>
        <title>My Next.js App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <h1>Welcome to my Next.js App</h1>
        <p>This is a sample application.  Feel free to provide feedback using the widget!</p>
      </main>

      <FeedbackWidget />
    </div>
  );
}

In this code:

  • We import the `FeedbackWidget` component.
  • We render the `FeedbackWidget` component within the main `div` of the page.

Now, when you visit your application in the browser, you should see a “Give Feedback” button in the bottom-right corner of the screen. Clicking it will reveal the feedback form.

Handling Form Submission (Backend Integration)

Currently, the `handleSubmit` function only logs the feedback to the console. In a real application, you’ll want to send this feedback to a backend service. This section will demonstrate how to make a basic API call using `fetch`.

Important: For simplicity, this example will use a dummy API endpoint. In a production environment, you would replace this with your actual backend endpoint.

Modify the `handleSubmit` function in `FeedbackWidget.js`:

// components/FeedbackWidget.js
// ... (previous code)

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await fetch('/api/feedback', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ feedback }),
      });

      if (response.ok) {
        console.log('Feedback submitted successfully!');
        setFeedback('');
        setIsFormVisible(false);
      } else {
        console.error('Failed to submit feedback.');
        // Handle error (e.g., display an error message to the user)
      }
    } catch (error) {
      console.error('Error submitting feedback:', error);
      // Handle error (e.g., display an error message to the user)
    }
  };

// ... (rest of the code)

In this updated code:

  • We use `fetch` to make a POST request to `/api/feedback`.
  • We send the feedback text in the request body as JSON.
  • We check the response status to determine if the submission was successful.
  • We include error handling to catch potential issues during the API call.

Now, we need to create the API route at `/api/feedback`. Create a new file named `pages/api/feedback.js`:

// pages/api/feedback.js
export default async function handler(req, res) {
  if (req.method === 'POST') {
    try {
      const { feedback } = req.body;
      // In a real application, you would save the feedback to a database or other storage.
      console.log('Received feedback:', feedback);
      res.status(200).json({ message: 'Feedback received' });
    } catch (error) {
      console.error('Error processing feedback:', error);
      res.status(500).json({ error: 'Failed to process feedback' });
    }
  } else {
    res.status(405).json({ error: 'Method not allowed' });
  }
}

This API route:

  • Handles POST requests to `/api/feedback`.
  • Extracts the feedback from the request body.
  • Logs the feedback to the server console (replace this with your actual data storage logic).
  • Sends a success or error response to the client.

Important: This example logs the feedback to the server console. In a real-world application, you would replace the `console.log` statement with code to save the feedback to a database (e.g., MongoDB, PostgreSQL), a file, or another data storage solution. Consider using environment variables to store sensitive information like database credentials.

Adding Validation

To improve the user experience and prevent empty submissions, let’s add some basic validation to the form. Modify the `handleSubmit` function in `FeedbackWidget.js`:

// components/FeedbackWidget.js
// ... (previous code)

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!feedback.trim()) {
      alert('Please enter your feedback.'); // Or display an error message
      return;
    }
    try {
      const response = await fetch('/api/feedback', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ feedback }),
      });

      if (response.ok) {
        console.log('Feedback submitted successfully!');
        setFeedback('');
        setIsFormVisible(false);
      } else {
        console.error('Failed to submit feedback.');
        // Handle error (e.g., display an error message to the user)
      }
    } catch (error) {
      console.error('Error submitting feedback:', error);
      // Handle error (e.g., display an error message to the user)
    }
  };

// ... (rest of the code)

In this updated code:

  • We check if the `feedback` string is empty or contains only whitespace using `!feedback.trim()`.
  • If the feedback is empty, we display an alert (you might use a more user-friendly error message in a real application).
  • The API call is only made if the feedback is not empty.

Styling and Customization

The current styling is very basic. You can customize the appearance of the feedback widget to match your website’s design. Here are some styling options to consider:

  • CSS Modules: Create separate CSS files for your components and import them. This is a common and recommended approach for styling Next.js applications.
  • Styled Components: Use a library like styled-components to write CSS-in-JS.
  • Tailwind CSS: Integrate a utility-first CSS framework like Tailwind CSS for rapid styling.
  • Color Palette: Use your brand’s color palette for the button, background, and text.
  • Font: Choose a font that complements your website’s design.
  • Button Style: Customize the button’s appearance (e.g., rounded corners, hover effects).
  • Form Layout: Adjust the layout of the form elements (e.g., padding, margins).

Here’s an example using CSS Modules (create a file named `FeedbackWidget.module.css` in the `components` directory):

/* components/FeedbackWidget.module.css */
.widgetContainer {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 1000;
}

.button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

.formContainer {
  background-color: white;
  padding: 20px;
  border-radius: 5px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  margin-top: 10px;
}

.textarea {
  width: 100%;
  padding: 10px;
  margin-bottom: 10px;
  border-radius: 5px;
  border: 1px solid #ccc;
}

.submitButton {
  background-color: #008CBA;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

Then, modify `FeedbackWidget.js` to import and use these styles:

// components/FeedbackWidget.js
import React, { useState } from 'react';
import styles from './FeedbackWidget.module.css';

function FeedbackWidget() {
  const [isFormVisible, setIsFormVisible] = useState(false);
  const [feedback, setFeedback] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!feedback.trim()) {
      alert('Please enter your feedback.');
      return;
    }
    try {
      const response = await fetch('/api/feedback', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ feedback }),
      });

      if (response.ok) {
        console.log('Feedback submitted successfully!');
        setFeedback('');
        setIsFormVisible(false);
      } else {
        console.error('Failed to submit feedback.');
      }
    } catch (error) {
      console.error('Error submitting feedback:', error);
    }
  };

  return (
    <div className={styles.widgetContainer}>
      <button
        onClick={() => setIsFormVisible(!isFormVisible)}
        className={styles.button}
      >
        {isFormVisible ? 'Close Feedback' : 'Give Feedback'}
      </button>
      {isFormVisible && (
        <div className={styles.formContainer}>
          <h3>We value your feedback!</h3>
          <form onSubmit={handleSubmit}>
            <textarea
              value={feedback}
              onChange={(e) => setFeedback(e.target.value)}
              placeholder="Tell us what you think..."
              className={styles.textarea}
            />
            <button type="submit" className={styles.submitButton}>Submit Feedback</button>
          </form>
        </div>
      )}
    </div>
  );
}

export default FeedbackWidget;

Remember to adjust the CSS styles in `FeedbackWidget.module.css` to match your desired design.

Common Mistakes and How to Fix Them

Here are some common mistakes developers make when building feedback widgets and how to avoid them:

  • Incorrect File Paths: Double-check your file paths when importing components and modules. Typos can lead to import errors.
  • Missing Dependencies: Ensure you have installed all necessary dependencies. If you’re using a library for styling (e.g., styled-components), make sure it’s installed.
  • API Endpoint Errors: Verify that your API endpoint is correctly defined and that the request method (GET, POST, etc.) matches what’s expected by your backend. Use your browser’s developer tools (Network tab) to inspect API requests and responses.
  • CORS Issues: If your frontend and backend are on different domains, you might encounter Cross-Origin Resource Sharing (CORS) errors. Configure CORS on your backend to allow requests from your frontend’s domain.
  • Data Handling: Properly handle the feedback data. Consider sanitizing user input to prevent security vulnerabilities (e.g., XSS attacks). Store the feedback securely (e.g., in a database).
  • User Experience: Provide clear feedback to the user after submission (e.g., a success message). Consider adding loading indicators while the form is submitting.

Key Takeaways

  • Next.js provides a great environment for building interactive web applications.
  • React hooks like `useState` are essential for managing component state.
  • API routes in Next.js make it easy to create backend functionality.
  • Always handle errors and provide feedback to the user.
  • Prioritize user experience and security.

FAQ

Q: How can I store the feedback data?

A: You can store the feedback in a database (e.g., MongoDB, PostgreSQL), a file, or a third-party service. The best approach depends on your specific needs and the scale of your application.

Q: How can I prevent spam?

A: Implement measures to prevent spam, such as:

  • CAPTCHA: Use a CAPTCHA to verify that the user is a human.
  • Rate Limiting: Limit the number of submissions from a single IP address or user.
  • Input Validation: Validate the feedback content to detect and filter out spam.

Q: How can I customize the feedback form?

A: You can customize the form by:

  • Adding more input fields (e.g., name, email).
  • Using different form elements (e.g., radio buttons, dropdowns).
  • Adjusting the styling to match your brand.

Q: How do I deploy my Next.js application?

A: You can deploy your Next.js application to various platforms, such as Vercel (recommended for Next.js), Netlify, or your own server. Vercel provides a seamless deployment experience, especially for Next.js projects.

Conclusion

It’s important to remember that this is a basic foundation. From here, you can expand upon this project, adding features like user authentication, more detailed feedback forms, and integrations with other services. The possibilities are endless. Keep experimenting, learning, and iterating to create a feedback widget that perfectly suits your needs and enhances your users’ experience. The key is to start small, build incrementally, and always prioritize user experience and data security. By following this tutorial, you’ve taken the first step towards building a valuable tool that can significantly improve your website or application.