FediScanner

#react

← Back

I wrote a test I expected to pass: my mutation hook shouldn't call `setState` after the component unmounts.

It passed once, failed once on a flake, and the flake was a real race: `fetch` resolves before the abort signal is observed, so the "is this still the latest request?" guard read a stale controller.

What closes it, and the contract every admin write follows now: roland.leth.ro/blog/tech/the-a

Show Original PostReport

This pattern holds callback passed to a customhook in the ref and update existing value everytime rendered to have the latest value.

function useDebounce(callback, delay) {
const callbackRef = React.useRef(callback)

React.useLayoutEffect(() => {
callbackRef.current = callback
})

return React.useMemo(
() => debounce((...args) => callbackRef.current(...args), delay),
[delay],
)
}

This pattern is named by Yago Pereria. The above example describes the pattern by using useDebounce. Calling callbackref.current in the debounced function, the old function closed by closure is not called, but the latest callback is always called.

Why is ref used?
Because you need to have a latest value without re-rendering. If you hold the value in state, the component renderes everytime update the value and invokes infinite loop.

Dealing with Dependency Array

This is the main focus of the second half of the article. Eventhough you have to obey the exhaustive-deps rule basically, you must not place current of ref in the Dependency Array. Since updating ref does not trigger a re-render, React cannot detect changes to current and recalculate effects or memoized values, which leads to behavior that is difficult to debug. Because ref is stable, the result doesn't change if includes ref in the dependencies array or not.

After React19 is introduced, useEffectEvent was designed precisely to incorporate this pattern into the official API.
If you have access to that environment, , there may be situations where a handwritten “latest ref” is no longer necessary.

Do callbacks really get updated that often?

The key point here is not so much that “the contents of the callback are rewritten,” but rather that a different function object is created with each render.

function SearchBox() {
const [query, setQuery] = useState('')
const [filter, setFilter] = useState('all')

const search = useDebounce(() => {
fetch(`/api?q=${query}&filter=${filter}`)
}, 500)
}

Although this arrow function appears only once in the source code, a new instance is created each time the page is rendered, and each instance encapsulates the current query and filter.
Since the query changes with every input, it effectively becomes a “different function” with every keystroke.

If you still hold the function that is generated in the first render, the function that runs is the function that watches query = "" and keeps searching blank. This is a typical bug of closure.

If you keep the initial render function as is, the function that checks the initial value query = ‘’ will be executed 500 ms later, and it will continue to search for an empty string indefinitely. This is a classic “stale closure” bug.

Well, why not just put it in the dependency array? But if you do that, the debounce will break.

return useMemo(() => debounce(callback, delay), [callback, delay])

Since the callback changes with every render, the debounced instance itself is recreated each time, and any pending timers are discarded. As a result, the delay is constantly reset, rendering the debounce ineffective.

In other words, there are two requirements, and they don't align.

