Next.js is a React framework used for building full-stack web applications, supporting both server-side and client-side features. The server-side handles application logic, including routing, data fetching, and database connections, while the client-side uses React components to manage the user interface.
Sometimes we might want to create a reusable component that can be shared between React components and Next.js components. This can generate import errors if the shared component contains client-side hooks such as useState
, useReducer
, and useEffect
, which are not supported in server-side Next.js code.
In this tutorial, we’ll learn how to use Rehackt to import React components into server-side code without causing build failures due to client-side hooks.
In Next.js, components are server-side by default. Developers must explicitly mark a component as a Client Component to use event handlers and client-side hooks like useState
, useContext
, and useEffect
. Attempting to import React components with client-side hooks into server-side code will result in build errors in production.
Let’s take a look at the component below which contains code shared between client-side and server-side components:
// shared-code.js import { useState } from "react"; export const useLoginForm = () => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const handleUsernameChange = (e) => setUsername(e.target.value); const handlePasswordChange = (e) => setPassword(e.target.value); const validateForm = () => { if (username.trim() === "" || password.trim() === "") { setError("Both fields are required."); return false; } setError(""); return true; }; return { username, password, error, handleUsernameChange, handlePasswordChange, validateForm, }; }; export const fetchUserData = async (username, password) => { const users = [ { username: "popoola", fullname: "Popoola tope", password: "password1" }, { username: "adewale123", fullname: "Adewale blessing", password: "password2", }, { username: "joy101", fullname: "Smith joy", password: "password3" }, ]; const user = users.find( (user) => user.username == username && user.password == password ); if (user) { return { success: true, user, }; } else { return { success: false, message: "Invalid username or password", }; } };
In the shared-code
component above:
useLoginForm
custom hook manages the state and behavior of a login form using the React client-side useState
hook, which is not supported on the server sidefetchUserData
function is server-side code that validates the user’s login details and returns the details of the logged-in userWe can easily utilize the above component in the client-side component, as shown in the
LoginForm
component below:
// src/app/components/LoginForm.js "use client"; import { useFormState } from "react-dom"; import { useLoginForm } from "./shared-code"; import someAction from "./action"; export const LoginForm = () => { const [data, action] = useFormState(someAction, "Hello client"); const { username, password, error, handleUsernameChange, handlePasswordChange, validateForm, } = useLoginForm(); const handleSubmit = (event) => { event.preventDefault(); if (validateForm()) { action({ username, password }); } }; return ( <form onSubmit={handleSubmit}> <p>{data}</p> {error && <p style={{ color: "red" }}>{error}</p>} <div> <label> Username: <input type="text" value={username} name="username" onChange={handleUsernameChange} /> </label> </div> <div> <label> Password: <input type="password" value={password} name="password" onChange={handlePasswordChange} /> </label> </div> <button type="submit">Login</button> </form> ); }; export default LoginForm;
In the LoginForm
component above:
"use client"
at the top of the file
useLoginForm
hook is imported from the shared-code
component to manage the login form’s state and behaviorLoginForm
function utilizes the handleUsernameChange
, handlePasswordChange
, and validateForm
methods from the shared-code
component to validate the login details before sending them to the server sideNow, let’s import the fetchUserData
function from the shared-code
component on the server side to retrieve the details of the logged-in user:
import { fetchUserData } from "./shared-code"; export default async function someAction(username, password) { const result = await fetchUserData(username, password); if (result.success) { return `Hello ${result.user.fullname}, you are logged in.`; } else { return result.message; } }
The server component above returns the details of the logged-in user to the user interface upon successful authentication if the username and password are correct.
Now, let’s build the application to test if everything is working correctly. You can do this by running the build command below:
npm run build
During the build process, the application will encounter the following build errors and fail.
Failed to compile:
./src/app/components/shared-code.js Error: x You are importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so theyre Server Components by default. | Learn more: https://nextjs.org/docs/getting-started/react-essentials | | ,-[/Users/popoolatopzy/Desktop/works/logrocket/my-login-app/src/app/components/shared-code.js:1:1] 1 | // shared-code.js 2 | import { useState } from "react"; : ^^^^^^^^ 3 | 4 | export const useLoginForm = () => { 5 | const [username, setUsername] = useState(""); `---- Import trace for requested module: ./src/app/components/shared-code.js ./src/app/components/action.js > Build failed because of webpack errors
This error occurs because the shared-code
component uses a useState
hook, which is a client-side hook that is not supported in server-side code.
The Rehackt library allows developers to share reusable components between React components and Next.js server components without causing errors. The library achieves this by invisibly wrapping React, enabling the use of React functions like useState
, useReducer
, and useEffect
in shared code that can be used on both the server and client sides of a Next.js application.
To use Rehackt in your application’s component, you need to install the library by running the installation command below:
npm i rehackt
After installing the library, you can use it by importing the React client-side hook from Rehackt, as shown in the code below:
import { useState, useEffect, useReducer } from "rehackt";
When the above code is used in a component shared between the React client side and the Next.js server side, it will prevent the application from throwing errors during build time.
useState
cannot be executed on the server, only their import errors are handled.shared-code
componentIn our previous code, the shared-code
component threw an error because it used React’s useState
, which is a client-side hook not supported in Next.js server-side code. To resolve this issue and allow the shared-code
component to be shared between React components and Next.js server-side code, you need to import useState
from Rehackt
by updating shared-code.js
with the following code:
// shared-code.js import { useState } from "rehackt"; export const useLoginForm = () => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const handleUsernameChange = (e) => setUsername(e.target.value); const handlePasswordChange = (e) => setPassword(e.target.value); const validateForm = (e) => { if (username.trim() === "" || password.trim() === "") { setError("Both fields are required."); return false; } setError(""); return true; }; return { username, password, error, handleUsernameChange, handlePasswordChange, validateForm, }; }; export const fetchUserData = async (username, password) => { const users = [ { username: "popoola", fullname: "Popoola tope", password: "password1" }, { username: "adewale123", fullname: "Adewale blessing", password: "password2", }, { username: "joy101", fullname: "Smith joy", password: "password3" }, ]; const user = users.find( (user) => user.username == username && user.password == password ); if (user) { return { success: true, user, }; } else { return { success: false, message: "Invalid username or password", }; } };
Now, run the build command again. The application should build successfully without any errors related to importing React components in Next.js server-side code, as shown in the build screenshot below:
Rehackt is useful for building complex web applications that involve the use of reusable components, breaking the application’s functions and logic into smaller units that can be shared between React components and Next.js components. This modular approach promotes code consistency, maintainability, and efficiency, making it easier to manage and scale applications.
While Rehackt does not enable the use of client-side hooks in purely server-side code, it effectively removes import errors and facilitates the sharing of client-side logic in a Next.js environment. This capability is particularly valuable for developers looking to build complex, full-stack applications with React and Next.js, as it allows for seamless integration and code reuse across different parts of the application.
In this tutorial, you learned how to create a reusable component that can be shared between React components and Next.js server-side code without causing import errors during build time using Rehackt.
Rehackt provides a robust solution for sharing components that utilize client-side hooks like useState
, useReducer
, and useEffect
between the client and server sides of a Next.js application. This approach allows developers to create reusable components that are accessible to both React components and Next.js server-side code.
Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your Next.js apps — start monitoring for free.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowHandle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
Design React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.