11 KiB
Coding 9 (Composition Patterns)
Server Components
- Fetching data
- Directly accessing backend resources
- Protecting sensitive information (like access tokens and API keys) on the server
- Keeping large dependencies server-side helping in reducing client side javascript
Client Components
- Adding interactivity and handling event listeners
- Managing state and lifecycle effects ( useState(), useReducer(), useEffect() )
- Using browser-exclusive APIs
- Custom hooks
- React Class components
Server-only code
- Its not wise to include server side code to prevent exposing logic, api keys or data also it may not work on client too
- So its important to have a clear boundary
- Create server side functions in different files and import
server-onlymodule in that file(containing server side function) which will give error at build time if its code is used in client components
src/app
utils
server-utils.jsx
server-route
page.jsx
client-route
page.jsx
In src/app/utils/server-utils.jsx
import "server-only";
export const ServerSideFunction = () => {
console.log(
`use multiple libraries,
use environment variables,
interact with a database,
process confidential information`
);
return "server result";
};
In src/app/server-route/page.jsx
import { ServerSideFunction } from "../utils/server-utils";
export default function ServerRoutePage() {
console.log("Server router rendered");
const result = ServerSideFunction();
return (
<>
<h1>ServerRoutePage</h1>
<p>{result}</p>
</>
);
}
In src/app/client-route/page.jsx
"use client";
import { ServerSideFunction } from "../utils/server-utils";
export default function ClientRoutePage() {
console.log("Client route rendered");
const result = ServerSideFunction();
return (
<>
<h1>ClientRoutePage</h1>
<p>{result}</p>
</>
);
}
Third Party Packages
- third party packages in npm dont use 'use client' directive so they cant be used in server components so we need to encapsulate them in another client component and use it instead
- It is possible to use nested client side component inside server side component
Example : Using
react-slickpackage
Structure
src/app
components
ImageSlider.jsx
ImageSlider.css
utils
server-utils.jsx
server-route
page.jsx
client-route
page.jsx
In src/app/components/ImageSlider.jsx
"use client";
// import { ServerSideFunction } from "../utils/server-utils";
import Slider from "react-slick";
// Import css files
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import "./ImageSlider.css";
export default function ImageSlider() {
console.log("Client route rendered");
// const result = ServerSideFunction();
var settings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
};
return (
// <>
// <h1>ClientRoutePage</h1>
// <p>{result}</p>
// </>
// <Slider {...settings}>
<Slider {...settings}>
<div className="fit-con">
<img src="https://picsum.photos/id/34/367/267" alt="picsum" />
</div>
<div className="fit-con">
<img src="https://picsum.photos/id/35/367/267" alt="picsum" />
</div>
<div className="fit-con">
<img src="https://picsum.photos/id/37/367/267" alt="picsum" />
</div>
<div className="fit-con">
<img src="https://picsum.photos/id/41/367/267" alt="picsum" />
</div>
<div className="fit-con">
<img src="https://picsum.photos/id/40/367/267" alt="picsum" />
</div>
<div className="fit-con">
{/* <img src="https://picsum.photos/id/39/367/267" alt="picsum" /> */}
<h1>6</h1>
</div>
</Slider>
);
}
In src/app/components/ImageSlider.css
.fit-con {
display: flex !important;
justify-content: center;
}
In src/app/server-route/page.jsx
import ImageSlider from "../components/ImageSlider";
import { ServerSideFunction } from "../utils/server-utils";
export default function ServerRoutePage() {
console.log("Server router rendered");
const result = ServerSideFunction();
return (
<>
<h1>ServerRoutePage</h1>
<p>{result}</p>
<div style={{ width: "367px", margin: "auto" }}>
<ImageSlider />
</div>
</>
);
}
Context Providers
- Context providers are typically rendered near the root of an application to share global application state and logic. Eg : App theme
- React context is not supported in Server Compinent so just like encapusalating third package in separate client component we need to do same for this
Structure
src/app
layout.jsx
client-route
page.jsx
components
theme-provider.jsx
In src/app/components/theme-provider.jsx
"use client";
import { createContext, useContext } from "react";
const defaultTheme = {
colors: {
primary: "#007bff",
secondary: "#6c757d",
},
};
const ThemeContext = createContext(defaultTheme);
export const ThemeProvider = ({ children }) => {
return (
<ThemeContext.Provider value={defaultTheme}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
export { ThemeContext };
In src/app/layout.jsx
import { Inter } from "next/font/google";
import { ThemeProvider } from "./components/theme-provider";
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<ThemeProvider>
<body>{children}</body>
</ThemeProvider>
</html>
);
}
In src/app/client-route/page.jsx
"use client";
import { useContext } from "react";
import { ThemeContext } from "../components/theme-provider";
export default function ClientRoutePage() {
const theme = useContext(ThemeContext);
return (
<h1
style={{
color: theme.colors.secondary,
}}
>
Client Route
</h1>
);
}
To update the context value, use useState and transfer state, stateChanger t value of context provider
import ReactDOM from "react-dom";
import React, { useState } from "react";
import authContext from "./authContext";
import Login from "./Login";
const App = () => {
const [authenticated, setAuthenticated] = useState(false);
return (
<authContext.Provider value={{ authenticated, setAuthenticated }}>
<div> user is {`${authenticated ? "" : "not"} authenticated`} </div>
<Login />
</authContext.Provider>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
Client-only Code
- To prevent unintended server side usage of client code we can install and import package
client-only
Client Component Placement
- It is recommended to position client components lower in the component tree
- Once the component is client component all the child component becomes client components too
src/app
components
navbar.jsx
navlink.jsx
navsearch.jsx
landing-page
page.jsx
In components
// navlink.jsx
export default function Navlink() {
console.log("Navlink loaded");
return <div className="Main">NavLink</div>;
}
//navsearch.jsx
"use client";
import { useState } from "react";
export default function NavSearch() {
const [search, setSearch] = useState("");
console.log("NavSearch loaded2");
return <div className="Main">NavSearch</div>;
}
//navbar.jsx
import Navlink from "./navlink";
import NavSearch from "./navsearch";
export default function NavBar() {
console.log("navbar loaded");
return (
<>
<div className="">NavBar</div>
<Navlink />
<NavSearch />
</>
);
}
- useState hook can be used in navbar too but since its usage is required only in navsearch file so its best to use it there otherwise navbar needs to be a client component and navlink will be client component too
In src/app/landing-page.jsx
import NavBar from "../components/navbar";
export default function LandingPage() {
return (
<div className="">
<h1>Landing Page</h1>
<NavBar />
</div>
);
}
Interleaving Server and Client components
- Server component cannot be directly used inside client component as they are automatically converted to client component by default
- So a workaround is needed for this ie passing server component as prop to client component
src/app
interleaving
page.jsx
components
client-component.jsx
server-component.jsx
In src/app/server-component.jsx
import fs from "fs";
export default function ServerComponent() {
console.log("ServerComponent loaded");
const fileBuffer = fs.readFileSync("package.json");
return (
<div className="ServerComponent">
<h1>Server Component</h1>
</div>
);
}
Following wont work and will give error that fs is not working as server component which is children of client component becomes client component
In src/app/interleaving.jsx
import ClientComponent from "../components/client-component";
export default function InterleavingPage() {
console.log("InterleavingPage loaded");
return (
<div className="InterleavingPage">
<ClientComponent />
</div>
);
}
In src/app/client-component.jsx
"use client";
import { useState } from "react";
import ServerComponent from "./server-component";
export default function ClientComponent() {
console.log("client component loaded");
const [state, seyState] = useState();
return (
<div className="client-component">
<h1>Client Component</h1>
<ServerComponent />
</div>
);
}
Passing server component as props instead
In src/app/interleaving.jsx
import ClientComponent from "../components/client-component";
import ServerComponent from "../components/server-component";
export default function InterleavingPage() {
console.log("InterleavingPage loaded");
return (
<div className="InterleavingPage">
<ClientComponent>
<ServerComponent />
</ClientComponent>
</div>
);
}
In src/app/client-component.jsx
"use client";
import { useState } from "react";
// import ServerComponent from "./server-component";
export default function ClientComponent({ children }) {
console.log("client component loaded");
const [state, seyState] = useState();
return (
<div className="client-component">
<h1>Client Component</h1>
{children}
{/* <ServerComponent /> */}
</div>
);
}