I want the instance of debounce tp be stable(I don't want to re-invent the wheel) and I want the function in the instance to be up-to-date.

The ref is a tool to separate the two. Replace only the contens while preserving the identity of the outer function.

It seems possible to wrap that in the useCallback. In this way, user has a responsibility to manage array dependencies perfectly, and if even one is missed, it revertsto the same closure.

If the hook's authors handle it it using ref, it will work correctly even if users write it inline.

If you use it in the useEffect, the useEffectEvent that is introduced in React 19 solves this issue.
However, the usage for holding it outside of the effect is out of responsibility and latest ref pattern still holds good.#webdev #react #programming #frontend #software #coding #development #engineering #inclusive #community
React.js ~The Latest Ref Pattern ~

Show Original PostReport

For a long time, when I needed to make a React component flexible, my first thought always was:

Just pass everything through children.


And in many cases, that's exactly what you should do.

But once a component has several places where custom content can go, children can become a little too generic.

That's where the slot pattern becomes useful.

The confusing part is that React doesn't actually have a built-in Slot concept like some other frameworks do.

So what do people mean when they talk about slots in React?

Let's compare.

children: one place for any content


The most basic composition pattern in React is children.

type CardProps = {
children: React.ReactNode;
};

function Card({ children }: CardProps) {
return <div className="card">{children}</div>;
}

Then we can put anything inside:

<Card>
<h2>Profile</h2>
<p>Some information about the user</p>
</Card>

React passes everything between <Card> and </Card> through the children prop.

I usually think of children as:

Here is one area of the component. Put whatever you want inside it.


This works really well for things like:

  • cards
  • modals
  • layout wrappers
  • providers
  • containers

But what if the component has several separate customizable areas?

What if I need more than one children?


Imagine a modal:

<Modal>
...
</Modal>

It might have:

  • a title
  • main content
  • a footer
  • actions

Technically, we could put everything into children:

<Modal>
<h2>Delete account?</h2>

<p>This action cannot be undone.</p>

<div>
<Button>Cancel</Button>
<Button>Delete</Button>
</div>
</Modal>

This is fine if Modal doesn't need to know anything about that structure.

But sometimes it does.

Maybe the modal needs to render the title in one container, the content in another and the actions inside a sticky footer.

Then a single children prop is not very convenient.

Slots are basically named places for content


One common React version of the slot pattern is simply passing React nodes through different props.

type ModalProps = {
title: React.ReactNode;
children: React.ReactNode;
footer?: React.ReactNode;
};

function Modal({ title, children, footer }: ModalProps) {
return (
<div className="modal">
<header>{title}</header>

<main>{children}</main>

{footer && <footer>{footer}</footer>}
</div>
);
}

Now usage looks like this:

<Modal
title="Delete account?"
footer={
<>
<Button>Cancel</Button>
<Button>Delete</Button>
</>
}
>
<p>This action cannot be undone.</p>
</Modal>

Here:

  • children is the main content
  • title is one slot
  • footer is another slot

So, at least conceptually:

children is an unnamed/default slot, while slots give different pieces of content explicit destinations.


There is no special React syntax involved.

They're just props containing React nodes.

Why not use props for everything?


We could go even further:

<Modal
title="Delete account?"
description="This action cannot be undone."
cancelText="Cancel"
confirmText="Delete"
/>

And sometimes that's better.

The difference is that ordinary data props describe what the component should render, while slots usually let the consumer provide the actual UI.

For example:

footer={<CustomActions />}

gives much more freedom than:

confirmText="Delete"

Neither approach is always better.

It depends on how much control the component should expose.

Slots can make the API clearer


Let's imagine a component with several visual areas.

With generic children, I might need to invent additional wrappers:

<Card>
<CardHeader>
<h2>Settings</h2>
</CardHeader>

<CardContent>
...
</CardContent>

<CardFooter>
<Button>Save</Button>
</CardFooter>
</Card>

This is the compound component pattern, which is another common way to implement slot-like APIs.

Alternatively, I could make the slots explicit:

<Card
header={<h2>Settings</h2>}
footer={<Button>Save</Button>}
>
...
</Card>

Both APIs represent roughly the same idea:

This component has several predefined places where custom content can be inserted.


Which one I choose usually depends on the complexity of the component.

For something small, named props are often simpler.

For a larger component with many related parts, compound components can feel more natural.

A slot doesn't have to contain JSX


Another thing that confused me at first was thinking that every customizable area should be a ReactNode.

Sometimes a render function is more useful.

For example:

<List
items={users}
renderItem={(user) => (
<UserCard user={user} />
)}
/>

renderItem is not technically the same API as:

item={<UserCard />}

because the component passes data back to us.

But conceptually, it's solving a similar composition problem:

The library controls where something is rendered, while I control what gets rendered there.


You'll see this pattern a lot in component libraries.

So when do I use children?


I usually prefer children when the component has one obvious content area.

For example:

<Popover>
<SettingsForm />
</Popover>

or:

<PageLayout>
<Dashboard />
</PageLayout>

It's simple, familiar and doesn't add unnecessary API.

When do slots make more sense?


Slots become more useful when a component has several independent customizable areas.

For example:

<Page
header={<Header />}
sidebar={<Navigation />}
footer={<Footer />}
>
<Content />
</Page>

In this case, trying to put everything into a single children prop would probably make the API less explicit.

Slots also make sense when the component needs to control where each piece is rendered.

children vs slots

childrenSlotsOne default content areaMultiple named content areasBuilt into ReactA composition patternPassed through props.childrenUsually implemented with props or compound componentsGreat for wrappers and containersGreat for structured reusable componentsParent mostly provides contentComponent controls where each piece goes


And the important part:

They're not really competing concepts.


children can actually be one of the slots.

For example:

function Modal({
title,
children,
footer,
}: ModalProps) {
return (
<>
<header>{title}</header>
<main>{children}</main>
<footer>{footer}</footer>
</>
);
}

Here children is simply the default slot.

Final takeaway


React gives us children, but "slots" are mostly a pattern we build on top of React's normal composition model.

If my component has one customizable area, I usually reach for children.

If it has several distinct areas, I start thinking about named slots:

header
sidebar
footer
actions

or compound components:

<Card.Header />
<Card.Content />
<Card.Footer />

The goal isn't to use the more sophisticated pattern.

It's to make the component API obvious.

If I have to open the component implementation just to understand where my children will end up, that's usually a sign that the API could be clearer.#react #refactorit #webdev #frontend #software #coding #development #engineering #inclusive #community
React Children vs Slots: When to Use Each Pattern?

Show Original PostReport

If you've built a normal full-stack application, you probably know this architecture:

React → Express → MongoDB

Now let's add an AI-powered RAG pipeline.

Our architecture becomes:

React
↓
Express API
↓
RAG Service
↓
Embedding Model
↓
MongoDB Vector Search
↓
Relevant Context
↓
LLM
↓
Response

Let's break it down.

What Are We Building?


Imagine a developer documentation assistant.

Users upload technical documentation, and later ask:

How do I refresh an expired JWT?

Our application searches the uploaded documentation and gives the relevant information to an LLM before generating the response.

This is Retrieval-Augmented Generation (RAG).

1. Document Ingestion


First, documents need to enter our system.

PDF / Markdown / HTML
↓
Text Extraction
↓
Cleaning
↓
Chunking
↓
Embeddings
↓
MongoDB

Why chunk the documents?

Because retrieving a small relevant section is usually more useful than sending an entire document to the LLM.

Example:

authentication.md

├── Chunk 1
├── Chunk 2
├── Chunk 3
└── Chunk 4

2. Generate Embeddings


Each chunk is converted into a vector.

const embedding = await embeddingModel.embed(chunk);

Conceptually:

"Refresh tokens are used..."
↓
[0.12, -0.43, 0.77, ...]

The vector represents the semantic meaning of the text.

3. Store the Data


A MongoDB document might look like:

{
documentId: "auth-guide",
content: "Refresh tokens are used...",
embedding: [0.12, -0.43, 0.77],
metadata: {
page: 10,
source: "auth-guide.pdf"
}
}

MongoDB Atlas Vector Search can then be used to search these embeddings.

4. User Sends a Question


React sends:

POST /api/chat

{
"question": "How do I refresh an expired JWT?"
}

Express receives the request.

app.post("/api/chat", async (req, res) => {
const { question } = req.body;

const answer = await ragService.ask(question);

res.json({ answer });
});

5. Embed the Query


The question is converted into an embedding.

User Question
↓
Embedding Model
↓
Query Vector

Now the query vector can be compared with the document vectors.

6. Retrieve Relevant Chunks


Our vector search might return:

Refresh Token Documentation 0.94
JWT Authentication 0.89
Session Management 0.81
Database Configuration 0.31

We take the most relevant chunks.

This is the retrieval stage.

7. Build the Context


Now we combine the retrieved chunks:

const context = results
.map(result => result.content)
.join("\n\n");

Our application now has:

Question
+
Relevant Documentation

8. Build the Prompt


For example:

You are a developer documentation assistant.

Use the context below to answer the question.

Context:
[retrieved documents]

Question:
How do I refresh an expired JWT?

If the context doesn't contain the answer,
say that the information is unavailable.

9. Call the LLM


The backend sends the prompt to the LLM.

const answer = await llm.generate(prompt);

The model generates the response.

Then:

LLM
↓
Express
↓
React

React displays the answer.

Complete RAG Flow


USER
↓
React UI
↓
Express API
↓
RAG Service
↓
Query Embedding
↓
Vector Retrieval
↓
Relevant Chunks
↓
Context Construction
↓
Prompt Construction
↓
LLM
↓
Final Answer
↓
React

The Ingestion Side


Don't forget that there are actually two important pipelines.

Ingestion pipeline


Document
↓
Extract
↓
Clean
↓
Chunk
↓
Embed
↓
Store

Query pipeline


Question
↓
Embed
↓
Search
↓
Retrieve
↓
Build Context
↓
LLM
↓
Answer

This distinction makes RAG much easier to understand.

Why Chunking Is Important


Imagine a 200-page API reference.

The user asks:

How does refresh-token rotation work?

Sending the entire document to the LLM is inefficient.

Instead, retrieval might identify:

Chunk 47
Chunk 51
Chunk 52

as the relevant sections.

That's what makes the "retrieval" part valuable.

Production Improvements


A basic RAG demo isn't enough for a production application.

We could add:

  • Authentication
  • Rate limiting
  • Caching
  • Metadata filtering
  • Hybrid search
  • Reranking
  • Query rewriting
  • Citations
  • Observability
  • Token/cost tracking
  • Prompt-injection defenses

For example:

Query
↓
Validation
↓
Query Rewriting
↓
Hybrid Search
↓
Metadata Filtering
↓
Reranking
↓
Context
↓
LLM
↓
Citations

Example MERN Structure


server/

├── controllers/
│
├── routes/
│
├── models/
│
├── middleware/
│
└── services/
├── embeddingService.js
├── retrievalService.js
├── ragService.js
└── llmService.js

Keeping these services separate makes the architecture easier to maintain.

RAG Isn't Just "An LLM + Vector DB"


That's probably the most important lesson.

A useful RAG system depends on:

Document quality
+
Chunking
+
Embeddings
+
Retrieval
+
Context selection
+
Prompt design
+
LLM

If retrieval returns irrelevant information, even a powerful LLM can produce a poor response.

Final Architecture


A simple version:

React
↓
Node.js
↓
MongoDB Vector Search
↓
Relevant Context
↓
Gemini / OpenAI-style LLM
↓
React

A more advanced version:

React
↓
API Gateway
↓
Authentication
↓
Query Processing
↓
Hybrid Retrieval
↓
Reranking
↓
Context Compression
↓
LLM
↓
Citation Layer
↓
Response

And that's where a simple AI demo starts becoming a real full-stack engineering project.

Final Thought


The LLM is only one part of a RAG application.

The real engineering work happens around it:

ingestion → retrieval → context → generation → security → observability → optimization.

That's what makes RAG such an interesting architecture for full-stack developers.#ai #rag #react #node #software #coding #development #engineering #inclusive #community
Let’s Break Down a Full-Stack RAG Pipeline With React, Node.js & MongoDB

Show Original PostReport