Skip to content

Conversation

@atomiks
Copy link
Contributor

@atomiks atomiks commented Oct 16, 2025

This renames useEventCallback to useStableCallback, and useLatestRef to useValueAsRef to better signify what they're intended to be used for. Added JSDoc to explain each hook better.

@atomiks atomiks added the internal Behind-the-scenes enhancement. Formerly called “core”. label Oct 16, 2025
@atomiks atomiks force-pushed the refactor/untracked-hooks branch from 5327a8c to 851e845 Compare October 16, 2025 11:39
@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 16, 2025

vite-css-base-ui-example

pnpm add https://pkg.pr.new/mui/base-ui/@base-ui-components/react@2987
pnpm add https://pkg.pr.new/mui/base-ui/@base-ui-components/utils@2987

commit: 3859385

@netlify
Copy link

netlify bot commented Oct 16, 2025

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit 3859385
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/68f6cfe01e35cc0008aedbbd
😎 Deploy Preview https://deploy-preview-2987--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@mui-bot
Copy link

mui-bot commented Oct 16, 2025

Bundle size report

Bundle Parsed size Gzip size
@base-ui-components/react ▼-191B(-0.05%) ▼-79B(-0.07%)

Details of bundle changes

@romgrk romgrk added the breaking change Introduces changes that are not backward compatible. label Oct 16, 2025
Comment on lines +28 to +38
/**
* Stabilizes the function passed so it's always the same between renders.
*
* The function becomes non-reactive to any values it captures.
* It can safely be passed as a dependency of `React.useMemo` and `React.useEffect` without re-triggering them if its captured values change.
*
* The function must only be called inside effects and event handlers, never during render (which throws an error).
*
* This hook is a more permissive version of React 19.2's `React.useEffectEvent` in that it can be passed through contexts and called in event handler props, not just effects.
*/
export function useStableCallback<T extends Callback>(callback: T | undefined): T {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useStableCallback JSDoc

Comment on lines +5 to +10
/**
* Untracks the provided value by turning it into a ref to remove its reactivity.
*
* Used to access the passed value inside `React.useEffect` without causing the effect to re-run when the value changes.
*/
export function useValueAsRef<T>(value: T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useValueAsRef JSDoc

@atomiks atomiks force-pushed the refactor/untracked-hooks branch from 10ce18e to a571d4b Compare October 16, 2025 13:10
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Oct 20, 2025
@atomiks atomiks marked this pull request as ready for review October 20, 2025 23:57
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Oct 20, 2025
Copy link
Contributor

@romgrk romgrk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change to the utils package, can you release a new version of it when this is merged?

@atomiks
Copy link
Contributor Author

atomiks commented Oct 21, 2025

@romgrk the utils package is released together with the main react package, no?

@romgrk
Copy link
Contributor

romgrk commented Oct 21, 2025

No, the utils package is meant to be shared org-wide and decoupled from BUI. The goal is to avoid the problems of coupled versions that we had with @mui/utils released in sync with @mui/material, where we couldn't do breaking changes to the utils package. It doesn't have the internal- prefix only because that prefix is reserved for packages used within a repo, but the utils package is mostly just internal code that gets shared via npm because we have multiple repositories.

I see that its version is currently at 0.1.2, we could release it as 1.0.0 to start ensuring version numbers actually mean something.

@Janpot
Copy link
Member

Janpot commented Oct 21, 2025

It doesn't have the internal- prefix only because that prefix is reserved for packages used within a repo, but the utils package is mostly just internal code that gets shared via npm because we have multiple repositories.

Following your statement and our own guidelines: internal- is meant for packages that are not to be consumed by end-users, without strong semver guarantees. They can be used cross repositories, but evidently need to have their version pinned. According to those guidelines:

  • it should be called @base-ui-components/internal-utils
  • it should have its version pinned everywhere it's used
  • it is allowed to make breaking changes on a minor

But what breaking change do you need to make that can't happen in a backwards compatible way until the next major? (e.g. keep old export but mark as @deprecated).

I'm also happy to change or amend the guidelines if you have suggestions or a particular use-case that can't be sufficiently solved under them. I'm not sure it's the right strategy to fork major versions for the same npm scope. We all know how confusing that has been between X and core.

cc @oliviertassinari for visibility

@romgrk
Copy link
Contributor

romgrk commented Oct 21, 2025

According to those guidelines:

  • [...]
  • it is allowed to make breaking changes on a minor

[...]
I'm not sure it's the right strategy to fork major versions for the same npm scope.

I don't like that internal- packages aren't semver. I'm guessing those guidelines were put in place because the @mui/* util packages were all synced to @mui/material's version, but we don't really have a good reason to sync this new utils package to anything.

But what breaking change do you need to make that can't happen in a backwards compatible way until the next major? (e.g. keep old export but mark as @deprecated).

Which next major? The utils package isn't synced to anything right now. BUI is at 1.0.0-beta.4 and the utils is at 0.1.2. It's nicer to be able to evolve code without having to constantly worry about preserving deprecated code. The utils package in MUI even has a ton of exports as unstable_XXXX because of this limitation, but even then we also import it in other packages as @mui/utils/XXXX (which doesn't have the unstable_) prefix so we just end up with unstable_ prefixes for nothing because we can't make breaking changes anyway. Semver is the solution to let us evolve our codebase cleanly.

@Janpot
Copy link
Member

Janpot commented Oct 21, 2025

I don't like that internal- packages aren't semver.

For the record, I don't advocate for making it internal-, I make the argument for working on it in a backwards-compatible way within a major and sync majors within an npm scope. And I fully understand that it comes with challenges on our side.

but we don't really have a good reason to sync this new utils package to anything.

I think one advantage to sync major releases per npm scope is to signal end-users which packages are compatible with each other. e.g. similar to all babel packages sync their major release.

Another advantage is that dependants of the utils package will also be dependants of BUI itself. It will be helpful to those packages if they can make sure their dependency on utils and the BUI dependency on utils share the same instance. It could be important for deduplication. And it will become a hard requirement if utils gain singleton behavior, or begin to share react contexts. Having a synced release on major version with a loose dependency range is the most reliable way to guarantee it dedupes correctly during end-user installation.

Semver is the solution to let us evolve our codebase cleanly.

Semver is a mechanism that expresses compatibility between different versions of the same library. We also need to think about compatibility between different libraries that have to integrate. The answer needs more nuance. For a shared react support library I wouldn't necessarily recommend a workflow that make it easy to have multiple instances installed of the same library.

@romgrk
Copy link
Contributor

romgrk commented Oct 21, 2025

I make the argument for working on it in a backwards-compatible way within a major and sync majors within an npm scope.

We've already gone forward with an unsynced version, so we'll keep it as is for now, but let's make the upcoming release 0.2.0 instead of 1.0.0 so we keep that option open. If you want to push that suggestion forward, make your case on slack and/or the all components meeting as soon as possible so everyone affected can discuss it.

I think one advantage to sync major releases per npm scope is to signal end-users which packages are compatible with each other

But this isn't babel or even material-ui, the utils package in BUI is meant to be an org-wide generic utils package, it's only in BUI because we needed somewhere to place it. The utils package is a dependency of everything else, decoupled from any particular library.

It could be important for deduplication. And it will become a hard requirement if utils gain singleton behavior, or begin to share react contexts.

Duplication would happen with your strategy anyway (keeping old code with @deprecated annotations along with new code). I agree that the singleton/context behavior could become tricky to deal with but definitely not impossible, however for now I only aim to have pure(-ish) functions in the utils package. Let's deal with that problem if it ever comes up.

@atomiks atomiks merged commit 38f9485 into mui:master Oct 21, 2025
20 checks passed
@atomiks atomiks deleted the refactor/untracked-hooks branch October 21, 2025 23:56
@Janpot
Copy link
Member

Janpot commented Oct 22, 2025

But this isn't babel or even material-ui, the utils package in BUI is meant to be an org-wide generic utils package, it's only in BUI because we needed somewhere to place it. The utils package is a dependency of everything else, decoupled from any particular library.

You could almost argue it should be renamed and moved to mui/mui-public. 🙂

Duplication would happen with your strategy anyway (keeping old code with @deprecated annotations along with new code).

Not really, except perhaps during one patch version where dependants haven't had the chance to move to the new API yet. After that, tree-shaking removes the deprecated APIs. Nonetheless, the real harm comes from duplicate installations, not from having a shim around that allows usage of the old API until next major.

@mnajdova mnajdova added this to the v1.0.0 milestone Oct 22, 2025
@romgrk
Copy link
Contributor

romgrk commented Oct 23, 2025

You could almost argue it should be renamed and moved to mui/mui-public. 🙂

Having the utils in a separate repo from MUI X is already somewhat painful as changes needed there require a PR and a release in BUI. In fact we have BUI code duplicated at many locations there due to the poor DX. It would make the DX even less convenient if there was yet another repo to consider.

And tbh making breaking changes in the utils is something I appreciate, because it forces the rest of the codebases to evolve to better and more efficient patterns or hooks rather than stay on outdated code. For example, I've recently been reviewing the charts performance and because they stayed on an older (deprecated) version of the Store/selectors API, they ended up with lower performance and more refactoring work to do.

@Janpot
Copy link
Member

Janpot commented Oct 23, 2025

And tbh making breaking changes in the utils is something I appreciate, because it forces the rest of the codebases to evolve to better and more efficient patterns or hooks rather than stay on outdated code.

I think I disagree with this paradigm.:

  • I think the author of a breaking change in the utils package should own it end-to-end and thus be responsible for updating dependant libraries. The reason is to create the right incentive to avoid pushing unforeseen work on the product teams.
  • Doing the breaking change in a backwards compatible manner (create new API => update dependants => remove old API) also scales properly when multiple authors need to make breaking changes. Each can do so at their own pace and don't need to sync with a breaking release.
  • In case something went wrong in production, rolling back an update on a dependant library becomes almost trivial when the old APIs are still available.

@romgrk
Copy link
Contributor

romgrk commented Oct 23, 2025

We already understand the pain points of a sync'ed utils package with @mui/utils, I think we should try out an unsync'ed @base-ui-components/utils and see if that really causes issues down the line. Anyway our new hooks are too unstable for a major version sync'ed to BUI right now, as I said changes needed by X require a new release so we can't wait around for BUI without causing delays in X PRs.

@Janpot
Copy link
Member

Janpot commented Oct 23, 2025

We already understand the pain points of a sync'ed utils package with @mui/utils, I think we should try out an unsync'ed @base-ui-components/utils and see if that really causes issues down the line.

Sure. to note that we do understand the pain points since releases were unsynced before we synced them. Also to note that under what I proposed, renaming the package would suffice to adhere to the new guideline.

Can you clarify further, in your opinion, who do you think should be responsible for propagating breaking changes in this package to downstream packages? The author of the breaking change or the product team in question?

@romgrk
Copy link
Contributor

romgrk commented Oct 23, 2025

I don't know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Introduces changes that are not backward compatible. internal Behind-the-scenes enhancement. Formerly called “core”.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants