This commit is contained in:
nm1 2026-11-04 00:33:23 +05:30
commit 74ae93a96e
35 changed files with 94691 additions and 0 deletions

View file

@ -0,0 +1,14 @@
Basics
```js
const [name, setName] = useState('Edward');
const [age, setAge] = useState('0');
function handleClick() {
setName('Taylor');
setAge(a => a + 1);
}
```
as react batches states updates so it when it is called multiple times it may not update correctly so this updater function/functional form helps

View file

@ -0,0 +1,2 @@
Issues
- Sometimes there may be a problem by adding keys in component, so make sure to not use any state as in from useState hook or any state as in instance as key

View file

@ -0,0 +1,3 @@
Links
- https://morioh.com/a/8a60baf74ebe/dockerizing-a-react-application-with-nodejs-postgres-and-nginx

View file

@ -0,0 +1,3 @@
Intro
https://github.com/nodegui/react-nodegui

View file

@ -0,0 +1,19 @@
Packer
https://github.com/nodegui/packer
To create executable file
- First step is to install the packer as a dev dependency. You can do so by:
`npm install --save-dev @nodegui/packer`
- Next you can run the init command:
`npx nodegui-packer --init MyApp`
This will produce the deploy directory containing the template. You can modify this to suite your needs. Like add icons, change the name, description and add other native features or dependencies. Make sure you commit this directory.
- Next you can run the pack command:
`npx nodegui-packer --pack <path to dist>`
This command essential takes the dist folder as the input and puts it in the suitable location inside the standalone executable. Also it runs the correct deployment tool (macdeployqt incase of mac, etc) and packs in the dependencies. The output of the command is found under the build directory. You should gitignore the build directory.

View file

