diff --git a/README.md b/README.md index da600f3..b0f5259 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ - [Server API](#config) - [Client API](#clientaddsignalssetters) - [Sync Caveats](#sync-caveats) +- [Migration](#migration) - [Examples](#examples) @@ -165,7 +166,7 @@ setCounter(function(count) end) ``` -Accessing the signal's value in an effect or computed signal will subscribe to it as a dependency. Changing the value will immediately update every effect and computed signal that depends signal, ensuring every part of your state is correct and up-to-date. +Accessing the signal's value in an effect or computed signal will subscribe to it as a dependency. Changing the value will immediately notify every effect and computed signal that depends on said signal, ensuring all of your state is correct and up-to-date. You can also pass a custom equality function to only update the signal if the new value is _not_ equal to the current value: @@ -180,7 +181,7 @@ setMax(-1) --> 2 ``` > [!NOTE] -> Looking for atoms? You can still use `atom()` to create a signal with a combined getter and setter. To use signals for APIs that require atoms, use `signalToAtom()`. +> Looking for atoms? You can still use [`atom()`](#atominitialvalue-equals) to create a signal with a combined getter and setter. --- @@ -794,6 +795,59 @@ This means nilable values can be represented as `None`, and code working with up --- +## Migration + +Charm v0.11 introduces _a lot_ of breaking changes, so below are some tips that might help you migrate from an older version. + +
+Terminology changes + +- Signal: A state container with one function to get the state, and another to update it +- Atom: A signal with the getter and setter combined into one function +- Effect: Main way to react to state changes +- `subscribe(getter, callback)`: Creates an effect that only subscribes to signals accessed in the getter + +
+ +
+ +**What to look out for:** + +1. Address all of the type errors introduced in your project after updating Charm. Most of them are caused by changes like: + - `batch()` was renamed to `batched()` + - The second arguments of `atom()` and `computed()` changed from an `options` table to an equality function + - Removed the cleanup argument in effect callbacks (`effect(function(cleanup) end)`) + +2. If you use Charm Sync, you'll have to rewrite a lot of your sync code. Fortunately, most of the changes should make your code _less_ complicated: + - You can now sync signals per-client, including computed signals. You shouldn't have to modify sync payloads to filter data anymore. + - Instead of creating client/server syncers, these modules now act like singletons. Sync APIs are called directly through `CharmSync.client`/`server`. + - [Read the updated docs for syncing state →](#client-server-sync) + +3. The [`strict` and `frozen` flags](#globals) are automatically enabled in Roblox Studio, so unsafe Charm code will start throwing errors if you didn't use the old `__DEV__` flag. The flags have the following behavior: + - `strict`: Yielding in effects, signals, and other critical Charm functions will throw an error + - `frozen`: Tables passed to signals are deeply frozen to strictly enforce data immutability and prevent accidental mutations + +4. Nested effects automatically clean up when the parent effect re-runs or gets disposed. In other words, all effects created during the execution of another effect will be added as a "child" and clean up with the parent effect. This might cause issues in code that relied on the old behavior, where effects were detached from the parent. + - This feature applies to all reaction APIs, including the listener function in `subscribe()` and the observer function in `observe()`. + - Effects that should not be tracked by a parent effect/scope should be wrapped in [`untracked()`](#untrackedcallback). + - This feature can cause runtime bugs that are hard to track! If you need to narrow down a cause, you can disable this feature by setting [`globals.trackInnerEffects`](#globals) to `false`. + - > [!NOTE] + > An example of this feature causing a bug is an old implementation of [`VideCharm.useAtom`](./packages/vide-charm/src/init.luau) that did not wrap the source update in `untracked()`. Because Vide effects run immediately after a source updates, Vide will notify components during the execution of the Charm effect in `useAtom`. + > + > This meant effects created as a side effect of a source update would implicitly get added as a child of the `useAtom` effect, and they could get disposed at the wrong time and desync UI. + +5. Consider refactoring your code to use some new quality-of-life features. Many of these are made possible with many thanks to [alien-signals](https://github.com/stackblitz/alien-signals)! + - Added [`signal()`](#signalinitialvalue-equals) to make reads and writes more explicit + - Added [`listen()`](#listengetter-callback) for running a subscription once immediately + - Added [`effectScope()`](#effectscopecallback) for collecting and cleaning up multiple effects at once + - Added [`onCleanup()`](#oncleanupcallback-failsilently) for running code when the currently-running effect, subscription, or observer cleans up + - The [`computed()`](#computedgetter) callback now gets called with the previous computed value + - Optimized `computed()` to use lazy evaluation instead of eager updates + - Optimized `mapped()` to only map values that changed in the original table + - Effect execution and ordering is more reliable + +--- + ## Examples Check out this simple [example project →](https://github.com/littensy/charm-example)