Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"image-embeds",
"list-table",
"code",
"reusable-snippets"
"reusable-snippets",
"react-hooks"
]
},
{
Expand Down Expand Up @@ -203,9 +204,7 @@
},
{
"group": "Custom Components",
"pages": [
"content/components/reusable-components"
]
"pages": ["content/components/reusable-components"]
},
{
"group": "API Components",
Expand Down
212 changes: 212 additions & 0 deletions react-hooks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
---
title: "React Hooks"
description: "Learn how to use React hooks"
icon: "react"
---

import { Counter } from "/snippets/counter.mdx";
import { ColorGenerator } from "/snippets/color-generator.mdx";

Create interactive examples and dynamic content in your documentation using familiar [React hooks](https://react.dev/reference/react/hooks).

<Note>
No imports needed! All standard React hooks are automatically available in your MDX files.
</Note>

## Available Hooks

You can use all standard [React hooks](https://react.dev/reference/react/hooks) in your documentation, such as `useState`, `useEffect`, and other core React functionality.

## Examples

### 1. Creating Components Directly in MDX

Create a component directly in your MDX file using React hooks to build interactive elements that respond to user actions:

*A basic counter component created with `useState` hook*

<Counter />

```jsx
export const Counter = () => {
const [count, setCount] = useState(0);

return (
<div>
<p>Current count: {count}</p>
<button onClick={() => setCount(count + 1)}>
+
</button>
</div>
);
}

<Counter />
```


### 2. Importing Components from Snippet Files

Import snippets into your MDX files to create reusable interactive components. Store snippets in the `snippets` folder for better organization. Learn more in the [reusable snippets documentation](/reusable-snippets).

*Interactive HSL color generator created with multiple React hooks*


<ColorGenerator />

<Steps>
<Step title="Create a snippet file">
Create a new file in the `snippets` folder with your component code.

```jsx /snippets/color-generator.mdx [expandable]
export const ColorGenerator = () => {
const [hue, setHue] = useState(180)
const [saturation, setSaturation] = useState(50)
const [lightness, setLightness] = useState(50)
const [colors, setColors] = useState([])

useEffect(() => {
const newColors = []
for (let i = 0; i < 5; i++) {
const l = Math.max(10, Math.min(90, lightness - 20 + i * 10))
newColors.push(`hsl(${hue}, ${saturation}%, ${l}%)`)
}
setColors(newColors)
}, [hue, saturation, lightness])

const copyToClipboard = (color) => {
navigator.clipboard
.writeText(color)
.then(() => {
console.log(`Copied ${color} to clipboard!`)
})
.catch((err) => {
console.error("Failed to copy: ", err)
})
}

return (
<div className="p-4 border dark:border-zinc-800 rounded-xl bg-white dark:bg-gray-800 shadow-sm">
<span className="text-xl mb-4 font-semibold text-gray-800 dark:text-white">HSL Color Generator</span>

<div className="space-y-4">
<div className="space-y-2">
<label className="block text-sm text-gray-700 dark:text-gray-300">
Hue: {hue}°
<input
type="range"
min="0"
max="360"
value={hue}
onChange={(e) => setHue(Number.parseInt(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 mt-1"
style={{
background: `linear-gradient(to right,
hsl(0, ${saturation}%, ${lightness}%),
hsl(60, ${saturation}%, ${lightness}%),
hsl(120, ${saturation}%, ${lightness}%),
hsl(180, ${saturation}%, ${lightness}%),
hsl(240, ${saturation}%, ${lightness}%),
hsl(300, ${saturation}%, ${lightness}%),
hsl(360, ${saturation}%, ${lightness}%))`,
}}
/>
</label>

<label className="block text-sm text-gray-700 dark:text-gray-300">
Saturation: {saturation}%
<input
type="range"
min="0"
max="100"
value={saturation}
onChange={(e) => setSaturation(Number.parseInt(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 mt-1"
style={{
background: `linear-gradient(to right,
hsl(${hue}, 0%, ${lightness}%),
hsl(${hue}, 50%, ${lightness}%),
hsl(${hue}, 100%, ${lightness}%))`,
}}
/>
</label>

<label className="block text-sm text-gray-700 dark:text-gray-300">
Lightness: {lightness}%
<input
type="range"
min="0"
max="100"
value={lightness}
onChange={(e) => setLightness(Number.parseInt(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 mt-1"
style={{
background: `linear-gradient(to right,
hsl(${hue}, ${saturation}%, 0%),
hsl(${hue}, ${saturation}%, 50%),
hsl(${hue}, ${saturation}%, 100%))`,
}}
/>
</label>
</div>

<div className="flex space-x-1">
{colors.map((color, idx) => (
<div
key={idx}
className="h-16 rounded flex-1 cursor-pointer transition-transform hover:scale-105"
style={{ backgroundColor: color }}
title={`Click to copy: ${color}`}
onClick={() => copyToClipboard(color)}
/>
))}
</div>

<div className="text-sm font-mono text-gray-700 dark:text-gray-300">
<p>
Base color: hsl({hue}, {saturation}%, {lightness}%)
</p>
</div>
</div>
</div>
)
}
```
</Step>
<Step title="Import the snippet">
Add an import statement at the top of your MDX file:

```jsx
import { ColorGenerator } from "/snippets/color-generator.mdx"
```
</Step>
<Step title="Use the component">
<p>Add the component to your MDX content wherever needed:</p>

```jsx
<ColorGenerator />
```
</Step>
</Steps>

## Important Considerations

<AccordionGroup>
<Accordion title="Client-Side Rendering Impact">
React hook components render on the client-side, which has several implications:

- **SEO**: Search engines might not fully index dynamic content
- **Initial Load**: Visitors may experience a flash of loading content before components render
- **Accessibility**: Ensure dynamic content changes are announced to screen readers
</Accordion>

<Accordion title="Performance Best Practices">
- **Optimize Dependency Arrays**: Include only necessary dependencies in your `useEffect` dependency arrays
- **Memoize Complex Calculations**: Use `useMemo` or `useCallback` for expensive operations
- **Reduce Re-renders**: Break large components into smaller ones to prevent cascading re-renders
- **Lazy Loading**: Consider lazy loading complex components to improve initial page load time
</Accordion>
</AccordionGroup>



112 changes: 112 additions & 0 deletions snippets/color-generator.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
export const ColorGenerator = () => {
const [hue, setHue] = useState(165)

Check warning on line 2 in snippets/color-generator.mdx

View check run for this annotation

Mintlify / Mintlify Validation - vale-spellcheck

snippets/color-generator.mdx#L2

Did you really mean 'setHue'?
const [saturation, setSaturation] = useState(84)

Check warning on line 3 in snippets/color-generator.mdx

View check run for this annotation

Mintlify / Mintlify Validation - vale-spellcheck

snippets/color-generator.mdx#L3

Did you really mean 'setSaturation'?
const [lightness, setLightness] = useState(31)

Check warning on line 4 in snippets/color-generator.mdx

View check run for this annotation

Mintlify / Mintlify Validation - vale-spellcheck

snippets/color-generator.mdx#L4

Did you really mean 'setLightness'?
const [colors, setColors] = useState([])

Check warning on line 5 in snippets/color-generator.mdx

View check run for this annotation

Mintlify / Mintlify Validation - vale-spellcheck

snippets/color-generator.mdx#L5

Did you really mean 'setColors'?

useEffect(() => {
const newColors = []

Check warning on line 8 in snippets/color-generator.mdx

View check run for this annotation

Mintlify / Mintlify Validation - vale-spellcheck

snippets/color-generator.mdx#L8

Did you really mean 'newColors'?
for (let i = 0; i < 5; i++) {
const l = Math.max(10, Math.min(90, lightness - 20 + i * 10))
newColors.push(`hsl(${hue}, ${saturation}%, ${l}%)`)
}
setColors(newColors)
}, [hue, saturation, lightness])

const copyToClipboard = (color) => {
navigator.clipboard
.writeText(color)
.then(() => {
console.log(`Copied ${color} to clipboard!`)
})
.catch((err) => {
console.error("Failed to copy: ", err)
})
}

return (
<div className="p-4 border dark:border-zinc-800 rounded-xl bg-white dark:bg-gray-900/50 shadow-sm not-prose">
<p className="text-xl mb-4 font-semibold text-gray-800 dark:text-white">HSL Color Generator</p>

<div className="space-y-4">
<div className="space-y-2">
<label className="block text-sm text-gray-700 dark:text-gray-300">
Hue: {hue}°
<input
type="range"
min="0"
max="360"
value={hue}
onChange={(e) => setHue(Number.parseInt(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 mt-1"
style={{
background: `linear-gradient(to right,
hsl(0, ${saturation}%, ${lightness}%),
hsl(60, ${saturation}%, ${lightness}%),
hsl(120, ${saturation}%, ${lightness}%),
hsl(180, ${saturation}%, ${lightness}%),
hsl(240, ${saturation}%, ${lightness}%),
hsl(300, ${saturation}%, ${lightness}%),
hsl(360, ${saturation}%, ${lightness}%))`,
}}
/>
</label>

<label className="block text-sm text-gray-700 dark:text-gray-300">
Saturation: {saturation}%
<input
type="range"
min="0"
max="100"
value={saturation}
onChange={(e) => setSaturation(Number.parseInt(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 mt-1"
style={{
background: `linear-gradient(to right,
hsl(${hue}, 0%, ${lightness}%),
hsl(${hue}, 50%, ${lightness}%),
hsl(${hue}, 100%, ${lightness}%))`,
}}
/>
</label>

<label className="block text-sm text-gray-700 dark:text-gray-300">
Lightness: {lightness}%
<input
type="range"
min="0"
max="100"
value={lightness}
onChange={(e) => setLightness(Number.parseInt(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 mt-1"
style={{
background: `linear-gradient(to right,
hsl(${hue}, ${saturation}%, 0%),
hsl(${hue}, ${saturation}%, 50%),
hsl(${hue}, ${saturation}%, 100%))`,
}}
/>
</label>
</div>

<div className="flex space-x-2">
{colors.map((color, idx) => (
<div
key={idx}
className="h-16 rounded flex-1 cursor-pointer transition-transform hover:scale-105"
style={{ backgroundColor: color }}
title={`Click to copy: ${color}`}
onClick={() => copyToClipboard(color)}
/>
))}
</div>

<div className="text-sm font-mono text-gray-700 dark:text-gray-300">
<p>
Base color: hsl({hue}, {saturation}%, {lightness}%)
</p>
</div>
</div>
</div>
)
}
32 changes: 32 additions & 0 deletions snippets/counter.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const Counter = () => {
const [count, setCount] = useState(0)

Check warning on line 2 in snippets/counter.mdx

View check run for this annotation

Mintlify / Mintlify Validation - vale-spellcheck

snippets/counter.mdx#L2

Did you really mean 'setCount'?

const increment = () => setCount(count + 1)
const decrement = () => setCount(count - 1)

return (
<div className="flex items-center justify-center">
<div className="flex items-center rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 shadow-sm">
<button
onClick={decrement}
className="flex items-center justify-center h-8 w-8 bg-white hover:bg-gray-100 text-gray-800 dark:bg-gray-900 dark:hover:bg-gray-800 dark:text-gray-200 border-r border-gray-200 dark:border-gray-700"
aria-label="Decrease"
>
-
</button>

<div className="flex text-sm items-center justify-center h-8 px-6 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 font-medium min-w-[4rem] text-center">
{count}
</div>

<button
onClick={increment}
className="flex items-center justify-center h-8 w-8 bg-white hover:bg-gray-100 text-gray-800 dark:bg-gray-900 dark:hover:bg-gray-800 dark:text-gray-200 border-l border-gray-200 dark:border-gray-700"
aria-label="Increase"
>
+
</button>
</div>
</div>
)
}