Alex's Avatar

Alex

@alexdln.com.bsky.social

Web Engineer, Author, Contributor, Photographer Frontend/React/Next.js/UX/DX/ะกapybara/Performance/Growth ๐Ÿ”จ Building slidebook.dev | atsky.app | robindoc.com | nimpl.dev ๐Ÿก Living alexdln.medium.com | github.com/alexdln

383 Followers  |  376 Following  |  1,097 Posts  |  Joined: 08.11.2024  |  2.4182

Latest posts by alexdln.com on Bluesky

A week ago, I received a booking form with a default date entered by the form creator. I went to fill it out today, but that date is already in the past, and I can't unselect that date because it's in the past and disabled.

I've booked for 10:00 AM on November 11th. I hope they approve it

14.11.2025 22:49 โ€” ๐Ÿ‘ 1    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
                    node?.addEventListener("resize", onSizeChangeThrottled);
                    onSizeChangeThrottled();

                    const unlisten = listen("node", (newNode) => {
                        node?.removeEventListener("resize", onSizeChangeThrottled);
                        node = newNode || null;
                        node?.addEventListener("resize", onSizeChangeThrottled);
                        onSizeChangeThrottled();
                    });

                    return () => {
                        unlisten();
                        node?.removeEventListener("resize", onSizeChangeThrottled);
                    };

node?.addEventListener("resize", onSizeChangeThrottled); onSizeChangeThrottled(); const unlisten = listen("node", (newNode) => { node?.removeEventListener("resize", onSizeChangeThrottled); node = newNode || null; node?.addEventListener("resize", onSizeChangeThrottled); onSizeChangeThrottled(); }); return () => { unlisten(); node?.removeEventListener("resize", onSizeChangeThrottled); };

For some reason, I feel like something here could be written more simply...

Nevertheless, I've almost completed an update for viewport tracking, which will allow you to register any element for tracking as quickly as possible

14.11.2025 15:48 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0

Awareness is the best thing you can do.

Today you might repeat something harmful to โ€œthat author on bskyโ€, tomorrow - to a close friend, and later - to your child. Where you draw the line between hatred and love is up to you.

Ignorance can come at an unbearably high cost.
#TransgenderAwarenessWeek

14.11.2025 15:00 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
A narrow garden path lined with dry, brown, end-of-season plants leads to a tree stump and fallen branches against an old stone wall. The scene looks overgrown and autumnal, with scattered leaves and a few green shrubs among mostly withered vegetation

A narrow garden path lined with dry, brown, end-of-season plants leads to a tree stump and fallen branches against an old stone wall. The scene looks overgrown and autumnal, with scattered leaves and a few green shrubs among mostly withered vegetation

Fall magic

14.11.2025 14:21 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
React Optimizing Compiler 
We gave an early preview of React Forget at React Conf 2021. Itโ€™s a compiler that automatically generates the equivalent of useMemo and useCallback calls to minimize the cost of re-rendering, while retaining Reactโ€™s programming model.

Recently, we finished a rewrite of the compiler to make it more reliable and capable. This new architecture allows us to analyze and memoize more complex patterns such as the use of local mutations, and opens up many new compile-time optimization opportunities beyond just being on par with memoization Hooks.

React Optimizing Compiler We gave an early preview of React Forget at React Conf 2021. Itโ€™s a compiler that automatically generates the equivalent of useMemo and useCallback calls to minimize the cost of re-rendering, while retaining Reactโ€™s programming model. Recently, we finished a rewrite of the compiler to make it more reliable and capable. This new architecture allows us to analyze and memoize more complex patterns such as the use of local mutations, and opens up many new compile-time optimization opportunities beyond just being on par with memoization Hooks.

_And apparently the react compiler does exactly that. I don't know why I didn't think of him right away_

14.11.2025 10:53 โ€” ๐Ÿ‘ 1    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0

...How can I simplify the decomposition?What if I have local state, but only a small element on the page depends on it?

useState, will trigger a check for all elements. Contection, on the other hand, solves this, but it requires creating a Provider at a higher level.

14.11.2025 10:44 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0

