Library Compatibility #10
josephsavona
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
React Compiler optimizes components and hooks by modeling the JavaScript specification and the Rules of React. Where possible the compiler verifies that code honors the Rules of React and skips invalid code. In some cases it is infeasible to detect whether the code is valid or not — for example, when calling external functions — and the compiler assumes that code is following the rules.
Developers generally have control over whether they follow the rules of React in their own code. However, there are some additional considerations related to libraries:
Compiling Libraries with React Compiler
We recommend that React developers only compile their own source code with React Compiler, and do not compile 3rd-party code. This means that library authors have full control over the implementation of their library: whether to use React Compiler or manually optimize. We generally recommend that libraries follow the Rules of React as fully as possible in order to work correctly with advanced features such as concurrent rendering.
Designing Libraries for Usage with React Compiler
When designing the public API of a library it is important to consider whether the API can be consumed in a way that follows the Rules of React. These rules are not specific to React Compiler — they are general rules that ensure code is fully compatible with all of React's features including declarative rendering memoization, suspense, transitions, actions, streaming server rendering, and the compiler.
Examples of things to be careful of:
Any non-hook usage of your library should be memoize-able
In React, developers may wrap any code — except hook invocations — in a useMemo to avoid re-rendering. Thus, if your library exposes a non-Hook API, that API should work equivalently when wrapped in useMemo.
For example, imagine a library that provides a
useData()
hook which returns an object. The object has aread(key)
function which can be used to read the value of a specific key from the object:In this case, it should always be safe to wrap calls to
data.read()
in useMemo, since this method is not a hook. Thus it should be safe for users to do the following:To avoid issues with such usage, make sure that the API in question (
data.read()
here) is not accessing refs or other shared mutable state, unless there is some other mechanism that will force React to update the callsite. For example, if the data needs to change,useData()
could return a new value rather than mutate in-place.Returning Hooks as First-Class Values
In React, a component or hook must always call the exact same hooks in the exact same order on every render. This is because internally, React associates some state with each hook call — changing the hooks or their order could therefore associate the wrong state with the wrong hook, causing all kinds of unpredictable behavior and bugs in your application.
Libraries should therefore take care to provide hooks as static functions. Consider this example, where a library exposes a
useStore()
function which then returns a hook that can be read to "select" the part of the store that the user cares about. This example seems fine:However, developers may easily misunderstand that this function is hook, and use in incorrectly:
Instead, APIs should provide hooks as static functions. In the above, the library could export a separate, static
useSelector(store, selector)
function that can be used as follows:Testing Libraries with React Compiler
We recommend that library authors try testing their examples with React Compiler to see if those examples still work with the compiler applied. The compiler can diagnose violations of the rules of React and highlight bugs that may occur with or without the compiler, such as those discussed above.
Beta Was this translation helpful? Give feedback.
All reactions