@ -0,0 +1,123 @@
Redux
Sure, Redux is commonly used to manage state in complex React applications, avoiding the need to pass props deeply through nested components. Below is a simple example that illustrates how Redux can be used to share state between components without prop drilling.
Let's create a counter app where you can increase, decrease, and reset the count. We'll use three components: `App` (the parent component), `CounterDisplay` (to display the count), and `CounterControls` (to increase, decrease, or reset the count), showing how Redux allows them to communicate without passing props through each other directly.
### Step 1: Setup
First, ensure you have Redux and React-Redux installed in your project:
```bash
npm install redux react-redux
```
### Step 2: Create Redux Store, Reducer, and Actions
**store.js**
```javascript
import { createStore } from 'redux';
// Reducer function
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return { count: 0 };
default:
return state;
}
}
// Create and export the store
export const store = createStore(counterReducer);
```
### Step 3: Setup Redux Provider
In your main entry file (like `index.js`), wrap your root component with the `Provider` from `react-redux` and pass the store to it:
**index.js**
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { store } from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
```
### Step 4: Creating Components
**App.js** - The parent component
```javascript
import React from 'react';
import CounterDisplay from './CounterDisplay';
import CounterControls from './CounterControls';
function App() {
return (
<div>
<h1>Redux Counter Example</h1>
<CounterDisplay />
<CounterControls />
</div>
);
}
export default App;
```
**CounterDisplay.js** - Component to display the count
```javascript
import React from 'react';
import { useSelector } from 'react-redux';
function CounterDisplay() {
const count = useSelector(state => state.count); // Accessing count from Redux state
return <h2>Count: {count}</h2>;
}
export default CounterDisplay;
```
**CounterControls.js** - Component to control the count
```javascript
import React from 'react';
import { useDispatch } from 'react-redux';
function CounterControls() {
const dispatch = useDispatch(); // To dispatch actions
return (
<div>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>Increase</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrease</button>
<button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
</div>
);
}
export default CounterControls;
```
### Brief Explanation
In this example:
- We've created a Redux store to hold our state, which in this case is simply a `count` value.
- The `counterReducer` handles actions (increment, decrement, reset) to update the state accordingly.
- The `App` component is our root component, rendering both the `CounterDisplay` and `CounterControls` components.
- The `CounterDisplay` component uses `useSelector` to read the `count` value from the Redux store directly, without needing to receive it as a prop.
- The `CounterControls` component uses `useDispatch` to dispatch actions that update the state, without needing to receive dispatch functions as props.
By using Redux, we've avoided the need to pass the `count` state or the dispatch functions through the `App` component, directly connecting the `CounterDisplay` and `CounterControls` components to the state they need. This simplifies the component structure and makes state management more efficient and scalable for larger applications.

View file

@ -0,0 +1,8 @@
Resources
- https://primereact.org/ : template for react components
primereact-10.2.1.tgz https://shdw-drive.genesysgo.net/C7yTh47Yqy2zbUbbA9QfAm59XBAqX5VU1QWU9Uyiex9Q/c44da329-b917-4fdf-9e9e-128d74a15154.tgz
- https://mui.com/material-ui/ :template for react components
- https://navithecoderboi.me/#skills : portfolio source code
- react-icons : to replace font-awesome cdn thing

View file

@ -0,0 +1,384 @@
Drag and Drop File Upload
[react-drop-file-input-main.zip](../../../../_resources/react-drop-file-input-main.zip)
- JSX
```JSX
import React, { useRef, useState } from 'react';
import PropTypes from 'prop-types';
import './drop-file-input.css';
import { ImageConfig } from '../../config/ImageConfig';
import uploadImg from '../../assets/cloud-upload-regular-240.png';
const DropFileInput = props => {
const wrapperRef = useRef(null);
const [fileList, setFileList] = useState([]);
const onDragEnter = () => wrapperRef.current.classList.add('dragover');
const onDragLeave = () => wrapperRef.current.classList.remove('dragover');
const onDrop = () => wrapperRef.current.classList.remove('dragover');
const onFileDrop = (e) => {
const newFile = e.target.files[0];
if (newFile) {
const updatedList = [...fileList, newFile];
setFileList(updatedList);
props.onFileChange(updatedList);
}
}
const fileRemove = (file) => {
const updatedList = [...fileList];
updatedList.splice(fileList.indexOf(file), 1);
setFileList(updatedList);
props.onFileChange(updatedList);
}
return (
<>
<div
ref={wrapperRef}
className="drop-file-input"
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDrop={onDrop}
>
<div className="drop-file-input__label">
<img src={uploadImg} alt="" />
<p>Drag & Drop your files here</p>
</div>
<input type="file" value="" onChange={onFileDrop}/>
</div>
{
fileList.length > 0 ? (
<div className="drop-file-preview">
<p className="drop-file-preview__title">
Ready to upload
</p>
{
fileList.map((item, index) => (
<div key={index} className="drop-file-preview__item">
<img src={ImageConfig[item.type.split('/')[1]] || ImageConfig['default']} alt="" />
<div className="drop-file-preview__item__info">
<p>{item.name}</p>
<p>{item.size}B</p>
</div>
<span className="drop-file-preview__item__del" onClick={() => fileRemove(item)}>x</span>
</div>
))
}
</div>
) : null
}
</>
);
}
DropFileInput.propTypes = {
onFileChange: PropTypes.func
}
export default DropFileInput;
```
&nbsp;
CSS
```css
.drop-file-input {
position: relative;
width: 400px;
height: 200px;
border: 2px dashed var(--border-color);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--input-bg);
}
.drop-file-input input {
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.drop-file-input:hover,
.drop-file-input.dragover {
opacity: 0.6;
}
.drop-file-input__label {
text-align: center;
color: var(--txt-second-color);
font-weight: 600;
padding: 10px;
}
.drop-file-input__label img {
width: 100px;
}
.drop-file-preview {
margin-top: 30px;
}
.drop-file-preview p {
font-weight: 500;
}
.drop-file-preview__title {
margin-bottom: 20px;
}
.drop-file-preview__item {
position: relative;
display: flex;
margin-bottom: 10px;
background-color: var(--input-bg);
padding: 15px;
border-radius: 20px;
}
.drop-file-preview__item img {
width: 50px;
margin-right: 20px;
}
.drop-file-preview__item__info {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.drop-file-preview__item__del {
background-color: var(--box-bg);
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
box-shadow: var(--box-shadow);
cursor: pointer;
opacity: 0;
transition: opacity 0.3s ease;
}
.drop-file-preview__item:hover .drop-file-preview__item__del {
opacity: 1;
}
```
---
&nbsp;
- For multiple file uploading use this and to limit the height of preview file list:
```jsx
import React, { useRef, useState } from "react";
import PropTypes from "prop-types";
import "./drop-file-input.css";
import { ImageConfig } from "../../config/ImageConfig";
import uploadImg from "../../assets/cloud-upload-regular-240.png";
const DropFileInput = (props) => {
const wrapperRef = useRef(null);
const [fileList, setFileList] = useState([]);
const onDragEnter = () => wrapperRef.current.classList.add("dragover");
const onDragLeave = () => wrapperRef.current.classList.remove("dragover");
const onDrop = () => wrapperRef.current.classList.remove("dragover");
const onFileDrop = (e) => {
const newFiles = e.target.files;
if (newFiles.length !== 0) {
const updatedList = [...fileList, ...newFiles];
setFileList(updatedList);
props.onFileChange(updatedList);
}
};
const fileRemove = (file) => {
const updatedList = [...fileList];
updatedList.splice(fileList.indexOf(file), 1);
setFileList(updatedList);
props.onFileChange(updatedList);
};
return (
<>
<div
ref={wrapperRef}
className="drop-file-input"
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDrop={onDrop}
>
<div className="drop-file-input__label">
<img src={uploadImg} alt="" />
<p>Drag & Drop your files here</p>
</div>
<input type="file" value="" multiple onChange={onFileDrop} />
</div>
{fileList.length > 0 ? (
<div className="drop-file-preview">
<p className="drop-file-preview__title">Ready to upload</p>
{fileList.map((item, index) => (
<div key={index} className="drop-file-preview__item">
<img
src={
ImageConfig[item.type.split("/")[1]] || ImageConfig["default"]
}
alt=""
/>
<div className="drop-file-preview__item__info">
<p>{item.name}</p>
<p>{item.size}B</p>
</div>
<span
className="drop-file-preview__item__del"
onClick={() => fileRemove(item)}
>
x
</span>
</div>
))}
</div>
) : null}
</>
);
};
DropFileInput.propTypes = {
onFileChange: PropTypes.func,
};
export default DropFileInput;
```
&nbsp;
CSS
```css
.drop-file-input {
position: relative;
width: 400px;
height: 200px;
border: 2px dashed var(--border-color);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--input-bg);
}
.drop-file-input input {
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.drop-file-input:hover,
.drop-file-input.dragover {
opacity: 0.6;
}
.drop-file-input__label {
text-align: center;
color: var(--txt-second-color);
font-weight: 600;
padding: 10px;
}
.drop-file-input__label img {
width: 100px;
}
.drop-file-preview {
margin-top: 30px;
max-height: 70%;
overflow-y: auto;
}
.drop-file-preview p {
font-weight: 500;
}
.drop-file-preview__title {
margin-bottom: 20px;
}
.drop-file-preview__item {
position: relative;
display: flex;
margin-bottom: 10px;
background-color: var(--input-bg);
padding: 15px;
border-radius: 20px;
}
.drop-file-preview__item img {
width: 50px;
margin-right: 20px;
}
.drop-file-preview__item__info {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.drop-file-preview__item__del {
background-color: var(--box-bg);
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
box-shadow: var(--box-shadow);
cursor: pointer;
opacity: 0;
transition: opacity 0.3s ease;
}
.drop-file-preview__item:hover .drop-file-preview__item__del {
opacity: 1;
}
```
---

View file

@ -0,0 +1,66 @@
Toast
https://primereact.org/toast/
Here package `primereact` is used so use `npm i primereact` to install it
In Toast.jsx
```jsx
import { useEffect, useRef, useState } from "react";
import { Toast } from "primereact/toast";
// import { Button } from "primereact/button";
const ToastEle = (props) => {
const toast = useRef(null);
const { errorMessage, errorVisibility, setErrorVisibility } = props;
const showError = () => {
toast.current.show({ severity: "error", detail: errorMessage, life: 3000 });
};
useEffect(() => {
if (errorVisibility === true) {
showError();
setErrorVisibility(false);
}
}, [errorVisibility]);
return <Toast ref={toast} position="top-left" />;
};
export default ToastEle;
```
&nbsp;
In App.jsx
```jsx
import { PrimeReactProvider } from "primereact/api";
import "primereact/resources/themes/lara-light-indigo/theme.css"; // theme
// import "primeflex/primeflex.css"; // css utility
// import "primeicons/primeicons.css";
import "primereact/resources/primereact.css"; //
import ToastEle from "./Components/Toast";
function App() {
// for error message at top
const [errorVisibility, setErrorVisibility] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
return(
<PrimeReactProvider>
<ToastEle
errorVisibility={errorVisibility}
errorMessage={errorMessage}
setErrorVisibility={setErrorVisibility}
/>
</PrimeReactProvider>
)
```

View file

@ -0,0 +1,69 @@
Initialization
- For React :
https://tailwindcss.com/docs/guides/create-react-app
- `npm i -D tailwindcss` or `npm i tailwindcss -save-dev`
- `npx tailwindcss init`to create `tailwind.config.js`
- Configure `tailwind.config.js`
```js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
```
- Add the `@tailwind` directives for each of Tailwind’s layers to your `./src/index.css` file.
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
- Start your build process `npm run start`
- For Nextjs
https://tailwindcss.com/docs/guides/nextjs
- `npm install -D tailwindcss postcss autoprefixer`
- `npx tailwindcss init -p` to generate `tailwind.config.js` and `postcss.config.js`
- Configuring `tailwind.config.js`
```js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {},
},
plugins: [],
}
```
- rest all same as react. instead of `index.css`, use `globals.css` for nextjs
### vanilla
https://v3.tailwindcss.com/docs/installation
```sh
npm install -D tailwindcss@3
npx tailwindcss init
```
in src/input.css
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
and finally(for vanilla cases atleast)
`npx tailwindcss -i ./src/input.css -o ./src/output.css --watch`

View file

@ -0,0 +1,23 @@
Change parameters of URL without refreshing it
```jsx
const changeURLParams = (newValue) => {
const newURL = `${window.location.pathname}${newValue}`;
window.history.pushState({ path: newURL }, "", newURL);
};
```
Change queries of URL without refreshing it
```jsx
const changeQueryParams = () => {
const urlParams = new URLSearchParams(window.location.search);
urlParams.set('queryParamName', 'newValue');
const newURL = `${window.location.pathname}?${urlParams.toString()}`;
window.history.pushState({ path: newURL }, '', newURL);
};
```

View file

@ -0,0 +1,38 @@
Props.children
My simple explanation of what this.props.children does is that it is used to display whatever you include between the opening and closing tags when invoking a component.
A simple example:
Here’s an example of a stateless function that is used to create a component. Again, since this is a function, there is no this keyword so just use props.children
```jsx
const Picture = (props) => {
return (
<div>
<img src={props.src}/>
{props.children}
</div>
)
}
```
This component contains an <img> that is receiving some props and then it is displaying {props.children}.
Whenever this component is invoked, {props.children} will also be displayed and this is just a reference to what is between the opening and closing tags of the component.
```jsx
//App.js
render () {
return (
<div className='container'>
<Picture key={picture.id} src={picture.src}>
//what is placed here is passed as props.children
</Picture>
</div>
)
}
```
Instead of invoking the component with a self-closing tag <Picture /> if you invoke it with full opening and closing tags <Picture> </Picture> you can then place more code between it.
This de-couples the <Picture> component from its content and makes it more reusable.

View file

@ -0,0 +1,25 @@
ternary operator for routing
When using `{}` for js expression make sure to wrap in under any component or element whether it is empty like `<> </>`
```jsx
const [route,setRoute]=useState('home')
return (
<>
{route === "home" ? (
<div className="App">
<Nav />
<Article
files={files}
setFiles={setFiles}
category={category}
setCategory={setCategory}
/>
</div>
) : (
<Output />
)}
</>
);
```

View file

@ -0,0 +1,161 @@
electron
https://www.electronjs.org/docs/latest/tutorial/tutorial-first-app
- Make a folder and open terminal and when in project directory execute this
`npm init`
- Now create a file `main.js` and print something like `hello world`
```js
console.log('hello world')
```
- Now to make `main.js` entry point. have this following configuration in `package.json`. main property have to point to main.js and start script have to be `electron .`
```json
{
"name": "electron-app",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "electron ."
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^33.2.0"
}
}
```
- Start app with `npm run start`. this will output hello world. no window will open as its only cli
&nbsp;
**GUI**
- to serve html file `index.html`
```html
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
</body>
</html>
```
- in main.js
```js
const { app, BrowserWindow } = require("electron");
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
title: "SilverBullet",
autoHideMenuBar: true,
icon: "images/silverbullet.jpg",
});
// win.loadFile("index.html");
win.loadURL("http://localhost:3001");
};
app.whenReady().then(() => {
createWindow();
});
app.on("window-all-closed", function () {
if (process.platform !== "darwin") {
app.quit();
}
});
```
&nbsp;
c
**Packaging electron app**
https://www.electronjs.org/docs/latest/tutorial/tutorial-packaging
- run following
```bash
npm install --save-dev @electron-forge/cli
npx electron-forge import
```
- then in package.json, following scripts will be added
```json
//...
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make"
},
//...
```
- to package use `npm run package`
&nbsp;
**Context menu**
https://stackoverflow.com/questions/71698892/how-to-apply-electron-context-menu-in-window
&nbsp;
**Open file in default app instead of new window**
- In electron if there are assets or files and we wanna open then we have to use shell object
and web-contents setWindowOpenHandler to intercept opening of new window to intercept them and direct it to default file opener
https://www.electronjs.org/docs/latest/api/web-contents#contentssetwindowopenhandlerhandler
```js
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
title: "SilverBullet",
autoHideMenuBar: true,
icon: "images/silverbullet.jpg",
});
// win.title = "Silverbullet";
// win.loadFile("index.html");
win.loadURL(`http://${hostname}:${PORT}`);
win.webContents.setWindowOpenHandler((details) => {
console.log("detected");
// shell.openExternal(url); // open link in default app
// console.log(__dirname, url);
// shell.openPath(path.join(__dirname, ur));
// shell.openPath(winDetails.url);
// const urlString = decodeURI(winDetails.url).replace(
// `http://${hostname}:${PORT}/`,
// "file://",
// );
const urlString = details.url.replace(
`http://${hostname}:${PORT}/`,
`file://${datadirname}/`,
// "",
);
console.log(urlString);
shell.openExternal(urlString);
return null;
});
};
```

View file

@ -0,0 +1,93 @@
Advanced
- server actions
able to use forms in server component
https://inv.in.projectsegfau.lt/watch?v=Yc2MJYwLqzs
- sending form data
https://nextjs.org/docs/pages/building-your-application/data-fetching/forms-and-mutations
https://www.youtube.com/watch?v=AbLAxs-7yl0
for `application/x-www-form-urlencoded`
just use
```jsx
<form action="/route/api" method="POST">
<input type="text" name="city">
</form>
const onSubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const response = await fetch("/addschool/api", {
method: "POST",
body: formData,
});
};
```
this will just Formdata which is not multipart but of urlencoded and field will be `city`
for multipart form data
```html
<form onSubmit={onSubmit}>
<input type="text" name="city">
</form>
```
- receiving form data
```js
import { redirect } from "next/navigation";
import fs from "fs";
export async function POST(request) {
// console.log("request: ", request.body);
const formData = await request.formData();
console.log(formData);
const number = formData.get("contactNumber");
const file = formData.get("imageAttachment");
const fileBuffer = Buffer.from(await file.arrayBuffer());
console.log("fileBuffer: ", fileBuffer);
fs.writeFileSync(file.name, fileBuffer);
console.log("number ", number);
redirect("/addschool");
}
```
- uploading file in vercel blob
https://vercel.com/brads-projects-55888f01/~/stores/blob/store_I4WjQqMnSOkFDxWt/guides
- standalone builds
node modules may contain lot of modules that are not required so
1. configure next.config.js like this
```js
module.exports={
output : "standalone"
}
```
2. then at dist there is standalone folder is created go to that folder and do `npm i` and then go to root directory
3. Remove the output standalone config and then build it again to have folder `.next`, copy this folder in standalone directory and when running `npm run start` it will run this build
- remove type errors in nextjs build
```js
// in next.config.js
module.exports = {
typescript: {
// !! WARN !!
// Dangerously allow production builds to successfully complete even if
// your project has type errors.
// !! WARN !!
ignoreBuildErrors: true,
},
}
```

View file

@ -0,0 +1,486 @@
Coding 10 Data fetching
Data Fetching
- Server components support various configurations for caching, revalidating and optimizing data fetching
- Client components have to use third party libraries such as TanStack Query which offers robus APIs
Structure
```text
src/app
users
page.jsx
page.css
```
In `src/app/users/page.jsx`
```js
import "./page.css";
function User(props) {
const { id, name, username, email, phone } = props;
return (
<div key={id} className="user">
<div className="col1">
<div>{name}</div>
<div>{username} </div>
</div>
<div className="col2">
<div className="">{email}</div>
<div className="">{phone}</div>
</div>
</div>
);
}
export default async function UsersPage() {
console.log("UsersPage loaded");
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
console.log(users);
return (
<div className="UsersPage">
{users.map((user) => (
<User
id={user.id}
name={user.name}
username={user.username}
email={user.email}
phone={user.phone}
/>
))}
</div>
);
}
```
In `src/app/users/page.css`
```css
body {
background: rgb(20, 20, 20);
}
.UsersPage {
text-align: left;
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 2rem;
padding: 4rem;
margin: auto;
}
.UsersPage .user {
min-width: 350px;
width: 60%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background: white;
margin: auto;
border: 1px solid grey;
border-radius: 5px;
}
.UsersPage .user .col1,
.UsersPage .user .col2 {
display: flex;
flex-direction: column;
/* align-items: center; */
flex-grow: 1;
}
.UsersPage .user .col2 {
align-items: end;
}
```
&nbsp;
**Loading and Error States**
Structure
```text
src/app
users
page.jsx
page.css
error.jsx
loading.jsx
```
In `@/users/page.jsx`
```js
import { resolve } from "styled-jsx/css";
import "./page.css";
function User(props) {
const { id, name, username, email, phone } = props;
return (
<div key={id} className="user">
<div className="col1">
<div className="font-bold">{name}</div>
<div>{username} </div>
</div>
<div className="col2">
<div className="">{email}</div>
<div className="">{phone}</div>
</div>
</div>
);
}
export default async function UsersPage() {
await new Promise((resolve) => setTimeout(resolve, 2000));
console.log("UsersPage loaded");
const response = await fetch("https://jsonplaceholder.typicode.com/users1");
const users = await response.json();
console.log(users);
return (
<div className="UsersPage">
{users.map((user) => (
<User
id={user.id}
name={user.name}
username={user.username}
email={user.email}
phone={user.phone}
/>
))}
</div>
);
}
```
In `@/users/page.css`
```css
body {
background: rgb(20, 20, 20);
}
.UsersPage {
text-align: left;
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 2rem;
padding: 4rem;
margin: auto;
}
.UsersPage .user {
min-width: 350px;
width: 60%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background: white;
margin: auto;
border: 1px solid grey;
border-radius: 5px;
}
.UsersPage .user .col1,
.UsersPage .user .col2 {
display: flex;
flex-direction: column;
/* align-items: center; */
flex-grow: 1;
}
.UsersPage .user .col2 {
align-items: end;
}
```
In `@/users/loading.jsx`
```js
export default function LoadingPage() {
return (
<div className="flex items-center justify-center h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-white"></div>
</div>
);
}
```
In `@/users/error.jsx`
```js
"use client";
import { useEffect } from "react";
export default function ErrorPage({ error }) {
useEffect(() => {
console.error(`${error}`);
}, [error]);
return (
<div className="flex items-center justify-center h-screen">
<div className="text-2xl text-red-500">Error fetching users data</div>
</div>
);
}
```
&nbsp;
**JSON Server Setup**
- Install npm package `json-server@0.17.4`. Make sure that this is the version
- At the root of project create 2 files named `db.json` and `server.js`.
In `db.json`type
```json
{
"products": [
{
"id": 1,
"title": "Product 1",
"price": 800,
"description": "Description 1"
},
{
"id": 2,
"title": "Product 2",
"price": 1000,
"description": "Description 2"
},
{
"id": 3,
"title": "Product 3",
"price": 2500,
"description": "Description 3"
}
]
}
```
In `server.js`
```js
const jsonserver = require("json-server");
const server = jsonserver.create();
const PORT = 3002;
const router = jsonserver.router("./db.json");
const middlewares = jsonserver.defaults();
server.use(middlewares);
server.use((req, res, next) => {
console.log("request recieved");
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, PATCH, OPTIONS"
);
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
server.use(router);
server.listen(PORT, () => {
console.log("JSON Server is running");
});
```
As port is 3002 so when we visit to `localhost:3002/products`. we get array of products
&nbsp;
Caching Data
- By default, Next.js automatically catches the returned values of fetch in the Data Cache on the server.
In `src/app/products/page.jsx`
```js
import "./page.css";
export default async function Products() {
console.log("Products loaded");
const response = await fetch("http://localhost:3002/products");
const products = await response.json();
return (
<ul className="Products space-y-4 p-4">
{products.map((product) => (
<li className="p-4 bg-white shadow-md rounded-lg text-gray-700">
<h2 className="text-xl font-semibold">{product.title}</h2>
<p>{product.description}</p>
<p className="text-lg font-medium">{product.price}</p>
</li>
))}
</ul>
);
}
```
&nbsp;
**Data Cache**
- It is a server-side cache that persists the result of data fteches across incoming server requests and deployments
- It improves app performance and reduce costs by eliminating need to refetch data from data source with every request
- ![8d6701c74b6fe0c0c8e567dba819b36c.png](../../../../_resources/8d6701c74b6fe0c0c8e567dba819b36c.png)
- When request is made instead of getting information from data source it checks if its exist on cache. If it does exist then it simply returns it otherwise it will get data from source, send back to client also saving it in cache.
- This data cache is stored in `.next/cache`. One such is `fetch-cache` in this folder. It contains cached data of the routes.
- Unlike browser cache this is server cache means even if we load this page in different browser or incognito mode we get same result
- If we clear the cache file and restart json and next dev server then we will get updated value
&nbsp;
**Opt out of caching**
- To simply opt out of caching when fetching, set field `cookie` as `no-store`.
eg
```js
const response = await fetch("http://localhost:3002/products", {
cache: "no-store",
});
```
- When using `no-store` all subsequent fetch requests will also be out out of caching too so its wise to place those fetch requests where caching is needed to use before then those where caching is opted out
- There is a workaround to use cache even after using it after fetch request with `cache` set as `no-store`
Type the following at top of the code
`export const fetchCache = "default-cache";`
This is route level
https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#fetchcache
Eg: In `src/app/products/page.jsx`
```js
import "./page.css";
export const fetchCache = "default-cache";
export default async function Products() {
console.log("Products loaded");
const response = await fetch("http://localhost:3002/products", {
cache: "no-store",
});
const products = await response.json();
const detailedResponse = await fetch("http://localhost:3002/products/1");
const detailedData = await detailedResponse.json();
console.log("detailedata: ", detailedData);
return (
<ul className="Products space-y-4 p-4">
{products.map((product) => (
<li className="p-4 bg-white shadow-md rounded-lg text-gray-700">
<h2 className="text-xl font-semibold">{product.title}</h2>
<p>{product.description}</p>
<p className="text-lg font-medium">{product.price}</p>
</li>
))}
</ul>
);
}
```
Other values of fetchCache
```js
export const fetchCache = 'auto'
// 'auto' | 'default-cache' | 'only-cache'
// 'force-cache' | 'force-no-store' | 'default-no-store' | 'only-no-store'
```
- Another way is using dynamic functions ( cookies(), headers(), searchParams ). Make sure to use them before making fetch requests. fetch requests before using dynamic functions will be cached by dedault though
Eg:
```js
import { cookies } from "next/headers";
import "./page.css";
// export const fetchCache = "default-cache";
export default async function Products() {
console.log("Products loaded");
const cookieStore = cookies();
cookieStore.get("sd");
const detailedResponse = await fetch("http://localhost:3002/products/1");
const detailedData = await detailedResponse.json();
const response = await fetch("http://localhost:3002/products", {
cache: "no-store",
});
const products = await response.json();
console.log("detailedata: ", detailedData);
return (
<ul className="Products space-y-4 p-4">
{products.map((product) => (
<li className="p-4 bg-white shadow-md rounded-lg text-gray-700">
<h2 className="text-xl font-semibold">{product.title}</h2>
<p>{product.description}</p>
<p className="text-lg font-medium">{product.price}</p>
</li>
))}
</ul>
);
}
```
&nbsp;
**Request Memoization** (Client-side caching)
- It is a technique that deduplicates requests for the same data within a single render pass. This feature is by default
- This approach allows for re-use of data in a React Component tree, prevents redundant network calls
- After data is fetched initially, result is stored in memory
- When same data is needed across different components in a rout( eg in a layout, page and multiple components ), it eliminates the need to fetch data a the top of tree
Structure
```text
src/app
products
layout.jsx
page.jsx
```
In `src/app/products/layout.jsx`
```js
export default async function LayoutPage({ children }) {
const productresponse = await fetch("http://localhost:3002/products");
const data = await productresponse.json();
console.log("product data :", data);
console.log("Product LayoutPage loaded");
return <>{children};</>;
}
```
this url is fetched in page.jsx too but at json server this route is accessed one time only bcz layout.jsx and page.jsx is in single render pass
- ![f2ea8455f4a7eb8c141ec7f64752c652.png](../../../../_resources/f2ea8455f4a7eb8c141ec7f64752c652.png)
- When request is made it checks memory for the result. If not then it goes to server and in server it checks data cache then it checks data source.
&nbsp;
Note: Its not limited to client side bcz components can be server component too
&nbsp;
Imp notes about it
- Request memoization is a React future and not limited to nextjs. In React 19 request memization will no longer be default feature. So its possible then in future it will be no longer by default too
- Memoization only applies to GET method in fetch requests.
- It applies to react component tree and not extend to route handlers as they are not part of react component tree
- For cases where fetch is not suitable (eg some database clients, CMS clients or GraphQL clients), we can use the React cache function to meoize functions.

View file

@ -0,0 +1,235 @@
Coding 2
**Private Folders**
- In nextjs routing is based on folders so things like grouping and management for code is also done by project so there have to be folders which is not routable or write ui logic rather than routing logic. These folders r called private folders.
- They begin with `_`(underscore). eg: `_lib`.
- If we want underscore to be as part of route then use the prefix `%5F` which is url encoded form of underscore(`_`).
&nbsp;
**Route Groups**
- Allows us to logically group our routes and project files without affecting the URL path structure.
- Folder purpose is not for routing only but for grouping of various routes and we may not want to effect the url. Example if project structure
```text
src/app
auth
Login
Register
page.jsx
````
To access we need `localhost:3000/auth/register`. If we want to access via `localhost:3000/register` then we need to tell next that `auth` is route group. surround auth with parenthesis like this `(auth)`.
Now
```text
src/app
(auth)
Login
Register
page.jsx
````
&nbsp;
**Layout**
- A page is UI that is unique to a route
- A layout is UI that is shared between multiple pages in the app.
Eg:
In `src/app/layout.jsx`
```jsx
export const metadata = {
title: "Next.js",
description: "Generated by Next.js",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<header style={{ background: "lightblue", padding: "1rem" }}>
Head
</header>
{children}
<footer style={{ background: "orange", padding: "1rem" }}>
Footer
</footer>
</body>
</html>
);
}
```
Now every page.jsx content will be under this header and footer. and the routes ie folder inside app and all those nested one will inherit too
&nbsp;
**Route Group Layout**
- We may not want the same layout for every nested route so to fix it we can use route groups for layout. Purpose being is to selectively apply layout to certain segments while leaving other unchanged
Structure
```text
src/app
(auth)
forgot-password
(with-auth-layout)
Login
Register
layout.jsx
page.jsx
```
In `src/app/(auth)/(with-auth-layout)/layout.jsx` .
```jsx
export default function AuthLayout({ children }) {
return (
<div className="">
<h2>Inner Layout</h2>
{children}
</div>
);
}
```
Now in `login` and `register` we would have
```text
Head
Inner Layout
Login
Footer
```
This inner layout is added for login and password only and wont work for forgot-password page
&nbsp;
**Routing metadata**
- Nextjs have metadata api to define metadata for each page
- Medadata ensures accurate and relevant information is displayed when the page is shared or indexed.
- Configuring metadata
- Export static metadata object
- Export a dynamic generateMetadata function
**MetaData Rules**
- Both layout.jsx and page.jsx files can export metadata. If defined in a layout, it applies to all pages in that layout, but defined in a page, then that page only.
- Metadata is read in order ie from root level to final page level.
- When there's metadata in multiple places for the same route, they get combined, but page metadata will replace layout metadata if they have the same properties
Structure
```text
src/app
about
page.jsx
page.jsx
layout.jsx
```
In `src/app/layout.jsx`
```jsx
// object has to be 'metadata'
export const metadata = {
title: "Next.js",
description: "Generated by Next.js",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
```
In `src/app/about/page.jsx`
```jsx
export const metadata = {
title: "About me",
};
export default function About() {
return <h1>About me</h1>;
}
```
all the routes will inherit the metadata of rootlayout page but the metadata inside their own page will take more precedence.
So for `localhost:3000/about` page title is 'About me' and descruption will be inherited by metadata object in `layout.jsx` in app folder.
**Dynamic metadata**
- Depends on dynamic info such as current route parameters, external data or metadata from parent segments
For dynamic routes, metadata may have to dynamic too. instead of exporting the object function is exported which returns the metadata with dynamic information
Structure
```text
src/app
products
[productId]
page.jsx
page.jsx
page.jsx
layout.jsx
````
In `src/app/products/[productId]/page.jsx`
```jsx
// function name has to be 'generateMetadata' case-sensitive
export const generateMetadata = ({ params }) => {
return {
title: `Product ${params.productId}`,
};
};
export default function ProductDetails({ params }) {
console.log("params: ", typeof params.productId);
return <h1> Details about Product {params.productId}</h1>;
}
```
As fetch can be used so this function ie generateMetadata can be async too
In `src/app/products/[productId]/page.jsx`
```jsx
// function name has to be 'generateMetadata' case-sensitive
export const generateMetadata = ({ params }) => {
return {
title: `Product ${params.productId}`,
};
};
export default function ProductDetails({ params }) {
console.log("params: ", typeof params.productId);
return <h1> Details about Product {params.productId}</h1>;
}
```
&nbsp;
**Title metadata**
- Title needed not be a string and it can be a object too. This object has 3 properties ie `absolute`, `default`, `template`.
- Default acts same as the one defined when title is string ie when inherited by other routes as when defined in layout.jsx
Structure
```text
src/app
blog
page.jsx
layout.jsx
```
In `src/app/layout.jsx`
```jsx
export const metadata = {
title: {
default: "Nextjs",
template: "%s | end",
},
description: "Generated by Next.js",
};
```
In `src/app/blog/page.jsx`
```jsx
export const metadata = {
title: {
default: "blog",
},
};
```
metadata of `localhost:3000/blog` is `blog | end`
If
```jsx
export const metadata = {
title: {
absolute: "blog",
},
};
```
then
metadata of `localhost:3000/blog` is `blog` as absolute will counter template too

View file

@ -0,0 +1,352 @@
Coding 3
**Link Component Navigation**
- To enable client-side navigation Next.js provides us with the Link component.
- The `<Link>` component is a React component that extends the HTML `<a>` element and is primary way for navigation
- To use it, we need to import it from 'next/link'.
Structure
```text
src/app
blog
page.jsx
products
page.jsx
page.jsx
```
In `src/app/page.jsx`
```jsx
import Link from "next/link";
const Home = () => {
return (
<>
<h1> Hello </h1>
<Link href="/blog">Blog</Link>
<Link href="/products">Products</Link>
</>
);
};
export default Home;
```
In `src/app/blog/page.jsx` `/` path is used to go to root page
```jsx
import Link from "next/link";
export default function Blog() {
return (
<>
<h1>Blog</h1>
<Link href="/">Home</Link>
</>
);
}
```
**For Dynamic Routes**
Structure
```text
src/app
blog
page.jsx
products
[productId]
page.jsx
page.jsx
page.jsx
```
In `src/app/products/page.jsx`
```jsx
import Link from "next/link";
export default function ProductList() {
const productId = 100;
return (
<>
<h1>Product List</h1>
<h2>Product 1</h2>
<h2>Product 2</h2>
<Link href={"products/3"} replace>
{" "}
<h2>Product 3</h2>{" "}
</Link>
<Link href={`products/${productId}`}>
{" "}
<h2>Product {productId}</h2>{" "}
</Link>
</>
);
}
```
- Link for Dynmaic routes is generated just like we access the value of variable.
With `replace` keyword we can clear the url stack history means after navigating to `localhost:3000/products/3` and when we click back button we will navigate to `localhost:3000` not `localhost:3000/products`.
&nbsp;
**Active Links**
- First we need to create links for login, register, forgot-password in layout.jsx page for `(auth)`. So instead of creating these 3 links for each page ie login, register and forgot-password we used layout.jsx to serve this as template/same for all route groups.
Structure
```text
src/app
(auth)
login
password
forgot-password
layout.jsx
page.jsx
```
In `src/app/(auth)/layout.jsx`
```jsx
import Link from "next/link";
const navLinks = [
{ name: "Register", href: "/register" },
{ name: "Login", href: "/login" },
{ name: "Forgot Password", href: "/forgot-password" },
];
export default function OuterAuthLayout({ children }) {
return (
<div className="">
{navLinks.map((link) => {
return <Link href={link.href}>{link.name}</Link>;
})}
{children}
</div>
);
}
```
- So in order to style it we need to know the path of current url so we need to use the `usePathname` from 'next/navigation' hook. As we r using hook we need client side rendering for this and have to type `use client` at top in jsx file.
Structure
```text
src/app
(auth)
login
password
forgot-password
layout.jsx
style.css
page.jsx
```
In `src/app/(auth)/layout.jsx`. Use the particular class for active link
```jsx
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import "./style.css";
const navLinks = [
{ name: "Register", href: "/register" },
{ name: "Login", href: "/login" },
{ name: "Forgot Password", href: "/forgot-password" },
];
export default function OuterAuthLayout({ children }) {
const pathname = usePathname();
return (
<div className="">
{navLinks.map((link) => {
const isActive = pathname.startsWith(link.href);
return (
<Link
href={link.href}
key={link.name}
className={isActive ? "active-link" : ""}
>
{link.name}
</Link>
);
})}
{children}
</div>
);
}
```
In `src/app/(auth)/style.css`
```css
a {
margin: 0 1rem;
color: blue;
}
a.active-link {
color: black;
}
```
&nbsp;
**Navigating Programitically**
- Instead of using jsx, javascript can be used too so for that we can use the hook ie `useRouter` from 'next/navigation'.
Structure
```text
src/app
order-product
page.jsx
page.jsx
```
In `src/app/order-product/page.jsx`
```jsx
"use client";
import { useRouter } from "next/navigation";
export default function OrderProduct() {
const router = useRouter();
const handleClick = () => {
console.log("order placed");
router.push("/");
};
return (
<>
<h1>Order Product</h1>
<button onClick={handleClick}>Place Order</button>
</>
);
}
```
router.push('/') will push the root route to url stack and when we click on back after going through homepage it will brink back to the page `localhost:3000/order-product`. In `Link` coponent there was a attribute named replace which clears the url stack so to do same programitically use `router.replace('/')`. Not only that but we can go to previous or next url stack too by `router.back()` and `router.forward()`
&nbsp;
**Templates**
- Templates are similar to layouts in the way they wrap each child layout or page
But with templates when a user navigates between routes that share a template, a new instance of component is mounted, DOM elements are recreated, state is not preserved.
- So to only to be used once we dont have to conserve states and need a template to recreate pages so their states dont conflict with each other
Without template
```text
src/app
(auth)
login
password
forgot-password
layout.jsx
style.css
page.jsx
```
In `src/app/(auth)/layout.jsx`
```jsx
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import "./style.css";
import { useState } from "react";
const navLinks = [
{ name: "Register", href: "/register" },
{ name: "Login", href: "/login" },
{ name: "Forgot Password", href: "/forgot-password" },
];
export default function OuterAuthLayout({ children }) {
const pathname = usePathname();
const [input, setInput] = useState("");
return (
<div className="">
<div className="">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
</div>
{navLinks.map((link) => {
const isActive = pathname.startsWith(link.href);
return (
<Link
href={link.href}
key={link.name}
className={isActive ? "active-link" : ""}
>
{link.name}
</Link>
);
})}
{children}
</div>
);
}
```
When we move to new route input will be same and wont be reset.
**With templates**
Just change the file name from layout.jsx to template.jsx and now input will be reset and nothing will be preserved.
Some limitations though. If there is a route group(say named inner-auth) like this
Structure
```text
src/app
(auth)
(inner-auth)
login
password
forgot-password
template.jsx
style.css
page.jsx
```
In this case, states between `login` and `password` routes will be shared not with `forget-password` route though.
&nbsp;
**Loading UI**
- Till now different files like `page.jsx`, `layout.jsx`, `not-found.jsx` and `template.jsx` have been used. There is another one ie `loading.jsx`.
Structure
```text
src/app
blog
first
second
loading.jsx
layout.jsx
page.jsx
page.jsx
```
In `src/app/blog/loading.jsx`
```jsx
export default function LoadingBlog() {
return (
<div className="LoadingBlog">
<h1>Loading</h1>
</div>
);
}
```
To test it, make sure that `src/app/blog/page.jsx` loads slower due to setTimeout. Make sure that layout.jsx wont load slow as first layout.jsx is loaded then loading.jsx contents then page.jsx.
In `src/app/blog/page.jsx`
```jsx
import Link from "next/link";
export const metadata = {
title: {
absolute: "blog",
default: "blog",
},
};
function promiseSetTimeOut(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
export default async function Blog() {
await promiseSetTimeOut(1000);
return (
<>
<h1>Blog</h1>
<Link href="/">Home</Link>
</>
);
}
```

View file

@ -0,0 +1,160 @@
Coding 4
**Error Handling**
- To handle errors, another file is there named `error.jsx`.
- During development mode it will tell the detailed error ie where in the ocde there is error but in the production mode ie when we run `npm run build` then `npm run start` then we get different kind of error and to show specific message in that case
Structure
```text
src/app
products
[productId]
reviews
[reviewId]
error.jsx
page.jsx
page.jsx
```
In `src/app/products/[productId]/reviews/[reviewId]/page.jsx`
```jsx
import { notFound } from "next/navigation";
function getRandomInt(count) {
return Math.floor(Math.random() * count);
}
export default function ReviewDetail(prop) {
console.log("params: ", prop.params);
if (Number(prop.params.reviewId) > 1000) {
notFound();
}
if (getRandomInt(2) == 1) {
throw new Error("error with review");
}
return (
<h1>
Review {prop.params.reviewId} about product {prop.params.productId}
</h1>
);
}
```
So to catch these errors and display them
In `src/app/products/[productId]/reviews/[reviewId]/error.jsx`
```jsx
"use client";
export default function ErrorBoundary({ error }) {
return <div className="">Error in reviewId. Error is {error.message}</div>;
}
```
Make sure that error boundary is client component
- Automatically wrap a route segment and its nested children in a React Error Boundary
- Create error UI tailored to specific segments using the file-system heirarchy to adjust granularity
- Isolate errors to affected segments while keeping the rest of the application functional.
- Add functionality to attempt to recover from an error without a full page reload
&nbsp;
Component Heirarchy
```jsx
<Layout>
<Template>
<ErrorBoundary fallback={<Error />} >
<Suspense fallback={<Loading />}>
<ErrorBoundary fallback={<NotFound />} >
<Page />
</ErrorBoundary>
</Suspense>
</ErrorBoundary>
</Template>
</Layout>
```
&nbsp;
**Recovering from Errors**
- In props of ErrorBoundary there is a property named error through which we can print error. Also there is one more field ie reset which tries to reload the component giving error in case it get resolved.
- In order for the error to get resolved the component which was giving error must be client component too so use `use client;` at top in `page.jsx`.
In `src/app/products/[productId]/reviews/[reviewId]/error.jsx`
```jsx
"use client";
export default function ErrorBoundary({ error, reset }) {
return (
<div className="">
Error in reviewId. Error is {error.message} <br />
<button onClick={reset}>Try again</button>
</div>
);
}
```
&nbsp;
**Handling Errors in Nested Routes**
- Errors bubble up to the closest parent error boundary
- An error.jsx file will cater to errors for all its nested child segments
- By positioning error.jsx files at different levels in the nested folders of a route , we can achieve a more granular level of error handling.
Structure
```text
src/app
products
[productId]
reviews
[reviewId]
error.jsx
page.jsx
page.jsx
```
So here `error.jsx` is at `reviewsId` so layout.jsx at products folder so layout.jsx of products will render but for reviews folder , it wont as error bondary is for reviewsId so all the upper parents component will render.
If we shift the `error.jsx` to `products` then products `layout.jsx` wont execute too.
&nbsp;
**Handling errors in Layout**
- It is true that error.jsx file should be as close as to route as possible but unlike page.jsx layout component will render before error component so if error occurs in layout then error.jsx at same route/folder wont work.
- In such case error.jsx must be in parent folder for that route containing layout.jsx
Structure
```text
src/app
products
[productId]
layout.jsx
error.jsx
page.jsx
layout.jsx
```
In `src/app/products/[productId]/layout.jsx`
```jsx
"use client";
function getRandomInt(count) {
return Math.floor(Math.random() * count);
}
export default function ProductIdLayout({ children }) {
if (getRandomInt(2) == 1) {
throw new Error("error with review");
}
return (
<>
<h2>Features Products</h2>
{children}
</>
);
}
```
In `src/app/products/error.jsx`
```jsx
"use client";
export default function ErrorBoundary({ error, reset }) {
return (
<div className="">
Error in reviewId. Error is {error.message} <br />
<button onClick={reset}>Try again</button>
</div>
);
}
```

View file

@ -0,0 +1,306 @@
Coding 5 (Parallel Routes)
More advanced topics
# **Parallel Routes**
- Parallel routes are similar to using multiple React components in a layout.jsx file like this
Structure
```text
src/app
complex-dashboard
users.jsx
reviews.jsx
notifications.jsx
page.jsx
layout.jsx
components
card.jsx
```
In `src/app/complex-dashboard/layout.jsx`
```jsx
import Notifications from "./notifications";
import Reviews from "./reviews";
import Users from "./users";
export default function ComplexDashboardLayout({ children }) {
return (
<>
<div className="">{children}</div> {/* Contents from page.jsx */}
<div style={{ display: "flex" }}>
<div style={{ display: "flex", flexDirection: "column" }}>
<Users /> {/* Contents from users.jsx */}
<Reviews /> {/* Contents from reviews.jsx */}
</div>
<div style={{ display: "flex", flex: 1 }}>
<Notifications /> {/* Contents from notifications.jsx */}
</div>
</div>
</>
);
}
```
In `src/app/complex-dashboard/page.jsx`
```jsx
export default function ComplexDashboard() {
return <h1>Complex Dashboard</h1>;
}
```
In `src/app/complex-dashboard/users.jsx`
```jsx
import Card from "../components/card";
export default function Users() {
return <Card>Users</Card>;
}
```
Similarly in `reviews.jsx` and `notifications.jsx`
In `src/app/components/card.jsx`
```jsx
export default function Card({ children }) {
const cardStyle = {
padding: "100px",
margin: "10px",
boxShadow: "0 4px 8px 0 rgba(0,0,0,0.2)",
border: "1px solid #ddd",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
return <div style={cardStyle}>{children}</div>;
}
```
&nbsp;
**Doing same with parallel routes**
- We have to use slots as they help structure our content in modular fashion
- We will use the '@folder_name' convention
- Each slot is passed as a prop to its corresponding 'layout.jsx' file
Transfer the files users.jsx, reviews.jsx and notifications.jsx like this
```bash
cd $projectRoot/src/app/complex-dashboard
mv users.jsx @users/page.js
mv reviews.jsx @reviews/page.js
mv notifications.jsx @notifications/page.js
```
Make sure that instead of 'page.jsx' 'page.js' should be used as that is expected from slots. Components may not rendered if `page.jsx` is used. Sometimes slot is not added in next so restart the dev server in such case.
Structure
```text
src/app
complex-dashboard
@users
page.js
@reviews
page.js
@notifications
page.js
page.jsx
layout.jsx
components
card.jsx
```
In `src/app/complex-dashboard/layout.jsx`
```jsx
export default function ComplexDashboardLayout({
children,
notifications,
users,
reviews,
}) {
console.log("users: ", users);
return (
<>
<div className="">{children}</div> {/* Contents from page.jsx */}
<div style={{ display: "flex" }}>
<div style={{ display: "flex", flexDirection: "column" }}>
{users} {/* Contents from users.jsx */}
{reviews} {/* Contents from reviews.jsx */}
</div>
<div style={{ display: "flex", flex: 1 }}>
{notifications} {/* Contents from notifications.jsx */}
</div>
</div>
</>
);
}
```
&nbsp;
Benefits of using parallel routes
- Ability to split a single layout into various slots, making the code more manageable though it is also possible with components
Coming to main benefits which is not possible with components
- Independent route handling
Unlike components, these slots can have their own loading and error states as they r handled like other routes without changing the url stack
This gives more control where different sections load at varying speeds or encounter unique errors.
![806ab46a3d11ebdd3d3d54109f75f173.png](../../../../_resources/806ab46a3d11ebdd3d3d54109f75f173.png)
- Sub navigation in routes
- Each slot can function as their own mini application with their own navigation and state management.
If we try to navigate to another route using components then it would be different as component is part of the page and when using `Link` component to change route then whole page has to be loaded so other component will not appear unless they r specifically rerendered in new page
![95bfea8e37d940bf5923d25fe037d8c4.png](../../../../_resources/95bfea8e37d940bf5923d25fe037d8c4.png)
&nbsp;
&nbsp;
# Unmatched Routes
- Adding a sub navigation in notifications slot
Structure
```text
src/app
complex-dashboard
@users
page.js
@reviews
page.js
@notifications
archived
page.jsx
page.js
page.jsx
layout.jsx
components
card.jsx
```
In `src/app/complex-dashboard/@notifications/archived/page.jsx`
```jsx
import Card from "@/app/components/card";
import Link from "next/link";
export default function Notifications() {
return (
<Card>
<div>Archived Notifications</div>
<Link href={"/complex-dashboard"}>Default</Link>
</Card>
);
}
```
In `src/app/complex-dashboard/@notifications/page.js`
```jsx
import Card from "@/app/components/card";
import Link from "next/link";
export default function Notifications() {
return (
<Card>
<div>Notifications</div>
<Link href={"/complex-dashboard/archived"}>Archived</Link>
</Card>
);
}
```
When user click on the link named 'Archived' then archived will be added in route so our route will change from `localhost:3000/complex-dashboard` to `localhost:3000/complex-dashboard/archived`.
- In layout.jsx there r 4 slots ie props.children, users, reviews and notifications. At `localhost:3000/complex-dashboard/archived` props.children, users, reviews are unmatched routes as archived folder is inside notifications slot.
- When navigating witin the UI and not adding `archived` directly with browser, nextjs retains the previously active state of a slot regardless of changes in the URL
- During page reload or directly typing archived in url we will get the error 404 if default.jsx is not found in corresponding unmatched slots. Even if one default.jsx is missing in any unmatched slot 404 error will there instead of other matched components getting loaded.
- Adding `default.jsx` in unmatched slot is neccessary as it serves as fallback to render contents when nextjs cannot retrieve a slot's active state from the current URL.
- We can define the UI for unmatched routes in any way in default.jsx and also mirror the content found in corresponding page.jsx.
Adding `default.jsx` in unmatched slots
```bash
# copying contents to default.jsx from page.jsx
cd $projectRoot/src/app/complex-dashboard
cp page.js default.js
cp @users/page.js @users/default.js
cp @reviews/page.js @reviews/default.js
```
Structure
```text
src/app
complex-dashboard
@users
default.js
page.js
@reviews
default.js
page.js
@notifications
archived
page.jsx
page.js
default.js
page.jsx
layout.jsx
components
card.jsx
```
&nbsp;
&nbsp;
# Conditional Routes
- With parallel routes it is possible to have conditional routes like login page or register page depending upon state and benefits with loading and error state and sub navigation unlike using component directly.
- Ternary operators can be used for conditional rendering
Structure
```text
src/app
complex-dashboard
@login.jsx
default.js
page.js
@users
default.js
page.js
@reviews
default.js
page.js
@notifications
archived
page.jsx
page.js
default.js
page.jsx
layout.jsx
components
card.jsx
```
In `src/app/complex-dashboard/@login/page.js` and `src/app/complex-dashboard/@login/default.js`
```jsx
import Card from "@/app/components/card";
export default function Login() {
return <Card>Please login to continue</Card>;
}
```
In `src/app/complex-dashboard/layout.jsx`
```jsx
export default function ComplexDashboardLayout({
children,
notifications,
users,
reviews,
login,
}) {
// if true that 'please login to continue' section wont appear
const isLoogedIn = false;
return isLoogedIn ? (
<>
<div className="">{children}</div> {/* Contents from page.jsx */}
<div style={{ display: "flex" }}>
<div style={{ display: "flex", flexDirection: "column" }}>
{users} {/* Contents from users.jsx */}
{reviews} {/* Contents from reviews.jsx */}
</div>
<div style={{ display: "flex", flex: 1 }}>
{notifications} {/* Contents from notifications.jsx */}
</div>
</div>
</>
) : (
login
);
}
```
Make sure that default.js file also exist which can be same as page.js for login slot

Some files were not shown because too many files have changed in this diff Show more