...Contection allows you to trigger a re-render at the exact point, skipping all other layers. The compiler, on the other hand, optimizes each layer to ensure optimal re-rendering. This speeds up the process without requiring any additional effort. And this is the question I'm asking myself now...

14.11.2025 10:44 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0

While searching for ways to speed up React apps, I decided to read up on the history of React Forget (compiler). I understand that it and Contection share the same goal, but they approach it from different angles...

14.11.2025 10:44 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0

I shouldn't get carried away with new ideas - it always ends badly...

But I sincerely believe that this is the path React development should take!

14.11.2025 00:18 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
const ComponentA = () => {
    const widthBreakpoint = useViewportWidthBreakpoint(ViewportStore, { enabled: "after-hydration" });
    console.log("ComponentA, subscribing to width breakpoint", widthBreakpoint.current);
    return <div>ComponentA: {widthBreakpoint.current}</div>;
};

const ComponentB = () => {
    const width = useViewportWidth(ViewportStore, { enabled: "after-hydration" });
    console.log("ComponentB, subscribing to width", width);
    return <div>ComponentB: {width}</div>;
};

const ComponentA = () => { const widthBreakpoint = useViewportWidthBreakpoint(ViewportStore, { enabled: "after-hydration" }); console.log("ComponentA, subscribing to width breakpoint", widthBreakpoint.current); return <div>ComponentA: {widthBreakpoint.current}</div>; }; const ComponentB = () => { const width = useViewportWidth(ViewportStore, { enabled: "after-hydration" }); console.log("ComponentB, subscribing to width", width); return <div>ComponentB: {width}</div>; };

It's past midnight, and I've only just finished setting up the "enabled" setting.

Now "contection" and "contection-viewport" have setting for manually controlling when to activate the subscription and re-render. The option for SSR solutions is also available out of the box

14.11.2025 00:16 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
Preview
contection/modules/viewport at main ยท alexdln/contection A state management library that extends React Context API with fine-grained subscriptions and computed values - alexdln/contection

Now you can subscribe to:
- width or height,
- breakpoint changes (width or height),
- relative changes (eg larger than lg);

Or use current viewport information without re-rendering at all.

And all this is built on a single resize observer, optimized and accelerated

13.11.2025 14:05 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
1. Create a Viewport Store
import { createViewportStore } from "contection-viewport";

const ViewportStore = createViewportStore({
  width: {
    device: {
      mobile: 0,
      tablet: 600,
      desktop: 1024,
    },
  },
});

1. Create a Viewport Store import { createViewportStore } from "contection-viewport"; const ViewportStore = createViewportStore({ width: { device: { mobile: 0, tablet: 600, desktop: 1024, }, }, });

2. Provide the Store
function App() {
  return (
    <ViewportStore>
      <YourComponents />
    </ViewportStore>
  );
}

2. Provide the Store function App() { return ( <ViewportStore> <YourComponents /> </ViewportStore> ); }

3. Use the Store
import { useViewportWidthBreakpoint } from "contection-viewport";

function ResponsiveComponent() {
  // Component re-renders only when 'default' breakpoint changes
  const breakpoint = useViewportWidthBreakpoint(ViewportStore, "default");

  return (
    <div>
      {breakpoint.current === "mobile" && <MobileView />}
      {breakpoint.current === "tablet" && <TabletView />}
      {breakpoint.current === "desktop" && <DesktopView />}
    </div>
  );
}

