diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index fd3451f406d41..20e7bc95aed4c 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -14,6 +14,7 @@ import type { Thenable, RejectedThenable, Awaited, + ReactStore, } from 'shared/ReactTypes'; import type { Fiber, @@ -44,10 +45,12 @@ import { enableNoCloningMemoCache, enableViewTransition, enableGestureTransition, + enableStore, } from 'shared/ReactFeatureFlags'; import { REACT_CONTEXT_TYPE, REACT_MEMO_CACHE_SENTINEL, + REACT_STORE_TYPE, } from 'shared/ReactSymbols'; import { @@ -1145,6 +1148,10 @@ function useThenable(thenable: Thenable): T { return result; } +function useStore(store: ReactStore): T { + return store._current; +} + function use(usable: Usable): T { if (usable !== null && typeof usable === 'object') { // $FlowFixMe[method-unbinding] @@ -1156,6 +1163,12 @@ function use(usable: Usable): T { const context: ReactContext = (usable: any); return readContext(context); } + if (enableStore) { + if (usable.$$typeof === REACT_STORE_TYPE) { + const store: ReactStore = (usable: any); + return useStore(store); + } + } } // eslint-disable-next-line react-internal/safe-string-coercion diff --git a/packages/react-reconciler/src/__tests__/ReactUseStore-test.js b/packages/react-reconciler/src/__tests__/ReactUseStore-test.js new file mode 100644 index 0000000000000..a29393bac1cfa --- /dev/null +++ b/packages/react-reconciler/src/__tests__/ReactUseStore-test.js @@ -0,0 +1,51 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ +'use strict'; + +let React; +let ReactNoop; +let Scheduler; +let act; +let use; +let assertLog; +let createStore; + +describe('ReactUse', () => { + beforeEach(() => { + jest.resetModules(); + + React = require('react'); + ReactNoop = require('react-noop-renderer'); + Scheduler = require('scheduler'); + act = require('internal-test-utils').act; + use = React.use; + createStore = React.unstable_createStore; + + const InternalTestUtils = require('internal-test-utils'); + assertLog = InternalTestUtils.assertLog; + }); + + // @gate enableStore + it('should read the current value', async () => { + const store = createStore(1); + + function App() { + const value = use(store); + Scheduler.log(value); + return {value}; + } + + const root = ReactNoop.createRoot(); + await act(() => { + root.render(); + }); + assertLog([1]); + expect(root).toMatchRenderedOutput(1); + }); +}); diff --git a/packages/react/index.fb.js b/packages/react/index.fb.js index 828db7a48a542..fa49510fdec74 100644 --- a/packages/react/index.fb.js +++ b/packages/react/index.fb.js @@ -7,7 +7,11 @@ * @flow */ -import {captureOwnerStack as captureOwnerStackImpl} from './src/ReactClient'; +import { + captureOwnerStack as captureOwnerStackImpl, + unstable_createStore as createStore, +} from './src/ReactClient'; +import {enableStore} from 'shared/ReactFeatureFlags'; export { __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, @@ -76,4 +80,9 @@ if (__DEV__) { captureOwnerStack = captureOwnerStackImpl; } -export {captureOwnerStack}; +let unstable_createStore: typeof createStore; +if (enableStore) { + unstable_createStore = createStore; +} + +export {captureOwnerStack, unstable_createStore}; diff --git a/packages/react/src/ReactClient.js b/packages/react/src/ReactClient.js index 3ead64acf682f..12c5f91e7a6bb 100644 --- a/packages/react/src/ReactClient.js +++ b/packages/react/src/ReactClient.js @@ -30,6 +30,7 @@ import { isValidElement, } from './jsx/ReactJSXElement'; import {createContext} from './ReactContext'; +import {createStore} from './ReactStore'; import {lazy} from './ReactLazy'; import {forwardRef} from './ReactForwardRef'; import {memo} from './ReactMemo'; @@ -128,6 +129,8 @@ export { addTransitionType as unstable_addTransitionType, // enableGestureTransition startGestureTransition as unstable_startGestureTransition, + // enableStore + createStore as unstable_createStore, // DEV-only useId, act, diff --git a/packages/react/src/ReactStore.js b/packages/react/src/ReactStore.js new file mode 100644 index 0000000000000..06e44ba0c4010 --- /dev/null +++ b/packages/react/src/ReactStore.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {ReactStore} from 'shared/ReactTypes'; +import {REACT_STORE_TYPE} from 'shared/ReactSymbols'; +import {enableStore} from 'shared/ReactFeatureFlags'; + +export function createStore( + defaultValue: T, + reducer?: (T, mixed) => T, +): ReactStore { + if (!enableStore) { + throw new Error('Not implemented.'); + } + + const store: ReactStore = { + $$typeof: REACT_STORE_TYPE, + + _current: defaultValue, + _sync: defaultValue, + _transition: defaultValue, + }; + + return store; +} diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 216b6d668a95f..e3b0a239ad704 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -270,3 +270,5 @@ export const enableUpdaterTracking = __PROFILE__; export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const ownerStackLimit = 1e4; + +export const enableStore = false; diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index 937c01cf75912..8d2595716c4aa 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -51,6 +51,8 @@ export const REACT_VIEW_TRANSITION_TYPE: symbol = Symbol.for( 'react.view_transition', ); +export const REACT_STORE_TYPE: symbol = Symbol.for('react.store'); + const MAYBE_ITERATOR_SYMBOL = Symbol.iterator; const FAUX_ITERATOR_SYMBOL = '@@iterator'; diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 4c8ca77bb160b..7d7e12977dc29 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -64,6 +64,13 @@ export type ReactContext = { displayName?: string, }; +export type ReactStore = { + $$typeof: symbol, + _current: T, + _sync: T, + _transition: T, +}; + export type ReactPortal = { $$typeof: symbol | number, key: null | string, @@ -141,7 +148,7 @@ export type StartTransitionOptions = { name?: string, }; -export type Usable = Thenable | ReactContext; +export type Usable = Thenable | ReactContext | ReactStore; export type ReactCustomFormAction = { name?: string, diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 4298a267a36ff..be97f16ac7995 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -88,6 +88,7 @@ export const enableSrcObject = false; export const enableHydrationChangeEvent = true; export const enableDefaultTransitionIndicator = false; export const ownerStackLimit = 1e4; +export const enableStore = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 8a7a59ebb2654..08002512c9084 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -86,6 +86,7 @@ export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = __PROFILE__; export const enableProfilerNestedUpdatePhase = __PROFILE__; export const enableUpdaterTracking = __PROFILE__; +export const enableStore = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 6de2578838ff6..f82bfde271975 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -96,6 +96,7 @@ export const enableReactTestRendererWarning = true; export const disableDefaultPropsExceptForClasses = true; export const enableObjectFiber = false; +export const enableStore = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 643626d6f9731..0a8e95ea3059d 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -76,6 +76,7 @@ export const enableHydrationChangeEvent = false; export const enableDefaultTransitionIndicator = false; export const enableFragmentRefs = false; export const ownerStackLimit = 1e4; +export const enableStore = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index d22d5cadd744d..08c887b2fc6a5 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -91,6 +91,7 @@ export const enableDefaultTransitionIndicator = false; export const enableFragmentRefs = false; export const ownerStackLimit = 1e4; +export const enableStore = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index 700aebb1cc42d..5076766a35f46 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -40,6 +40,7 @@ export const enableViewTransition = __VARIANT__; export const enableComponentPerformanceTrack = __VARIANT__; export const enableScrollEndPolyfill = __VARIANT__; export const enableFragmentRefs = __VARIANT__; +export const enableStore = __VARIANT__; // TODO: These flags are hard-coded to the default values used in open source. // Update the tests so that they pass in either mode, then set these diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 839248e2e7b04..fa37fd3214314 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -38,6 +38,7 @@ export const { enableComponentPerformanceTrack, enableScrollEndPolyfill, enableFragmentRefs, + enableStore, } = dynamicFeatureFlags; // On WWW, __EXPERIMENTAL__ is used for a new modern build.