3. Use the Store import { useViewportWidthBreakpoint } from "contection-viewport"; function ResponsiveComponent() { // Component re-renders only when 'default' breakpoint changes const breakpoint = useViewportWidthBreakpoint(ViewportStore, "default"); return ( <div> {breakpoint.current === "mobile" && <MobileView />} {breakpoint.current === "tablet" && <TabletView />} {breakpoint.current === "desktop" && <DesktopView />} </div> ); }

import { useViewportWidth, useViewportHeight } from "contection-viewport";

function Dimensions() {
  // Component re-renders when width changes
  const width = useViewportWidth(ViewportStore);
  // Component re-renders when height changes
  const height = useViewportHeight(ViewportStore);

  return (
    <div>
      <p>Width: {width}px</p>
      <p>Height: {height}px</p>
    </div>
  );
}

import { useViewportWidth, useViewportHeight } from "contection-viewport"; function Dimensions() { // Component re-renders when width changes const width = useViewportWidth(ViewportStore); // Component re-renders when height changes const height = useViewportHeight(ViewportStore); return ( <div> <p>Width: {width}px</p> <p>Height: {height}px</p> </div> ); }

Contection isn't just an optimized state manager. It's the foundation for optimizations across every interaction.

At the same time, I wanted to keep it simple, so I added modules - separate packages for specific tasks! And this is the first of them - contection-viewport

13.11.2025 14:01 โ€” ๐Ÿ‘ 1    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
ComponentB, subscribing to width 1440
forward-logs-shared.ts:95 ComponentB, subscribing to width 1440
forward-logs-shared.ts:95 ComponentB, subscribing to <= lg false
forward-logs-shared.ts:95 ComponentB, subscribing to <= lg false
forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint xl
forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint xl
forward-logs-shared.ts:95 ComponentB, subscribing to width 1438
forward-logs-shared.ts:95 ComponentB, subscribing to width 1438
forward-logs-shared.ts:95 ComponentB, subscribing to width 1435
forward-logs-shared.ts:95 ComponentB, subscribing to width 1435
forward-logs-shared.ts:95 ComponentB, subscribing to width 1383
forward-logs-shared.ts:95 ComponentB, subscribing to width 1383
forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint lg
forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint lg
forward-logs-shared.ts:95 ComponentB, subscribing to width 1258
forward-logs-shared.ts:95 ComponentB, subscribing to width 1258
forward-logs-shared.ts:95 ComponentB, subscribing to <= lg true
forward-logs-shared.ts:95 ComponentB, subscribing to <= lg true
forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint md
forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint md
forward-logs-shared.ts:95 ComponentB, subscribing to width 1004
forward-logs-shared.ts:95 ComponentB, subscribing to width 1004
forward-logs-shared.ts:95 ComponentB, subscribing to width 866
forward-logs-shared.ts:95 ComponentB, subscribing to width 866
forward-logs-shared.ts:95 ComponentB, subscribing to width 790
forward-logs-shared.ts:95 ComponentB, subscribing to width 790
forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint sm
forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint sm

ComponentB, subscribing to width 1440 forward-logs-shared.ts:95 ComponentB, subscribing to width 1440 forward-logs-shared.ts:95 ComponentB, subscribing to <= lg false forward-logs-shared.ts:95 ComponentB, subscribing to <= lg false forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint xl forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint xl forward-logs-shared.ts:95 ComponentB, subscribing to width 1438 forward-logs-shared.ts:95 ComponentB, subscribing to width 1438 forward-logs-shared.ts:95 ComponentB, subscribing to width 1435 forward-logs-shared.ts:95 ComponentB, subscribing to width 1435 forward-logs-shared.ts:95 ComponentB, subscribing to width 1383 forward-logs-shared.ts:95 ComponentB, subscribing to width 1383 forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint lg forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint lg forward-logs-shared.ts:95 ComponentB, subscribing to width 1258 forward-logs-shared.ts:95 ComponentB, subscribing to width 1258 forward-logs-shared.ts:95 ComponentB, subscribing to <= lg true forward-logs-shared.ts:95 ComponentB, subscribing to <= lg true forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint md forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint md forward-logs-shared.ts:95 ComponentB, subscribing to width 1004 forward-logs-shared.ts:95 ComponentB, subscribing to width 1004 forward-logs-shared.ts:95 ComponentB, subscribing to width 866 forward-logs-shared.ts:95 ComponentB, subscribing to width 866 forward-logs-shared.ts:95 ComponentB, subscribing to width 790 forward-logs-shared.ts:95 ComponentB, subscribing to width 790 forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint sm forward-logs-shared.ts:95 ComponentA, subscribing to width breakpoint sm

Testing in a real project...

13.11.2025 13:41 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
issue-32 implement viewport module #35 Open
alexdln wants to merge 6 commits into main from issue-32  
+2,880 โˆ’34 
 Conversation 0
 Commits 6
 Checks 1
 Files changed 21

issue-32 implement viewport module #35 Open alexdln wants to merge 6 commits into main from issue-32 +2,880 โˆ’34 Conversation 0 Commits 6 Checks 1 Files changed 21

Wow, that was a lot harder than I expected. But it was fun and a really good test of Contection's capabilities and weaknesses!

13.11.2025 13:30 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
 PASS  modules/viewport/__tests__/createViewportStore.test.tsx

Test Suites: 3 passed, 3 total
Tests:       36 passed, 36 total
Snapshots:   0 total
Time:        7.049 s
Ran all test suites related to changed files.

PASS modules/viewport/__tests__/createViewportStore.test.tsx Test Suites: 3 passed, 3 total Tests: 36 passed, 36 total Snapshots: 0 total Time: 7.049 s Ran all test suites related to changed files.

It's so nice to finally have a working idea after a day of work.

I'm testing Contection as a basis for other packages - that should be interesting!

12.11.2025 20:27 โ€” ๐Ÿ‘ 1    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
### `Consumer`

Component that consumes the store using render props pattern.

**Props:**

- `children: (data) => React.ReactNode` - Render function
- `options?: { keys?: string[], mutation?: Function }` - Optional subscription and mutation options:
  - `keys?: string[]` - Array of store keys to subscribe to. If omitted, subscribes to all keys.
  - `mutation?: (newStore, prevStore?, prevMutatedStore?) => T` - Function to compute derived value from subscribed state. Receives:
    - `newStore` - Current store state (or selected keys if `keys` is provided)
    - `prevStore` - Previous store state (or selected keys). `undefined` on first call
    - `prevMutatedStore` - Previous result of the mutation function. `undefined` on first call

### `Consumer` Component that consumes the store using render props pattern. **Props:** - `children: (data) => React.ReactNode` - Render function - `options?: { keys?: string[], mutation?: Function }` - Optional subscription and mutation options: - `keys?: string[]` - Array of store keys to subscribe to. If omitted, subscribes to all keys. - `mutation?: (newStore, prevStore?, prevMutatedStore?) => T` - Function to compute derived value from subscribed state. Receives: - `newStore` - Current store state (or selected keys if `keys` is provided) - `prevStore` - Previous store state (or selected keys). `undefined` on first call - `prevMutatedStore` - Previous result of the mutation function. `undefined` on first call

There are already 760 lines in the README... I think it's time to think about a documentation website...

12.11.2025 10:39 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
A store display shelf featuring boxed toys of Buzz Lightyear and Woody from Toy Story. On the left are several Buzz Lightyear โ€œSpace Rangerโ€ action figures in white and purple packaging, and on the right are Woody โ€œSheriffโ€ dolls in blue and brown โ€œWoodyโ€™s Roundupโ€ boxes.

A store display shelf featuring boxed toys of Buzz Lightyear and Woody from Toy Story. On the left are several Buzz Lightyear โ€œSpace Rangerโ€ action figures in white and purple packaging, and on the right are Woody โ€œSheriffโ€ dolls in blue and brown โ€œWoodyโ€™s Roundupโ€ boxes.

In honor of the trailer for the new series, I can't not to share that I recently went into a Disney store for the first time. And when I saw this, I stood there and was as happy as a child ๐Ÿ‘ถ

11.11.2025 20:36 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
A screenshot of the GitHub PR creation interface, where an extra scrollbar appears at the bottom of the page.

A screenshot of the GitHub PR creation interface, where an extra scrollbar appears at the bottom of the page.

Well, I can understand when it's just another startup, but @github.com, how?

11.11.2025 18:24 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0

I added support for the prevMutatedStore argument, which is the result of the previous call. Is there any way to dynamically type this argument?

11.11.2025 13:08 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
    // interface TestStore {
    //     count: number;
    //     name: string;
    //     user: {
    //         id: number;
    //         email: string;
    //     };
    //     theme?: "light" | "dark";
    //     items: string[];
    // }
    // Update should accept partial store
    update({ count: 1 });
    update({ name: "New", theme: undefined });
    // @ts-expect-error - should not accept undefined value if it was not defined in the store
    update({ name: "New", items: undefined });
    update({ name: "New", items: [] });
    update((prev) => ({ count: prev.count + 1 }));
    // @ts-expect-error - should not accept invalid key
    update({ invalidKey: "value" });
    // @ts-expect-error - should not accept undefined value if it was not defined in the store
    update((prev) => ({ count: prev.count + 1, items: undefined }));
    update((prev) => ({ count: prev.count + 1, items: [] }));

// interface TestStore { // count: number; // name: string; // user: { // id: number; // email: string; // }; // theme?: "light" | "dark"; // items: string[]; // } // Update should accept partial store update({ count: 1 }); update({ name: "New", theme: undefined }); // @ts-expect-error - should not accept undefined value if it was not defined in the store update({ name: "New", items: undefined }); update({ name: "New", items: [] }); update((prev) => ({ count: prev.count + 1 })); // @ts-expect-error - should not accept invalid key update({ invalidKey: "value" }); // @ts-expect-error - should not accept undefined value if it was not defined in the store update((prev) => ({ count: prev.count + 1, items: undefined })); update((prev) => ({ count: prev.count + 1, items: [] }));

And this is the file that allows to control all such changes and guarantee the reliability of their typing

11.11.2025 13:05 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
// Test 4: useStore with keys
function testUseStoreWithKeys() {
    const data = useStore(Store, { keys: ["count", "name"] });

    // Should return only selected keys
    const count: number = data.count;
    const name: string = data.name;
    // @ts-expect-error - should not have unselected keys
    const user = data.user;
    // @ts-expect-error - should not accept invalid keys
    const invalid = useStore(Store, { keys: ["invalidKey"] });
}

// Test 5: useStore with mutation
function testUseStoreWithMutation() {
    // Mutation without keys
    const doubled = useStore(Store, {
        mutation: (Store) => Store.count * 2,
    });
    const doubledType: number = doubled;

    // Mutation with keys
    const computed = useStore(Store, {
        keys: ["count", "name"],
        mutation: (Store) => `${Store.name}: ${Store.count}`,
    });
    const computedType: string = computed;

// Test 4: useStore with keys function testUseStoreWithKeys() { const data = useStore(Store, { keys: ["count", "name"] }); // Should return only selected keys const count: number = data.count; const name: string = data.name; // @ts-expect-error - should not have unselected keys const user = data.user; // @ts-expect-error - should not accept invalid keys const invalid = useStore(Store, { keys: ["invalidKey"] }); } // Test 5: useStore with mutation function testUseStoreWithMutation() { // Mutation without keys const doubled = useStore(Store, { mutation: (Store) => Store.count * 2, }); const doubledType: number = doubled; // Mutation with keys const computed = useStore(Store, { keys: ["count", "name"], mutation: (Store) => `${Store.name}: ${Store.count}`, }); const computedType: string = computed;

These simple checks make updates and optimization much easier, and also provide strong protection against accidental generic breakage

11.11.2025 10:21 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
A screenshot of the Github changes shows that many lines have been removed in the GlobalStoreConsumer type overloads..
More details at https://github.com/alexdln/contection/pull/28/files#diff-f09367ce42e4a7deddfb5df986db284563dae0359aad0f4ba4a21923746aa992

A screenshot of the Github changes shows that many lines have been removed in the GlobalStoreConsumer type overloads.. More details at https://github.com/alexdln/contection/pull/28/files#diff-f09367ce42e4a7deddfb5df986db284563dae0359aad0f4ba4a21923746aa992

When implementing a small improvement resulted in half the types being refactored ๐Ÿซ 

(In fact, writing a file with tests for all type variations was one of the best ideas for a package like this)

11.11.2025 10:19 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0

Just in case, I will translate it into another language so that more people can understand.

Broken scrollbars have become too common problem ๐Ÿซ 
This is what happens when the entire company is Apple-powered...

* Please don't use w-screen within a scrollbar element, use w-full *

10.11.2025 21:35 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0

Broken scrollbars have become too common problem ๐Ÿซ 
This is what happens when the entire company is Apple-powered...

* Please don't use 100vw within a scrollbar element, use 100% *

10.11.2025 21:34 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 2    ๐Ÿ“Œ 0
Support prevMutatedStore argument in dispatch callback #27
@alexdln

Description
In some cases, a mutation will return an object, which will be created anew each time the mutation is called. This means a re-render will occur every time the subscribed keys change, even though the object's contents may remain the same.

This issue will add the ability to handle such cases. Users will be able to immediately return prevMutatedStore if the data they need has not changed (similar to working with setState).

The issue only concerns scenarios where there is a mutation and it returns an object; in other scenarios, everything is quite optimal.

Support prevMutatedStore argument in dispatch callback #27 @alexdln Description In some cases, a mutation will return an object, which will be created anew each time the mutation is called. This means a re-render will occur every time the subscribed keys change, even though the object's contents may remain the same. This issue will add the ability to handle such cases. Users will be able to immediately return prevMutatedStore if the data they need has not changed (similar to working with setState). The issue only concerns scenarios where there is a mutation and it returns an object; in other scenarios, everything is quite optimal.

I'm getting used to writing out all my ideas, as I'm starting to think through more issues in parallel and forgetting about many parts. It's also more public-friendly, which clearly benefits the package ๐Ÿ˜Š

10.11.2025 21:09 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0

By the way, I still can't decide for myself about portals - do I want to use them, or is it better to create a global store and communicate through it (with Contection it costs nothing)

10.11.2025 16:04 โ€” ๐Ÿ‘ 1    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
Fix: Activity should hide portal contents #35091
 Merged
acdlite merged 2 commits into facebook:main from acdlite:activity-portals-fix  16 minutes ago
+245 โˆ’57 
 Conversation 11
 Commits 2
 Checks 240
 Files changed 4
commented 9 hours ago

This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it.

Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React.

However, React does own the contents of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle.

The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM.

So, whenever an Activity boundary is hidden, we must search for and hide any portal that is contained within it, and recursively hide its direct children, too.

To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.

Fix: Activity should hide portal contents #35091 Merged acdlite merged 2 commits into facebook:main from acdlite:activity-portals-fix 16 minutes ago +245 โˆ’57 Conversation 11 Commits 2 Checks 240 Files changed 4 commented 9 hours ago This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it. Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React. However, React does own the contents of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle. The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM. So, whenever an Activity boundary is hidden, we must search for and hide any portal that is contained within it, and recursively hide its direct children, too. To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.

How complex and complicated any update becomes at some point. It's great that they're coming up with new abstractions instead of creating hundreds of workarounds or crutches to make it work

10.11.2025 16:03 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0

Iโ€™ve dreamed of being an engineer since childhood. I used to think it would be all about technical drawings and similar things (which I really loved), but programming has captivated me even more!

10.11.2025 15:54 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0

I was choosing an emoji and realized it couldโ€™ve been me with my mid-level mechanical qualification from the factory ๐Ÿ˜…

10.11.2025 15:51 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
[Success] Daily auto tests for React Canary
Daily auto tests for React Canary #3: Manually run by alexdln
main	
2 minutes ago 29s

[Failed] Daily auto tests for React Canary
Daily auto tests for React Canary #2: Manually run by alexdln
main	
11 minutes ago 25s

[Failed] Daily auto tests for React Canary
Daily auto tests for React Canary #1: Manually run by alexdln
main	
20 minutes ago 28s

[Success] Daily auto tests for React Canary Daily auto tests for React Canary #3: Manually run by alexdln main 2 minutes ago 29s [Failed] Daily auto tests for React Canary Daily auto tests for React Canary #2: Manually run by alexdln main 11 minutes ago 25s [Failed] Daily auto tests for React Canary Daily auto tests for React Canary #1: Manually run by alexdln main 20 minutes ago 28s

Well, now itโ€™s even more stable and reliable ๐Ÿง‘โ€๐Ÿ”ง

10.11.2025 15:46 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 1

@alexdln.com is following 20 prominent accounts