profile
viewpoint
Brian Vaughn bvaughn @facebook Mountain View, CA http://www.briandavidvaughn.com React JS core team at @facebook; formerly at @treasure-data and @google.

bvaughn/angular-form-for 436

Set of Angular directives to simplify creating and validating HTML forms.

bvaughn/debounce-decorator 72

Decorator for debouncing class methods

bvaughn/extensions-api-proposal-custom-performance-pane 35

Extensions API proposal for the Performance Panel

bvaughn/babel-repl 33

React powered Babel REPL

bvaughn/console.pretty 24

Pretty console logging

bvaughn/connect-tech-2016 12

Connect Tech 2016 presentation

bvaughn/forms-js 12

Core forms-js library

bvaughn/faux-codesandbox-client 7

Example Code Sandbox and React DevTools v4 integration

bvaughn/flat-object 5

Utilities for working with nested data structures

bvaughn/bootstrap-select-button 2

Creates an Angular-friendly bindable button dropdown using the Bootstrap CSS framework

issue closedbvaughn/react-window

recomputeRowHeights() in react-window?

I'm trying implement expandable row in VariableSizeList. Is there something like recomputeRowHeights() in react-window where I can force the List to re-calculate row height?

closed time in 6 hours

wot48

issue commentbvaughn/react-window

recomputeRowHeights() in react-window?

Have you searched the docs for this? :) It's been asked a few times.

wot48

comment created time in 20 hours

issue commentbvaughn/react-window

OverscanRowCount Causes A Lot of Unecessary Mounts

I'm not really looking at it to be honest 😅 I don't have the bandwidth right now. Maybe someone else would be interested in looking into it, although I've already started a v2 rewrite so it might be better to look at that instead.

I mentioned the overscan issue just in case you didn't know :)

I'm swamped with React and React DevTools stuff these days and this library rarely gets my attention because I just don't have the time :(

iansullivan88

comment created time in 20 hours

pull request commentreactjs/rfcs

useMutableSource

would like to notify children from the parent component. This used to be done in useLayoutEffect, but I found that it de-opts to sync mode,

Updating React state from inside of a layout effect will always trigger a sync update. That's because layout affects are meant to allow you to update layout (e.g. reposition a tooltip or a dialog) without the intermediate state being visible to the end user.

You could use one of the priority APIs (whatever they end up being in the stable release) to tell React to deprioritize the update, but if you were going to do that- you'd probably not want to use a layout effect to begin with.

So, only the way I found was to mutate it in render. I wonder if this is safe or unsafe, and possible alternatives.

Mutating in render is not safe for the reasons mentioned in the docs: https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects

Render phase may run multiple times, or may run and be thrown away without ever committing (if there's an error or a higher priority update). Mutations made during render won't get reset though and this will almost certainly cause problems.

bvaughn

comment created time in 20 hours

pull request commentreactjs/rfcs

useMutableSource

Can you link to a more specific location? Or summarize why you're getting that warning? Screen Shot 2020-02-21 at 3 44 45 PM

bvaughn

comment created time in a day

Pull request review commentfacebook/react

useMutableSource hook

 function loadModules({         });       }); +      describe('useMutableSource', () => {

Fair enough. I split the mutable source tests out with b028a0b.

bvaughn

comment created time in a day

push eventbvaughn/react

Brian Vaughn

commit sha b028a0bf6b3bc96038376a8437029c0377abef13

Split useMutableSource tests into separate suite

view details

push time in a day

push eventbvaughn/react

Andrew Clark

commit sha 5de5b61507d44c158fc0223728c5834fbd224ec5

Bugfix: `memo` drops lower pri updates on bail out (#18091) Fixes a bug where lower priority updates on a components wrapped with `memo` are sometimes left dangling in the queue without ever being processed, if they are preceded by a higher priority bailout. Cause ----- The pending update priority field is cleared at the beginning of `beginWork`. If there is remaining work at a lower priority level, it's expected that it will be accumulated on the work-in-progress fiber during the begin phase. There's an exception where this assumption doesn't hold: SimpleMemoComponent contains a bailout that occurs *before* the component is evaluated and the update queues are processed, which means we don't accumulate the next priority level. When we complete the fiber, the work loop is left to believe that there's no remaining work. Mitigation ---------- Since this only happens in a single case, a late bailout in SimpleMemoComponent, I've mitigated the bug in that code path by restoring the original update priority from the current fiber. This same case does not apply to MemoComponent, because MemoComponent fibers do not contain hooks or update queues; rather, they wrap around an inner fiber that may contain those. However, I've added a test case for MemoComponent to protect against a possible future regression. Possible next steps ------------------- We should consider moving the update priority assignment in `beginWork` out of the common path and into each branch, to avoid similar bugs in the future.

view details

Dan Abramov

commit sha 8b596e00a4fffd6bb5e6990f4dd59e40e80a5ea0

Remove unused arguments in the reconciler (#18092)

view details

Sebastian Markbåge

commit sha 65bbda7f169394005252b46a5992ece5a2ffadad

Rename Chunks API to Blocks (#18086) Sounds like this is the name we're going with. This also helps us distinguish it from other "chunking" implementation details.

view details

Sebastian Markbåge

commit sha 2c4221ce8bc5765bfddc4b32af4af602077a4a3e

Change string refs in function component message (#18031) This should refer to string refs specifically. The forwardRef part doesn't make any sense in this case. I think this was just an oversight.

view details

Brian Vaughn

commit sha 78e816032c8af962343abbf384e06f3e9bae9269

Don't warn about unmounted updates if pending passive unmount (#18096) I recently landed a change to the timing of passive effect cleanup functions during unmount (see #17925). This change defers flushing of passive effects for unmounted components until later (whenever we next flush pending passive effects). Since this change increases the likelihood of a (not actionable) state update warning for unmounted components, I've suppressed that warning for Fibers that have scheduled passive effect unmounts pending.

view details

Brian Vaughn

commit sha 0b0d75babe350d0da2ce6e4e3b7f1b30639c7f24

useMutableSource hook useMutableSource() enables React components to safely and efficiently read from a mutable external source in Concurrent Mode. The API will detect mutations that occur during a render to avoid tearing and it will automatically schedule updates when the source is mutated. RFC: reactjs/rfcs#147

view details

Brian Vaughn

commit sha 033de4bdebecebad84ba5d2948da2298dbb2b765

Refactored useMutableSource to use update queue This commit changes useMutableSource to use an update queue by composing useEffect and useState internally. This ended up being a little bigger than I would have expected due to the need to create mount, update, and rerender implementations, but that's probably okay for now. This commit also makes a few orthogonal changes as the result of discussions with Relay, Redux, etc: 1. Snapshots are eagerly evaluated so components can bailout early even when 'scoped subscriptions' are not possible. 2. The subscribe callback function now expects to be passed the updated snapshot value.

view details

Brian Vaughn

commit sha 41aeb139cc9253eb6a524dab9478d19999e3614c

Changed subscribe callback signature to not require the latest snapshot value This is being done to reduce how frequently we potentially deopt during render. I also tidied up the useMutableSource mount/update/rerender implementations to reduce their overall file size.

view details

Brian Vaughn

commit sha 86c18b6daf0fb757961417e7545c95b2a23345a5

Handle errors that occur during eager snapshot evaluation A selector might throw after a source mutation. e.g. it might try to read from a part of the store that no longer exists. In this case we should still schedule an update with React. Worst case the selector will throw again and then an error boundary will handle it.

view details

Brian Vaughn

commit sha 008cc368957f3ea3742818a93708fcb52cca0e9b

Fixed a typo/bug in setState updater function

view details

Brian Vaughn

commit sha 62832026d41f4747aa95e6ac7f9a1d2852fbb930

Avoid deopt on changed getSnapshot function unless snapshot also changes getSnapshot should be memoized to only change when its inputs change, but if that memoization is done incorrectly (or if the new snapshot just happens to be the same regardless of a changed dependency) then we can avoid deopting to a root re-render.

view details

Brian Vaughn

commit sha 91e5675d3b9227f08b459766201f6574e9c0c5ae

Read mutable source composed hooks from current dispatcher Rather than passing them in explicitly. This removes the extra function call.

view details

push time in a day

Pull request review commentfacebook/react

useMutableSource hook

 function loadModules({         });       }); +      describe('useMutableSource', () => {

Uh, sure. The test suite for this hook isn't particular large compared to other hooks though, especially useEffect :)

bvaughn

comment created time in a day

delete branch bvaughn/react

delete branch : suppress-unmounted-state-warning-pending-passive-effects

delete time in a day

push eventfacebook/react

Brian Vaughn

commit sha 78e816032c8af962343abbf384e06f3e9bae9269

Don't warn about unmounted updates if pending passive unmount (#18096) I recently landed a change to the timing of passive effect cleanup functions during unmount (see #17925). This change defers flushing of passive effects for unmounted components until later (whenever we next flush pending passive effects). Since this change increases the likelihood of a (not actionable) state update warning for unmounted components, I've suppressed that warning for Fibers that have scheduled passive effect unmounts pending.

view details

push time in a day

PR merged facebook/react

Reviewers
Don't warn about unmounted updates if pending passive unmount CLA Signed React Core Team

I recently landed a change to the timing of passive effect cleanup functions during unmount (see #17925). This change defers flushing of passive effects for unmounted components until later (whenever we next flush pending passive effects).

Since this change increases the likelihood of a (not actionable) state update warning for unmounted components, I've suppressed that warning for Fibers that have scheduled passive effect unmounts pending.

+191 -0

5 comments

2 changed files

bvaughn

pr closed time in a day

Pull request review commentfacebook/react

useMutableSource hook

 function rerenderReducer<S, I, A>(   return [newState, dispatch]; } +type MutableSourceState<Source, Snapshot> = {|+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+  snapshot: Snapshot,+  source: MutableSource<any>,+  subscribe: MutableSourceSubscribeFn<Source, Snapshot>,+|};++function readFromUnsubcribedMutableSource<Source, Snapshot>(+  source: MutableSource<Source>,+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+): Snapshot {+  const root = ((getWorkInProgressRoot(): any): FiberRoot);+  invariant(+    root !== null,+    'Expected a work-in-progress root. This is a bug in React. Please file an issue.',+  );++  const getVersion = source._getVersion;+  const version = getVersion();++  // Is it safe for this component to read from this source during the current render?+  let isSafeToReadFromSource = false;++  // Check the version first.+  // If this render has already been started with a specific version,+  // we can use it alone to determine if we can safely read from the source.+  const currentRenderVersion = getWorkInProgressVersion(source);+  if (currentRenderVersion !== null) {+    isSafeToReadFromSource = currentRenderVersion === version;+  } else {+    // If there's no version, then we should fallback to checking the update time.+    const pendingExpirationTime = getPendingExpirationTime(root, source);++    if (pendingExpirationTime === NoWork) {+      isSafeToReadFromSource = true;+    } else {+      // If the source has pending updates, we can use the current render's expiration+      // time to determine if it's safe to read again from the source.+      isSafeToReadFromSource =+        pendingExpirationTime === NoWork ||+        pendingExpirationTime >= renderExpirationTime;+    }++    if (isSafeToReadFromSource) {+      // If it's safe to read from this source during the current render,+      // store the version in case other components read from it.+      // A changed version number will let those components know to throw and restart the render.+      setWorkInProgressVersion(source, version);+    }+  }++  if (isSafeToReadFromSource) {+    return getSnapshot(source._source);+  } else {+    invariant(+      false,+      'Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.',+    );+  }+}++type MutableSourceRef<Source, Snapshot> = {|+  getSnapshot: null | MutableSourceGetSnapshotFn<Source, Snapshot>,+  source: null | MutableSource<Source>,+  subscribe: null | MutableSourceSubscribeFn<Source, Snapshot>,+|};++function useMutableSourceImpl<Source, Snapshot>(+  useEffectImpl: (+    create: () => (() => void) | void,+    deps: Array<mixed> | void | null,+  ) => void,+  useRefImpl: (+    initialValue: MutableSourceRef<Source, Snapshot>,+  ) => {|current: MutableSourceRef<Source, Snapshot>|},+  useStateImpl: (+    initialState: () => MutableSourceState<Source, Snapshot>,+  ) => [MutableSourceState<Source, Snapshot>, Dispatch<any>],+  source: MutableSource<Source>,+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+  subscribe: MutableSourceSubscribeFn<Source, Snapshot>,+): Snapshot {+  const getVersion = source._getVersion;+  const version = getVersion();++  const [state, setState] = useStateImpl(+    () =>+      ({+        getSnapshot,+        snapshot: readFromUnsubcribedMutableSource(source, getSnapshot),+        source,+        subscribe,+      }: MutableSourceState<Source, Snapshot>),+  );++  const ref = useRefImpl({+    getSnapshot: null,+    source: null,+    subscribe: null,+  });++  let didUnsubscribe = false;++  // If we got a new source or subscribe function,+  // we'll need to subscribe in a passive effect,+  // and also check for any changes that fire between render and subscribe.+  useEffectImpl(() => {+    const shouldResubscribe =+      subscribe !== ref.current.subscribe || source !== ref.current.source;++    // We need to re-subscribe any time either the source or the subscribe function changes.+    // We dont' need to re-subscribe when getSnapshot changes,+    // but we will need the latest getSnapshot inside of our change handler-+    // so we store a reference to it the ref as well.+    ref.current = {+      getSnapshot,+      source,+      subscribe,+    };++    if (shouldResubscribe) {+      const handleChange = () => {+        // It's possible that this callback will be invoked even after being unsubscribed,+        // if it's removed as a result of a subscription event/update.+        // In this case, React might log a DEV warning about an update from an unmounted component.+        // We can avoid triggering that warning with this check.+        if (didUnsubscribe) {

Yeah, I think I can remove this check once I rebase on master :)

bvaughn

comment created time in a day

Pull request review commentfacebook/react

useMutableSource hook

 function rerenderReducer<S, I, A>(   return [newState, dispatch]; } +type MutableSourceState<Source, Snapshot> = {|+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+  snapshot: Snapshot,+  source: MutableSource<any>,+  subscribe: MutableSourceSubscribeFn<Source, Snapshot>,+|};++function readFromUnsubcribedMutableSource<Source, Snapshot>(+  source: MutableSource<Source>,+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+): Snapshot {+  const root = ((getWorkInProgressRoot(): any): FiberRoot);+  invariant(+    root !== null,+    'Expected a work-in-progress root. This is a bug in React. Please file an issue.',+  );++  const getVersion = source._getVersion;+  const version = getVersion();++  // Is it safe for this component to read from this source during the current render?+  let isSafeToReadFromSource = false;++  // Check the version first.+  // If this render has already been started with a specific version,+  // we can use it alone to determine if we can safely read from the source.+  const currentRenderVersion = getWorkInProgressVersion(source);+  if (currentRenderVersion !== null) {+    isSafeToReadFromSource = currentRenderVersion === version;+  } else {+    // If there's no version, then we should fallback to checking the update time.+    const pendingExpirationTime = getPendingExpirationTime(root, source);++    if (pendingExpirationTime === NoWork) {+      isSafeToReadFromSource = true;+    } else {+      // If the source has pending updates, we can use the current render's expiration+      // time to determine if it's safe to read again from the source.+      isSafeToReadFromSource =+        pendingExpirationTime === NoWork ||+        pendingExpirationTime >= renderExpirationTime;+    }++    if (isSafeToReadFromSource) {+      // If it's safe to read from this source during the current render,+      // store the version in case other components read from it.+      // A changed version number will let those components know to throw and restart the render.+      setWorkInProgressVersion(source, version);+    }+  }++  if (isSafeToReadFromSource) {+    return getSnapshot(source._source);+  } else {+    invariant(+      false,+      'Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.',+    );+  }+}++type MutableSourceRef<Source, Snapshot> = {|+  getSnapshot: null | MutableSourceGetSnapshotFn<Source, Snapshot>,+  source: null | MutableSource<Source>,+  subscribe: null | MutableSourceSubscribeFn<Source, Snapshot>,+|};++function useMutableSourceImpl<Source, Snapshot>(+  useEffectImpl: (+    create: () => (() => void) | void,+    deps: Array<mixed> | void | null,+  ) => void,+  useRefImpl: (+    initialValue: MutableSourceRef<Source, Snapshot>,+  ) => {|current: MutableSourceRef<Source, Snapshot>|},+  useStateImpl: (+    initialState: () => MutableSourceState<Source, Snapshot>,+  ) => [MutableSourceState<Source, Snapshot>, Dispatch<any>],+  source: MutableSource<Source>,+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+  subscribe: MutableSourceSubscribeFn<Source, Snapshot>,+): Snapshot {+  const getVersion = source._getVersion;+  const version = getVersion();++  const [state, setState] = useStateImpl(+    () =>+      ({+        getSnapshot,+        snapshot: readFromUnsubcribedMutableSource(source, getSnapshot),+        source,+        subscribe,+      }: MutableSourceState<Source, Snapshot>),+  );++  const ref = useRefImpl({+    getSnapshot: null,+    source: null,+    subscribe: null,+  });++  let didUnsubscribe = false;++  // If we got a new source or subscribe function,+  // we'll need to subscribe in a passive effect,+  // and also check for any changes that fire between render and subscribe.+  useEffectImpl(() => {+    const shouldResubscribe =+      subscribe !== ref.current.subscribe || source !== ref.current.source;++    // We need to re-subscribe any time either the source or the subscribe function changes.+    // We dont' need to re-subscribe when getSnapshot changes,+    // but we will need the latest getSnapshot inside of our change handler-+    // so we store a reference to it the ref as well.+    ref.current = {+      getSnapshot,+      source,+      subscribe,+    };++    if (shouldResubscribe) {+      const handleChange = () => {+        // It's possible that this callback will be invoked even after being unsubscribed,+        // if it's removed as a result of a subscription event/update.+        // In this case, React might log a DEV warning about an update from an unmounted component.+        // We can avoid triggering that warning with this check.+        if (didUnsubscribe) {+          return;+        }++        const latestGetSnapshot = ((ref.current+          .getSnapshot: any): MutableSourceGetSnapshotFn<Source, Snapshot>);+        let newSnapshot = null;+        let snapshotDidThrow = false;+        try {+          newSnapshot = latestGetSnapshot(source._source);+        } catch (error) {+          snapshotDidThrow = true;

What's the benefit of this change?

Happy to make the change. Just curious.

bvaughn

comment created time in a day

Pull request review commentfacebook/react

useMutableSource hook

 function rerenderReducer<S, I, A>(   return [newState, dispatch]; } +type MutableSourceState<Source, Snapshot> = {|+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+  snapshot: Snapshot,+  source: MutableSource<any>,+  subscribe: MutableSourceSubscribeFn<Source, Snapshot>,+|};++function readFromUnsubcribedMutableSource<Source, Snapshot>(+  source: MutableSource<Source>,+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+): Snapshot {+  const root = ((getWorkInProgressRoot(): any): FiberRoot);+  invariant(+    root !== null,+    'Expected a work-in-progress root. This is a bug in React. Please file an issue.',+  );++  const getVersion = source._getVersion;+  const version = getVersion();++  // Is it safe for this component to read from this source during the current render?+  let isSafeToReadFromSource = false;++  // Check the version first.+  // If this render has already been started with a specific version,+  // we can use it alone to determine if we can safely read from the source.+  const currentRenderVersion = getWorkInProgressVersion(source);+  if (currentRenderVersion !== null) {+    isSafeToReadFromSource = currentRenderVersion === version;+  } else {+    // If there's no version, then we should fallback to checking the update time.+    const pendingExpirationTime = getPendingExpirationTime(root, source);++    if (pendingExpirationTime === NoWork) {+      isSafeToReadFromSource = true;+    } else {+      // If the source has pending updates, we can use the current render's expiration+      // time to determine if it's safe to read again from the source.+      isSafeToReadFromSource =+        pendingExpirationTime === NoWork ||+        pendingExpirationTime >= renderExpirationTime;+    }++    if (isSafeToReadFromSource) {+      // If it's safe to read from this source during the current render,+      // store the version in case other components read from it.+      // A changed version number will let those components know to throw and restart the render.+      setWorkInProgressVersion(source, version);+    }+  }++  if (isSafeToReadFromSource) {+    return getSnapshot(source._source);+  } else {+    invariant(+      false,+      'Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.',+    );+  }+}++type MutableSourceRef<Source, Snapshot> = {|+  getSnapshot: null | MutableSourceGetSnapshotFn<Source, Snapshot>,+  source: null | MutableSource<Source>,+  subscribe: null | MutableSourceSubscribeFn<Source, Snapshot>,+|};++function useMutableSourceImpl<Source, Snapshot>(+  useEffectImpl: (

This seemed like a reasonable alternative, but I'd be happy to change it. Would be minimal code impact- would just remove the extra function call.

bvaughn

comment created time in a day

issue commentfacebook/react

Add React DevTools to the Microsoft Edge extension store

Hi @brendyna! I'm wondering if you might be able to point me in the right direction. I've been trying to get a developer account setup so that I can publish the React extension, but I can't seem to get verified:

Screen Shot 2020-02-21 at 1 03 35 PM

There's no reason given, and the "Email support" link just takes me to a general FAQ page where there's nothing related that I can find. (The support chat has nothing about add-ons or developer accounts that I see.)

brendyna

comment created time in a day

push eventbvaughn/react

Brian Vaughn

commit sha fbd519401939b4fac4b87124b111699f36a5c2a8

Don't warn about unmounted updates if pending passive unmount I recently landed a change to the timing of passive effect cleanup functions during unmount (see #17925). This change defers flushing of passive effects for unmounted components until later (whenever we next flush pending passive effects). Since this change increases the likelihood of a (not actionable) state update warning for unmounted components, I've suppressed that warning for Fibers that have scheduled passive effect unmounts pending.

view details

push time in a day

pull request commentfacebook/react

Don't warn about unmounted updates if pending passive unmount

This change is narrowly scoped to only suppress warnings for things that happen between a committed unmount and flushing of passive effects. It won't block any warnings for anything else- including a setState call made from within the passive effects destroy function.

bvaughn

comment created time in a day

Pull request review commentfacebook/react

Don't warn about unmounted updates if pending passive unmount

 function loadModules({               ]);             });           });++          it('does not warn about state updates for unmounted components with pending passive unmounts', () => {+            let completePendingRequest = null;+            function Component() {+              Scheduler.unstable_yieldValue('Component');+              const [didLoad, setDidLoad] = React.useState(false);+              React.useLayoutEffect(() => {+                Scheduler.unstable_yieldValue('layout create');+                return () => {+                  Scheduler.unstable_yieldValue('layout destroy');+                };+              }, []);+              React.useEffect(() => {+                Scheduler.unstable_yieldValue('passive create');+                // Mimic an XHR request with a complete handler that updates state.+                completePendingRequest = () => setDidLoad(true);+                return () => {+                  Scheduler.unstable_yieldValue('passive destroy');+                };+              }, []);+              return didLoad;+            }++            act(() => {+              ReactNoop.renderToRootWithID(<Component />, 'root', () =>+                Scheduler.unstable_yieldValue('Sync effect'),+              );+              expect(Scheduler).toFlushAndYieldThrough([+                'Component',+                'layout create',+                'Sync effect',+              ]);+              ReactNoop.flushPassiveEffects();+              expect(Scheduler).toHaveYielded(['passive create']);++              // Unmount but don't process pending passive destroy function+              ReactNoop.unmountRootWithID('root');+              expect(Scheduler).toFlushAndYieldThrough(['layout destroy']);++              // Simulate an XHR completing, which will cause a state update-+              // but should not log a warning.+              completePendingRequest();++              ReactNoop.flushPassiveEffects();+              expect(Scheduler).toHaveYielded(['passive destroy']);+            });+          });++          it('still warns about state updates for unmounted components with no pending passive unmounts', () => {

Sure!

bvaughn

comment created time in a day

issue commentbvaughn/react-window

OverscanRowCount Causes A Lot of Unecessary Mounts

Do do you have overscanRowCount={50}? This is what's currently causing the large number of items to render.

iansullivan88

comment created time in a day

Pull request review commentfacebook/react

Don't warn about unmounted updates if pending passive unmount

 function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {       // Only warn for user-defined components, not internal ones like Suspense.       return;     }++    if (+      deferPassiveEffectCleanupDuringUnmount &&+      runAllPassiveEffectDestroysBeforeCreates+    ) {+      // If there are pending passive effects unmounts for this Fiber,+      // we can assume that they would have prevented this update.+      if (pendingPassiveHookEffectsUnmount.includes(fiber)) {+        return;+      }+    }+

Huh. Yeah for me me too.

bvaughn

comment created time in a day

PR opened facebook/react

Don't warn about unmounted updates if pending passive unmount

I recently landed a change to the timing of passive effect cleanup functions during unmount (see #17925). This change defers flushing of passive effects for unmounted components until later (whenever we next flush pending passive effects).

Since this change increases the likelihood of a (not actionable) state update warning for unmounted components, I've suppressed that warning for Fibers that have scheduled passive effect unmounts pending.

+97 -0

0 comment

2 changed files

pr created time in a day

push eventbvaughn/react

Brian Vaughn

commit sha f4be01002415ba34103995355d0c8a6d7e6de026

Don't warn about unmounted updates if pending passive unmount I recently landed a change to the timing of passive effect cleanup functions during unmount (see #17925). This change defers flushing of passive effects for unmounted components until later (whenever we next flush pending passive effects). Since this change increases the likelihood of a (not actionable) state update warning for unmounted components, I've suppressed that warning for Fibers that have scheduled passive effect unmounts pending.

view details

push time in a day

Pull request review commentfacebook/react

Implemented Profiler onCommit() and onPostCommit() hooks

 describe('Profiler', () => {     });   }); -  [true, false].forEach(enableSchedulerTracing => {-    describe('onRender callback', () => {-      beforeEach(() => {-        jest.resetModules();+  [true, false].forEach(deferPassiveEffectCleanupDuringUnmount => {

This method of testing feature flags is controversial. I think it's nice to have the extra coverage, and these tests are already very verbose so the idea of duplicating them is not very appealing- but I don't want to die on this hill. I'll remove this and just test one of the flag settings if that's the team preference.

bvaughn

comment created time in a day

push eventbvaughn/react

Brian Vaughn

commit sha 8b268544f662c51fb050098bcf511a58980e00b7

Avoid deopt on changed getSnapshot function unless snapshot also changes getSnapshot should be memoized to only change when its inputs change, but if that memoization is done incorrectly (or if the new snapshot just happens to be the same regardless of a changed dependency) then we can avoid deopting to a root re-render.

view details

push time in a day

pull request commentreactjs/rfcs

useMutableSource

I think @salvoravida's suggestion was a good one. I think it's more of a perf optimization than part of the RFC, but I updated the implementation proposal this morning: https://github.com/facebook/react/pull/18000/commits/e209b124cfe25057eb8b34f1875bf2e449d5c09e

bvaughn

comment created time in a day

push eventbvaughn/react

Brian Vaughn

commit sha e209b124cfe25057eb8b34f1875bf2e449d5c09e

Avoid deopt on changed getSnapshot function unless snapshot also changes getSnapshot should be memoized to only change when its inputs change, but if that memoization is done incorrectly (or if the new snapshot just happens to be the same regardless of a changed dependency) then we can avoid deopting to a root re-render.

view details

push time in a day

push eventbvaughn/react

Brian Vaughn

commit sha 3b3ba5e39aeaa759af60b6d27cd6114e13015de9

Fixed a typo/bug in setState updater function

view details

push time in a day

Pull request review commentfacebook/react

useMutableSource hook

 function rerenderReducer<S, I, A>(   return [newState, dispatch]; } +type MutableSourceState<Source, Snapshot> = {|+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+  snapshot: Snapshot,+  source: MutableSource<any>,+  subscribe: MutableSourceSubscribeFn<Source, Snapshot>,+|};++function readFromUnsubcribedMutableSource<Source, Snapshot>(+  source: MutableSource<Source>,+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+): Snapshot {+  const root = ((getWorkInProgressRoot(): any): FiberRoot);+  invariant(+    root !== null,+    'Expected a work-in-progress root. This is a bug in React. Please file an issue.',+  );++  const getVersion = source._getVersion;+  const version = getVersion();++  // Is it safe for this component to read from this source during the current render?+  let isSafeToReadFromSource = false;++  // Check the version first.+  // If this render has already been started with a specific version,+  // we can use it alone to determine if we can safely read from the source.+  const currentRenderVersion = getWorkInProgressVersion(source);+  if (currentRenderVersion !== null) {+    isSafeToReadFromSource = currentRenderVersion === version;+  } else {+    // If there's no version, then we should fallback to checking the update time.+    const pendingExpirationTime = getPendingExpirationTime(root, source);++    if (pendingExpirationTime === NoWork) {+      isSafeToReadFromSource = true;+    } else {+      // If the source has pending updates, we can use the current render's expiration+      // time to determine if it's safe to read again from the source.+      isSafeToReadFromSource =+        pendingExpirationTime === NoWork ||+        pendingExpirationTime >= renderExpirationTime;+    }++    if (isSafeToReadFromSource) {+      // If it's safe to read from this source during the current render,+      // store the version in case other components read from it.+      // A changed version number will let those components know to throw and restart the render.+      setWorkInProgressVersion(source, version);+    }+  }++  if (isSafeToReadFromSource) {+    return getSnapshot(source._source);+  } else {+    invariant(+      false,+      'Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.',+    );+  }+}++type MutableSourceRef<Source, Snapshot> = {|+  getSnapshot: null | MutableSourceGetSnapshotFn<Source, Snapshot>,+  source: null | MutableSource<Source>,+  subscribe: null | MutableSourceSubscribeFn<Source, Snapshot>,+|};++function useMutableSourceImpl<Source, Snapshot>(+  useEffectImpl: (+    create: () => (() => void) | void,+    deps: Array<mixed> | void | null,+  ) => void,+  useRefImpl: (+    initialValue: MutableSourceRef<Source, Snapshot>,+  ) => {|current: MutableSourceRef<Source, Snapshot>|},+  useStateImpl: (+    initialState: () => MutableSourceState<Source, Snapshot>,+  ) => [MutableSourceState<Source, Snapshot>, Dispatch<any>],+  source: MutableSource<Source>,+  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,+  subscribe: MutableSourceSubscribeFn<Source, Snapshot>,+): Snapshot {+  const getVersion = source._getVersion;+  const version = getVersion();++  const [state, setState] = useStateImpl(+    () =>+      ({+        getSnapshot,+        snapshot: readFromUnsubcribedMutableSource(source, getSnapshot),+        source,+        subscribe,+      }: MutableSourceState<Source, Snapshot>),+  );++  const ref = useRefImpl({+    getSnapshot: null,+    source: null,+    subscribe: null,+  });++  let didUnsubscribe = false;++  // If we got a new source or subscribe function,+  // we'll need to subscribe in a passive effect,+  // and also check for any changes that fire between render and subscribe.+  useEffectImpl(() => {+    const shouldResubscribe =+      subscribe !== ref.current.subscribe || source !== ref.current.source;++    // We need to re-subscribe any time either the source or the subscribe function changes.+    // We dont' need to re-subscribe when getSnapshot changes,+    // but we will need the latest getSnapshot inside of our change handler-+    // so we store a reference to it the ref as well.+    ref.current = {+      getSnapshot,+      source,+      subscribe,+    };++    if (shouldResubscribe) {+      const handleChange = () => {+        // It's possible that this callback will be invoked even after being unsubscribed,+        // if it's removed as a result of a subscription event/update.+        // In this case, React might log a DEV warning about an update from an unmounted component.+        // We can avoid triggering that warning with this check.+        if (didUnsubscribe) {+          return;+        }++        const latestGetSnapshot = ((ref.current+          .getSnapshot: any): MutableSourceGetSnapshotFn<Source, Snapshot>);+        let newSnapshot = null;+        let snapshotDidThrow = false;+        try {+          newSnapshot = latestGetSnapshot(source._source);+        } catch (error) {+          snapshotDidThrow = true;+        }++        setState(prevState => {+          // Ignore values from stale sources!+          // Since we subscribe an unsubscribe in a passive effect,+          // it's possible that this callback will be invoked for a stale (previous) subscription.+          // This check avoids scheduling an update for that stale subscription.+          if (+            prevState.source !== source ||+            prevState.subscribe !== subscribe+          ) {+            return prevState;+          }++          // A selector might throw after a source mutation.+          // e.g. it might try to read from a part of the store that no longer exists.+          // In this case we should still schedule an update with React.+          // Worst case the selector will throw again and then an error boundary will handle it.+          if (snapshotDidThrow) {+            return {...prevState};+          }++          // If the value hasn't changed, no update is needed.+          // Return state as-is so React can bail out and avoid an unnecessary render.+          if (prevState.snapshot === newSnapshot) {+            return prevState;+          }++          setState({+            ...prevState,+            snapshot: newSnapshot,+          });

Ah, this looks like maybe a rebase mistake of some sort...not sure what's going on here. Yes, it should just be a return. Thanks for pointing it out :)

bvaughn

comment created time in a day

Pull request review commentreactjs/rfcs

useMutableSource

+- Start Date: 2020-02-13+- RFC PR: [18000](https://github.com/facebook/react/pull/18000)+- React Issue: N/A++# useMutableSource++`useMutableSource()` enables React components to **safely** and **efficiently** read from a mutable external source in Concurrent Mode. The API will detect mutations that occur during a render to avoid tearing and it will automatically schedule updates when the source is mutated.++# Basic example++This hook is designed to support a variety of mutable sources. Below are a few example cases.++### Browser APIs++`useMutableSource()` can also read from non traditional sources, e.g. the shared Location object, so long as they can be subscribed to and have a "version".++```js+// May be created in module scope, like context:+const locationSource = createMutableSource(+  window,+  // Although not the typical "version", the href attribute is stable,+  // and will change whenever part of the Location changes,+  // so it's safe to use as a version.+  () => window.location.href+);++// Because this method doesn't require access to props,+// it can be declared in module scope to be shared between components.+const getSnapshot = window => window.location.pathname;++// This method can subscribe to root level change events,+// or more snapshot-specific events.+// In this case, since Example is only reading the "friends" value,

Thanks!

bvaughn

comment created time in a day

push eventbvaughn/rfcs

Brian Vaughn

commit sha 99721c1c20a0166abf7fc35d17896aa83f98397e

Fixed typos

view details

push time in a day

Pull request review commentreactjs/rfcs

useMutableSource

+- Start Date: 2020-02-13+- RFC PR: [18000](https://github.com/facebook/react/pull/18000)+- React Issue: N/A++# useMutableSource++`useMutableSource()` enables React components to **safely** and **efficiently** read from a mutable external source in Concurrent Mode. The API will detect mutations that occur during a render to avoid tearing and it will automatically schedule updates when the source is mutated.++# Basic example++This hook is designed to support a variety of mutable sources. Below are a few example cases.++### Browser APIs++`useMutableSource()` can also read from non traditional sources, e.g. the shared Location object, so long as they can be subscribed to and have a "version".++```js+// May be created in module scope, like context:+const locationSource = createMutableSource(+  window,+  // Although not the typical "version", the href attribute is stable,+  // and will change whenever part of the Location changes,+  // so it's safe to use as a version.+  () => window.location.href+);++// Because this method doesn't require access to props,+// it can be declared in module scope to be shared between components.+const getSnapshot = window => window.location.pathname;++// This method can subscribe to root level change events,+// or more snapshot-specific events.+// In this case, since Example is only reading the "friends" value,+// we only have to subscribe to a change in that value+// (e.g. a "friends" event)+//+// Because this method doesn't require access to props,+// it can be declared in module scope to be shared between components.+const subscribe = (window, callback) => {+  window.addEventListener("popstate", callback);+  return () => window.removeEventListener("popstate", callback);+};++function Example() {+  const pathName = useMutableSource(locationSource, getSnapshot, subscribe);++  // ...+}+```++### Selectors that use props++Sometimes a state value is derived using component `props`. In this case, `useCallback` should be used to keep the snapshot and subscribe functions stable.++```js+// May be created in module scope, like context:+const userDataSource = createMutableSource(userData, () => userData.version);++// This method can subscribe to root level change events,+// or more snapshot-specific events.+// In this case, since Example is only reading the "friends" value,+// we only have to subscribe to a change in that value+// (e.g. a "friends" event)+//+// Because this method doesn't require access to props,+// it can be declared in module scope to be shared between components.+const subscribe = (userData, callback) => {+  userData.addEventListener("friends", callback);+  return () => userData.removeEventListener("friends", callback);+};++function Example({ onlyShowFamily }) {+  // Because the snapshot depends on props, it has to be created inline.+  // useCallback() memoizes the function though,+  // which lets useMutableSource() know when it's safe to reuse a snapshot value.+  const getSnapshot = useCallback(+    userData =>+      userData.friends+        .filter(+          friend => !onlyShowFamily || friend.relationshipType === "family"+        )+        .friends.map(friend => friend.id),

Thanks!

bvaughn

comment created time in a day

pull request commentreactjs/rfcs

useMutableSource

@bvaughn Doesn’t using setState’s early bail out mechanism get you that automatically and if it doesn’t then it’s probably not safe in the first place? Probably just want to piggy back on that.

@sebmarkbage I'm not sure which particular message you're asking about 😅

useState does bail out if you return the exact same state object. (I'm deliberately making use of that with my current implementation.) In this case we couldn't return the exact same object though, because we still want to update the getSnapshot function that's stored in state.

it also doesn't matter in this case, since the particular state update we're talking about is one that would happen during render. So it's not a case where eager bailouts would apply. The real question in this case, is can we just re-render with the updated state and avoid de-opting.

I think the answer is yes, but it was a long day and I'd like to sleep on it and think about it a little more tomorrow.

bvaughn

comment created time in 2 days

pull request commentreactjs/rfcs

useMutableSource

Why not compare also the result of getsnapshot(source) and schedule an update only if snapshot is changed, while save only new getsnapshot on the state object?

Interesting. On the one hand, supporting inline selectors is not a goal.

On the other, avoiding unnecessary deopts is a goal so it warrants more consideration...

We'd need to update state either way (to store the updated getSnapshot value). We might be able to avoid throwing and deopting though, if the snapshot didn't change. I'll give it some thought.

bvaughn

comment created time in 2 days

push eventbvaughn/react

Kunuk Nykjær

commit sha abfbae02a4dcb56ef0040401e7822007a9d61d5b

Update Rollup version to 1.19.4 and fix breaking changes (#15037) * update rollup versioni * ignore Rollup warnings for known warning codes * add lecacy support from elas7 * rollup 1.5 * upd to ver 1.6.0 * don't throw error * use return instead of throw error * upd code in comment * fix getters test * rollup 1.7 * rollup 1.7.3 * remove comments * use rollup 1.7.4 * update yarn.lock for new rollup version * rollup version 1.9.0 * rollback to version 1.7.4 * add globalThis to eslintrc.umd * rollup 1.9.0 * upd rollup plugin versions to satisfied latest versions * add result.json update * rollup 1.9.3 * rollup 1.10.0 * ver 1.10.1 * rollup 1.11.3 * rollup ver 1.12.3 * rollup 1.13.1 * rollup 1.14.6 * rollup 1.15.6 * rollup 1.16.2 * upd tests * prettier * Rollup 1.16.3 * upd * should throw when finding getters with a different syntax from the ones generated by Rollup * add more one test * rollup-plugin-prettier updated changed stuff, revert them * don't upd all the Rollup plugins * rollup-plugin-babel 3.0.7 * upd rollup plugin versions * upd rollup-plugin-commonjs * bracket spacing * rollup 1.16.6 * rollup 1.16.7 * rename test description * rollup 1.18.0 * use externalLiveBindings: false * rollup 1.19.3 * remove remove-getters * simplify CIRCULAR_DEPENDENCY warning * simplify if logic in sizes-plugin * rollup 1.19.4 * update output for small optimizations * remove globalThis * remove results.json file * re-add globalThis

view details

Brian Vaughn

commit sha 27218de2f131024e5a41750916b22c9938f65627

useMutableSource hook useMutableSource() enables React components to safely and efficiently read from a mutable external source in Concurrent Mode. The API will detect mutations that occur during a render to avoid tearing and it will automatically schedule updates when the source is mutated. RFC: reactjs/rfcs#147

view details

Brian Vaughn

commit sha 6c9b1e9e333c23c9d8e2cd27f594daba74108237

Refactored useMutableSource to use update queue This commit changes useMutableSource to use an update queue by composing useEffect and useState internally. This ended up being a little bigger than I would have expected due to the need to create mount, update, and rerender implementations, but that's probably okay for now. This commit also makes a few orthogonal changes as the result of discussions with Relay, Redux, etc: 1. Snapshots are eagerly evaluated so components can bailout early even when 'scoped subscriptions' are not possible. 2. The subscribe callback function now expects to be passed the updated snapshot value.

view details

Brian Vaughn

commit sha 3548eaaa7f56e73a18f9c4e2a088b63e07c39de0

Changed subscribe callback signature to not require the latest snapshot value This is being done to reduce how frequently we potentially deopt during render. I also tidied up the useMutableSource mount/update/rerender implementations to reduce their overall file size.

view details

Brian Vaughn

commit sha 60b14c1fd054d1a342e063411d2aa87ae02a955f

Handle errors that occur during eager snapshot evaluation A selector might throw after a source mutation. e.g. it might try to read from a part of the store that no longer exists. In this case we should still schedule an update with React. Worst case the selector will throw again and then an error boundary will handle it.

view details

push time in 2 days

push eventbvaughn/react

Brian Vaughn

commit sha 40ac6a8898eca1730bad7c76f7617072b3f01daf

Handle errors that occur during eager snapshot evaluation A selector might throw after a source mutation. e.g. it might try to read from a part of the store that no longer exists. In this case we should still schedule an update with React. Worst case the selector will throw again and then an error boundary will handle it.

view details

push time in 2 days

pull request commentreactjs/rfcs

useMutableSource

Excellent :)

bvaughn

comment created time in 2 days

pull request commentreactjs/rfcs

useMutableSource

Looks good @dai-shi (at least from what I understand). You won't need to pass the snapshot value to the change handler after my most recent update. (See this comment for more context on why.) But otherwise 👍

bvaughn

comment created time in 2 days

pull request commentreactjs/rfcs

useMutableSource

@markerikson's comment:

The "zombie child" problem you described is interesting. I think the way that could play it out with useMutableSource is like so:

  1. A component mounts and reads from part of the store.
  2. Something updates the store to delete that part.
  3. The subscribed component's change handler is called.
  4. useMutableSource eagerly reads its new snapshot value to schedule an update.
  5. User code throws an error because of an unexpected undefined value.

useMutableSource will need to be resilient to this case such that a thrown error is treated the same as a new snapshot (aka it schedules an update with React). During the subsequent update- if the component is unmounted, that's fine.

bvaughn

comment created time in 2 days

push eventbvaughn/rfcs

Brian Vaughn

commit sha b5cec6a40e4f39340d31025ff3f07c33dd488aae

Updated RFC to reflect that change handler no longer needs to pass new snapshot value to callback

view details

push time in 2 days

pull request commentfacebook/react

useMutableSource hook

After some consideration, I reverted this part of my previous commit:

  1. The subscribe callback function now expects to be passed the updated snapshot value.

Anytime we read from a source that we have not yet subscribed to (either because we have a new source or a new subscribe function) we have to fall back to checking the version and pending updates for the source: https://github.com/facebook/react/blob/84637cc92002543798990d1a8f322bcf9e202f0e/packages/react-reconciler/src/ReactFiberHooks.js#L863-L915

This check has an ambiguous cases where a component's selector might be able to safely read from a source, but we can't know for sure- so we throw and de-opt the entire render (including all pending updates at all priorities for the current root).

Anything that sends us down this path more often increases the likelihood of this bigger de-opt.

bvaughn

comment created time in 2 days

push eventbvaughn/react

Brian Vaughn

commit sha f4ada5de3188d41a394ad45feb4150df55eb7a4a

Changed subscribe callback signature to not require the latest snapshot value This is being done to reduce how frequently we potentially deopt during render. I also tidied up the useMutableSource mount/update/rerender implementations to reduce their overall file size.

view details

push time in 2 days

pull request commentfacebook/react

useMutableSource hook

Note to self: I think I might revert this part of my most recent commit:

  1. The subscribe callback function now expects to be passed the updated snapshot value.

Anytime we read from a source that we have not yet subscribed to (either because we have a new source or a new subscribe function) we have to fall back to checking the version and pending updates for the source: https://github.com/facebook/react/blob/84637cc92002543798990d1a8f322bcf9e202f0e/packages/react-reconciler/src/ReactFiberHooks.js#L863-L915

This check has an ambiguous cases where a component's selector might be able to safely read from a source, but we can't know for sure- so we throw and de-opt the entire render (including all pending updates at all priorities for the current root).

Anything that sends us down this path more often increases the likelihood of this bigger de-opt.

bvaughn

comment created time in 2 days

pull request commentreactjs/rfcs

useMutableSource

Excellent. Thanks for the fast feedback.

bvaughn

comment created time in 2 days

push eventbvaughn/rfcs

Brian Vaughn

commit sha fd8b9887b7f64ec4fad842bde066f87a9684bf4c

Updated RFC to include eager snapshot evaluation and other recent changes in implementation

view details

push time in 2 days

pull request commentreactjs/rfcs

useMutableSource

Just pushed an update to the RFC text to include some recent changes to the implementation (https://github.com/facebook/react/pull/18000/commits/84637cc92002543798990d1a8f322bcf9e202f0e) that incorporates early feedback from library authors.

cc @markerikson @dai-shi @salvoravida for my current thinking wrt Redux: https://github.com/bvaughn/rfcs/blob/useMutableSource/text/0000-use-mutable-source.md#redux-stores

bvaughn

comment created time in 2 days

pull request commentreactjs/rfcs

useMutableSource

Just pushed an update to the RFC text to include some recent changes to the implementation (https://github.com/facebook/react/pull/18000/commits/84637cc92002543798990d1a8f322bcf9e202f0e) that incorporates early feedback from library authors.

bvaughn

comment created time in 2 days

push eventbvaughn/rfcs

Brian Vaughn

commit sha fee7e4bcd609c1a2a8f0c4e3d950dccad86c4e71

Updated RFC to include eager snapshot evaluation and other recent changes in implementation

view details

push time in 2 days

push eventbvaughn/react

Brian Vaughn

commit sha 84637cc92002543798990d1a8f322bcf9e202f0e

Refactored useMutableSource to use update queue This commit changes useMutableSource to use an update queue by composing useEffect and useState internally. This ended up being a little bigger than I would have expected due to the need to create mount, update, and rerender implementations, but that's probably okay for now. This commit also makes a few orthogonal changes as the result of discussions with Relay, Redux, etc: 1. Snapshots are eagerly evaluated so components can bailout early even when 'scoped subscriptions' are not possible. 2. The subscribe callback function now expects to be passed the updated snapshot value. I feel confident that the first change is the right decision. I am less confident about the second one though. It may warrant more thought.

view details

push time in 2 days

push eventbvaughn/react

Dominic Gannaway

commit sha 3f85d53ca6f2af8a711daae6322e6bdda862f660

Further pre-requisite changes to plugin event system (#18083)

view details

Sunil Pai

commit sha b789060dca314f052d856cab509569cf41020cd5

Feature Flag for React.jsx` "spreading a key to jsx" warning (#18074) Adds a feature flag for when React.jsx warns you about spreading a key into jsx. It's false for all builds, except as a dynamic flag for fb/www. I also included the component name in the warning.

view details

Brian Vaughn

commit sha c303bf6a5face30da11698bdf953953e3d75acb3

useMutableSource hook useMutableSource() enables React components to safely and efficiently read from a mutable external source in Concurrent Mode. The API will detect mutations that occur during a render to avoid tearing and it will automatically schedule updates when the source is mutated. RFC: reactjs/rfcs#147

view details

push time in 2 days

pull request commentreactjs/rfcs

useMutableSource

@markerikson's comment: https://github.com/facebook/react/pull/18000

For React-Redux specifically, we've found that we have to subscribe in a useLayoutEffect(), otherwise it leads to bugs (see https://github.com/reduxjs/react-redux/pull/1444 and https://github.com/reduxjs/react-redux/issues/1313 ). It's possible that useMutableSource's semantics are different enough that that might not be an issue for us using it in the future, but FYI.

Subscribing in a layout effect would be required to repair a temporary tear that would otherwise be user visible.

The useSubscription hook had a similar problem, although I opted to use a passive effect still in that case to avoid another possible bug which I described via an inline comment.

Fortunately the design of useMutableSource prevents any possible temporary tearing, enabling us to defer to the passive effects phase which is better for a few reasons! I think I mentioned this in a recent Gist I wrote comparing the two hooks:

https://gist.github.com/bvaughn/054b82781bec875345bd85a5b1344698

bvaughn

comment created time in 2 days

pull request commentfacebook/react

useMutableSource hook

@markerikson Probably easiest if we chat on the RFC about design related concerns 😄 Just to keep them all in one place. I'll quote you and comment over there.

bvaughn

comment created time in 2 days

issue commentfacebook/react

Error: "getCommitTree(): Unable to reconstruct tree for root "1" and commit 152"

Of course 🙇 We're appreciate for anything you're able to do. No worries either way.

morfioce

comment created time in 2 days

pull request commentfacebook/react

useMutableSource hook

I've updated this diff and squashed it into two commits:

  • The first is my initial implementation, with a non-update-queue based approach.
  • The second commit uses the update queue by composing useEffect and useState internally. This second approach ended up being a little bigger than I would have expected due to the need to create mount, update, and rerender implementations.
bvaughn

comment created time in 2 days

issue commentfacebook/react

Error: "getCommitTree(): Unable to reconstruct tree for root "1" and commit 152"

Sweet. Thanks for the update! 😄 Any interest in digging into the underlying DevTools code to see if you can find a fix?

morfioce

comment created time in 2 days

Pull request review commentfacebook/react

fix(react-devtools-shared): useDebugValue with complex types

+describe('HooksTree', () => {+  let React;+  let ReactDOM;+  let bridge: FrontendBridge;+  let store: Store;+  let utils;+  let HooksTree;++  let BridgeContext;+  let InspectedElementContextController;+  let StoreContext;+  let TreeContextController;++  beforeEach(() => {+    utils = require('./utils');+    utils.beforeEachProfiling();++    bridge = global.bridge;+    store = global.store;+    store.collapseNodesByDefault = false;++    React = require('react');+    ReactDOM = require('react-dom');++    BridgeContext = require('react-devtools-shared/src/devtools/views/context')+      .BridgeContext;+    InspectedElementContextController = require('react-devtools-shared/src/devtools/views/Components/InspectedElementContext')+      .InspectedElementContextController;+    StoreContext = require('react-devtools-shared/src/devtools/views/context')+      .StoreContext;+    TreeContextController = require('react-devtools-shared/src/devtools/views/Components/TreeContext')+      .TreeContextController;++    HooksTree = require('react-devtools-shared/src/devtools/views/Components/HooksTree')+      .HooksTreeView;+  });++  const Contexts = ({+    children,+    defaultSelectedElementID = null,+    defaultSelectedElementIndex = null,+  }) => (+    <BridgeContext.Provider value={bridge}>+      <StoreContext.Provider value={store}>+        <TreeContextController+          defaultSelectedElementID={defaultSelectedElementID}+          defaultSelectedElementIndex={defaultSelectedElementIndex}>+          <InspectedElementContextController>+            {children}+          </InspectedElementContextController>+        </TreeContextController>+      </StoreContext.Provider>+    </BridgeContext.Provider>+  );++  it('shows the complex value of custom hooks with sub-hooks', () => {+    // props from `InspectedElementContext display complex values of useDebugValue: DisplayedComplexValue 1` snapshot++    const container = document.createElement('div');+    ReactDOM.render(+      <Contexts>+        <HooksTree+          hooks={[+            {+              id: null,+              isStateEditable: false,+              name: 'DebuggableHook',+              value: {+                foo: 2,+              },+              subHooks: [+                {+                  id: 0,+                  isStateEditable: true,+                  name: 'State',+                  value: 1,+                  subHooks: [],+                },+              ],+            },+          ]}+        />+      </Contexts>,+      container,+    );++    const hook = container.querySelector('.NameValueRow');+    // it's actually DebuggableHook: {foo:2} but the first colon is added by css+    // which isn't available in the test+    expect(hook.textContent).toEqual('DebuggableHook{foo: 2}');

Hard-coding a JSON object in a test is brittle. That object should be created by the backend. e.g. What happens if the backend structure changes in a way that would break things, but your test is written with the hard-coded JSON object?

I don't have time to dig deep on this right now unfortunately, so my feedback so far has been pretty shallow. In a few days I might have more time, but I need to wrap up some RFCs first.

If you disagree with my PR feedback so far, then let's just set it down for a while until I have time to really dig in.

eps1lon

comment created time in 2 days

Pull request review commentfacebook/react

fix(react-devtools-shared): useDebugValue with complex types

+describe('HooksTree', () => {+  let React;+  let ReactDOM;+  let bridge: FrontendBridge;+  let store: Store;+  let utils;+  let HooksTree;++  let BridgeContext;+  let InspectedElementContextController;+  let StoreContext;+  let TreeContextController;++  beforeEach(() => {+    utils = require('./utils');+    utils.beforeEachProfiling();++    bridge = global.bridge;+    store = global.store;+    store.collapseNodesByDefault = false;++    React = require('react');+    ReactDOM = require('react-dom');++    BridgeContext = require('react-devtools-shared/src/devtools/views/context')+      .BridgeContext;+    InspectedElementContextController = require('react-devtools-shared/src/devtools/views/Components/InspectedElementContext')+      .InspectedElementContextController;+    StoreContext = require('react-devtools-shared/src/devtools/views/context')+      .StoreContext;+    TreeContextController = require('react-devtools-shared/src/devtools/views/Components/TreeContext')+      .TreeContextController;++    HooksTree = require('react-devtools-shared/src/devtools/views/Components/HooksTree')+      .HooksTreeView;+  });++  const Contexts = ({+    children,+    defaultSelectedElementID = null,+    defaultSelectedElementIndex = null,+  }) => (+    <BridgeContext.Provider value={bridge}>+      <StoreContext.Provider value={store}>+        <TreeContextController+          defaultSelectedElementID={defaultSelectedElementID}+          defaultSelectedElementIndex={defaultSelectedElementIndex}>+          <InspectedElementContextController>+            {children}+          </InspectedElementContextController>+        </TreeContextController>+      </StoreContext.Provider>+    </BridgeContext.Provider>+  );++  it('shows the complex value of custom hooks with sub-hooks', () => {+    // props from `InspectedElementContext display complex values of useDebugValue: DisplayedComplexValue 1` snapshot++    const container = document.createElement('div');+    ReactDOM.render(+      <Contexts>+        <HooksTree+          hooks={[

I don't think there's a lot of value in testing the view components. DevTools tests the underlying business logic on the backend (and frontend via the contexts).

Let's remove this test :)

eps1lon

comment created time in 2 days

push eventreactjs/reactjs.org

Patrick Smith

commit sha 0741c81a14848dbbd578dcb966bdde7d03b93612

"async" mode to "concurrent" mode (#2761) Should hooks such as `useLayoutEffect`, `useEffect`, etc also be listed in the "Render phase lifecycles" section?

view details

push time in 2 days

PR merged reactjs/reactjs.org

Docs: "async" mode to "concurrent" mode CLA Signed

Changes mention of "async" mode to "concurrent" mode on strict-mode page.

Should hooks such as useLayoutEffect, useEffect, etc also be listed in the "Render phase lifecycles" section?

+2 -2

4 comments

1 changed file

BurntCaramel

pr closed time in 2 days

pull request commentreactjs/reactjs.org

Docs: "async" mode to "concurrent" mode

Should hooks such as useLayoutEffect, useEffect, etc also be listed in the "Render phase lifecycles" section?

No. Those are commit phase hooks. The strict mode docs only mention class lifecycle methods though.

BurntCaramel

comment created time in 2 days

pull request commentreactjs/rfcs

useMutableSource

@cartant comment

The idea you show looks promising, but the version would be over-incremented which would be problematic. This is the reason I showed a centralized proxy approach in the RFC.

I'm not sure whether or not the API ensures that subscribe is called only once per created store or is called for each use.

A subscription will be setup for each use of the hook. (It's not per-source, but per component/hook.)

bvaughn

comment created time in 2 days

push eventbvaughn/react

Brian Vaughn

commit sha 7de01f175656e8078c93a009d7ed7be4d6f74987

Refactored useMutableSource to use update queue For now, the hook just uses useEffect and useState under the hood. This probably makes it a little less effcient at runtime but perhaps a little smaller in code size. This change ended up being a little larger than I would have assumed. Maybe that indicates there is room for improvement in my initial implementation.

view details

push time in 3 days

issue commentbvaughn/react-window

Question about column span

Is there a way to do that at the moment ? If not, is it in your roadmap for react-window ?

No and no.

Does anyone have an idea of how I could implement that ?

I'd suggest asking on Stack Overflow :)

You might also read through #60

adrienpwd

comment created time in 3 days

Pull request review commentfacebook/react

fix(react-devtools-shared): useDebugValue with complex types

+describe('HooksTree', () => {+  let React;+  let ReactDOM;+  let bridge: FrontendBridge;+  let store: Store;+  let utils;+  let HooksTree;++  let BridgeContext;+  let InspectedElementContextController;+  let StoreContext;+  let TreeContextController;++  beforeEach(() => {+    utils = require('./utils');+    utils.beforeEachProfiling();++    bridge = global.bridge;+    store = global.store;+    store.collapseNodesByDefault = false;++    React = require('react');+    ReactDOM = require('react-dom');++    BridgeContext = require('react-devtools-shared/src/devtools/views/context')+      .BridgeContext;+    InspectedElementContextController = require('react-devtools-shared/src/devtools/views/Components/InspectedElementContext')+      .InspectedElementContextController;+    StoreContext = require('react-devtools-shared/src/devtools/views/context')+      .StoreContext;+    TreeContextController = require('react-devtools-shared/src/devtools/views/Components/TreeContext')+      .TreeContextController;++    HooksTree = require('react-devtools-shared/src/devtools/views/Components/HooksTree')+      .HooksTreeView;+  });++  const Contexts = ({+    children,+    defaultSelectedElementID = null,+    defaultSelectedElementIndex = null,+  }) => (+    <BridgeContext.Provider value={bridge}>+      <StoreContext.Provider value={store}>+        <TreeContextController+          defaultSelectedElementID={defaultSelectedElementID}+          defaultSelectedElementIndex={defaultSelectedElementIndex}>+          <InspectedElementContextController>+            {children}+          </InspectedElementContextController>+        </TreeContextController>+      </StoreContext.Provider>+    </BridgeContext.Provider>+  );++  it('shows the complex value of custom hooks with sub-hooks', () => {+    // props from `InspectedElementContext display complex values of useDebugValue: DisplayedComplexValue 1` snapshot++    const container = document.createElement('div');+    ReactDOM.render(+      <Contexts>+        <HooksTree+          hooks={[+            {+              id: null,+              isStateEditable: false,+              name: 'DebuggableHook',+              value: {+                foo: 2,+              },+              subHooks: [+                {+                  id: 0,+                  isStateEditable: true,+                  name: 'State',+                  value: 1,+                  subHooks: [],+                },+              ],+            },+          ]}+        />+      </Contexts>,+      container,+    );++    const hook = container.querySelector('.NameValueRow');+    // it's actually DebuggableHook: {foo:2} but the first colon is added by css+    // which isn't available in the test+    expect(hook.textContent).toEqual('DebuggableHook{foo: 2}');

This test seems kind of brittle.

I think we can probably just skip HooksTree-test.js :smile:

eps1lon

comment created time in 3 days

Pull request review commentfacebook/react

fix(react-devtools-shared): useDebugValue with complex types

+describe('HooksTree', () => {+  let React;+  let ReactDOM;+  let bridge: FrontendBridge;+  let store: Store;+  let utils;+  let HooksTree;++  let BridgeContext;+  let InspectedElementContextController;+  let StoreContext;+  let TreeContextController;++  beforeEach(() => {+    utils = require('./utils');+    utils.beforeEachProfiling();++    bridge = global.bridge;+    store = global.store;+    store.collapseNodesByDefault = false;++    React = require('react');+    ReactDOM = require('react-dom');++    BridgeContext = require('react-devtools-shared/src/devtools/views/context')+      .BridgeContext;+    InspectedElementContextController = require('react-devtools-shared/src/devtools/views/Components/InspectedElementContext')+      .InspectedElementContextController;+    StoreContext = require('react-devtools-shared/src/devtools/views/context')+      .StoreContext;+    TreeContextController = require('react-devtools-shared/src/devtools/views/Components/TreeContext')+      .TreeContextController;++    HooksTree = require('react-devtools-shared/src/devtools/views/Components/HooksTree')+      .HooksTreeView;+  });++  const Contexts = ({+    children,+    defaultSelectedElementID = null,+    defaultSelectedElementIndex = null,+  }) => (+    <BridgeContext.Provider value={bridge}>+      <StoreContext.Provider value={store}>+        <TreeContextController+          defaultSelectedElementID={defaultSelectedElementID}+          defaultSelectedElementIndex={defaultSelectedElementIndex}>+          <InspectedElementContextController>+            {children}+          </InspectedElementContextController>+        </TreeContextController>+      </StoreContext.Provider>+    </BridgeContext.Provider>+  );++  it('shows the complex value of custom hooks with sub-hooks', () => {+    // props from `InspectedElementContext display complex values of useDebugValue: DisplayedComplexValue 1` snapshot++    const container = document.createElement('div');+    ReactDOM.render(+      <Contexts>+        <HooksTree+          hooks={[

What is this test meant to be testing? I'm not sure I see the value it adds.

Hard-coding the hooks metadata doesn't seem like it's a very comprehensive test. I think the existing pattern of testing via the contexts is a better end-to-end coverage. Less brittle. :)

eps1lon

comment created time in 3 days

Pull request review commentfacebook/react

fix(react-devtools-shared): useDebugValue with complex types

 packages.forEach(name => {   ] = `<rootDir>/build/node_modules/${name}/$1`; }); +// https://jestjs.io/docs/en/webpack#mocking-css-modules+moduleNameMapper['\\.(css|less)$'] = 'identity-obj-proxy';

Ah, I see. I don't think we should import a DevTools view in a test like that. I'll comment above.

eps1lon

comment created time in 3 days

push eventbvaughn/react

Will Douglas

commit sha 93a229bab59f94e214256582c55d3b6c1fc2b958

Update eslint rule exhaustive deps to use new suggestions feature (#17385) This closes #16313

view details

Dan Abramov

commit sha 56a8c353219ac93ab358eb28009de881ae48251e

eslint-plugin-react-hooks@2.4.0

view details

Dan Abramov

commit sha d533229fba8f7e7e576436bf52bbcae56c862906

Fix Prettier

view details

Dominic Gannaway

commit sha f48a5e64e8fd903293f5b854beb795dcc6bae86d

Further cleanup of plugin event system (#18056)

view details

Haseeb Furkhan Mohammed

commit sha d5ddc16a3398c33d70489cee87f5176c85f3c9f5

React developer tools extension for Microsoft Edge (#18041) * Port Chrome extension to Microsoft Edge

view details

Brian Vaughn

commit sha 90be006da8e231279151e7b1518bb64c62e26851

Updated Yarn lockfile

view details

Ryota Murakami

commit sha 48c4867d745bbf91ae73892545960c0979c2dbf7

Update issue templates to directly link to relevant sources (#18039) GitHub supports linking to off-site sources for certain types of issue.

view details

Dominic Gannaway

commit sha 1a6d8179b6dd427fdf7ee50d5ac45ae5a40979eb

[react-interactions] Ensure onBeforeBlur fires for hideInstance (#18064)

view details

Andrew Clark

commit sha 56d8a73affad624ee4d48f1685e0a92adce0bd9c

[www] Disable Scheduler `timeout` w/ dynamic flag (#18069) Before attempting to land an expiration times refactor, I want to see if this particular change will impact performance (either positively or negatively). I will test this with a GK.

view details

Brian Vaughn

commit sha 691096c95d1019f57e0da2c9a060c5e094b7c586

Split recent passive effects changes into 2 flags (#18030) * Split recent passive effects changes into 2 flags Separate flags can now be used to opt passive effects into: 1) Deferring destroy functions on unmount to subsequent passive effects flush 2) Running all destroy functions (for all fibers) before create functions This allows us to test the less risky feature (2) separately from the more risky one. * deferPassiveEffectCleanupDuringUnmount is ignored unless runAllPassiveEffectDestroysBeforeCreates is true

view details

Brian Vaughn

commit sha 14afeb1033e25942e63787750388972916f20a39

Added missing feature flag

view details

Andrew Clark

commit sha 4d9f8500651c5d1e19d8ec9a2359d5476a53814b

Re-throw errors thrown by the renderer at the root in the complete phase (#18029) * Re-throw errors thrown by the renderer at the root React treats errors thrown at the root as a fatal because there's no parent component that can capture it. (This is distinct from an "uncaught error" that isn't wrapped in an error boundary, because in that case we can fall back to deleting the whole tree -- not great, but at least the error is contained to a single root, and React is left in a consistent state.) It turns out we didn't have a test case for this path. The only way it can happen is if the renderer's host config throws. We had similar test cases for host components, but none for the host root. This adds a new test case and fixes a bug where React would keep retrying the root because the `workInProgress` pointer was not advanced to the next fiber. (Which in this case is `null`, since it's the root.) We could consider in the future trying to gracefully exit from certain types of root errors without leaving React in an inconsistent state. For example, we should be able to gracefully exit from errors thrown in the begin phase. For now, I'm treating it like an internal invariant and immediately exiting. * Add comment

view details

Dominic Gannaway

commit sha 4912ba31e3dcc8d08f5b16ae38b38d74da85ea21

Add modern event system flag + rename legacy plugin module (#18073)

view details

Sunil Pai

commit sha a8643e905e39f041cda80b498dc06018b27f6554

add no-restricted-globals to eslint config (#18076) Our current lint config assumes a browser environment, which means it won't warn you if you use a variable like `name` without declaring it earlier. This imports the same list as the one used by create-react-app, and enables it against our codebase.

view details

Dominic Gannaway

commit sha 2512c309e34f0207d29c19392d144edab719f347

Remove Flare bundles from build (#18077)

view details

Moji Izadmehr

commit sha 44e5f5e6451cf192af5dee3aa1f3a87119fc231e

Add fiber summary tooltip to devtools profiling (#18048) * Add tooltip component * Separate logic of ProfilerWhatChanged to a component * Add hovered Fiber info tooltip component * Add flame graph chart tooltip * Add commit ranked list tooltip * Fix flow issues * Minor improvement in filter * Fix flickering issue * Resolved issues on useCallbacks and mouse event listeners * Fix lints * Remove unnecessary useCallback

view details

Dan Abramov

commit sha a12dd52a4a5f19d990694810db86cc30e98308ae

Don't build some packages for WWW (#18078)

view details

Dominic Gannaway

commit sha 1000f6135efba4f8d8ebffedeb7b472f532a8475

Add container to event listener signature (#18075)

view details

Dominic Gannaway

commit sha b6c94d636cb33a265671b864b97870da38d97207

Add guard around FocusWithin responder root events (#18080)

view details

Brian Vaughn

commit sha 7e770dae93e1a934b905d8678c7ce368ed86ef0b

Profiler tooltip tweaks (#18082) * Moved Profiler views into Profiler folder * Tweaked Profiler tooltip CSS styles * Tweaked Tooltip positioning code

view details

push time in 3 days

push eventfacebook/react

Brian Vaughn

commit sha 7e770dae93e1a934b905d8678c7ce368ed86ef0b

Profiler tooltip tweaks (#18082) * Moved Profiler views into Profiler folder * Tweaked Profiler tooltip CSS styles * Tweaked Tooltip positioning code

view details

push time in 3 days

delete branch bvaughn/react

delete branch : profiler-tooltip-tweaks

delete time in 3 days

PR merged facebook/react

Reviewers
Profiler tooltip tweaks CLA Signed React Core Team

Minor follow ups to #18048

+35 -44

3 comments

9 changed files

bvaughn

pr closed time in 3 days

Pull request review commentfacebook/react

[DevTools] Added resize support for Components panel.

 import portaledContent from '../portaledContent'; import {ModalDialog} from '../ModalDialog'; import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal'; import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';+import {useLocalStorage} from '../hooks';  import styles from './Components.css';  function Components(_: {||}) {-  // TODO Flex wrappers below should be user resizable.   return (     <SettingsModalContextController>       <OwnersListContextController>         <InspectedElementContextController>-          <div className={styles.Components}>-            <div className={styles.TreeWrapper}>-              <Tree />-            </div>-            <div className={styles.SelectedElementWrapper}>-              <NativeStyleContextController>-                <Suspense fallback={<Loading />}>-                  <SelectedElement />-                </Suspense>-              </NativeStyleContextController>-            </div>-            <ModalDialog />-            <SettingsModal />-          </div>+          <ComponentResizer>+            {({resizeElementRef, onResizeStart, resizeElementStyles}) => (+              <Fragment>+                <div+                  ref={resizeElementRef}+                  className={styles.TreeWrapper}+                  style={{+                    ...resizeElementStyles,+                  }}>+                  <Tree />+                </div>+                <div className={styles.ResizeBarWrapper}>+                  <div+                    onMouseDown={onResizeStart}+                    className={styles.ResizeBar}+                  />+                </div>+                <div className={styles.SelectedElementWrapper}>+                  <NativeStyleContextController>+                    <Suspense fallback={<Loading />}>+                      <SelectedElement />+                    </Suspense>+                  </NativeStyleContextController>+                </div>+                <ModalDialog />+                <SettingsModal />+              </Fragment>+            )}+          </ComponentResizer>         </InspectedElementContextController>       </OwnersListContextController>     </SettingsModalContextController>   ); } +type HorizontalResizeDirection = 'HORIZONTAL';+type VerticalResizeDirection = 'VERTICAL';++const RESIZE_DIRECTIONS: {|+  HORIZONTAL: HorizontalResizeDirection,+  VERTICAL: VerticalResizeDirection,+|} = {+  HORIZONTAL: 'HORIZONTAL',+  VERTICAL: 'VERTICAL',+};++const LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_HORIZONTAL_KEY = `React::DevTools::resizedElementPercentage::${RESIZE_DIRECTIONS.HORIZONTAL}`;+const LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_VERTICAL_KEY = `React::DevTools::resizedElementPercentage::${RESIZE_DIRECTIONS.VERTICAL}`;++function ComponentResizer({children}): {|children: Function|} {+  const [isResizing, setIsResizing] = useState<boolean>(false);+  const [+    horizontalPercentage,+    setHorizontalPercentage,+  ] = useLocalStorage<number>(+    LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_HORIZONTAL_KEY,+    65,+  );+  const [verticalPercentage, setVerticalPercentage] = useLocalStorage<number>(+    LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_VERTICAL_KEY,+    50,+  );+  const updateLocalStorageTimeoutId = useRef<number>(null);+  const componentsWrapperRef = useRef<HTMLDivElement>(null);+  const resizeElementRef = useRef<HTMLElement>(null);+  const [resizeElementStyles, setResizeElementStyles] = useState<Object>({});+  const getResizeDirection: Function = useCallback(() => {+    if (componentsWrapperRef.current === null) {+      return RESIZE_DIRECTIONS.HORIZONTAL;+    }+    const VERTICAL_MODE_MAX_WIDTH: number = 600;+    const {width} = componentsWrapperRef.current.getBoundingClientRect();++    return width > VERTICAL_MODE_MAX_WIDTH+      ? RESIZE_DIRECTIONS.HORIZONTAL+      : RESIZE_DIRECTIONS.VERTICAL;+  }, [componentsWrapperRef]);++  const onResizeStart = useCallback(() => {+    setIsResizing(true);+  }, [setIsResizing]);++  const onResizeEnd = useCallback(() => {+    setIsResizing(false);+  }, [setIsResizing]);++  const onResize = useCallback(+    e => {+      if (+        !isResizing ||+        componentsWrapperRef.current === null ||+        resizeElementRef.current === null+      ) {+        return;+      }++      e.preventDefault();++      const {+        height,+        width,+        left,+        top,+      } = componentsWrapperRef.current.getBoundingClientRect();+      const resizeDirection = getResizeDirection();+      const currentMousePosition: number =+        resizeDirection === RESIZE_DIRECTIONS.HORIZONTAL+          ? e.clientX - left+          : e.clientY - top;+      const BOUNDARY_PADDING: number = 40;+      const boundary: {|+        min: number,+        max: number,+      |} = {+        min: BOUNDARY_PADDING,+        max:+          resizeDirection === RESIZE_DIRECTIONS.HORIZONTAL+            ? width - BOUNDARY_PADDING+            : height - BOUNDARY_PADDING,+      };+      const isMousePositionInBounds: boolean =+        currentMousePosition > boundary.min &&+        currentMousePosition < boundary.max;++      if (isMousePositionInBounds) {+        const resizedElementDimension: number =+          resizeDirection === RESIZE_DIRECTIONS.HORIZONTAL ? width : height;+        const updatedFlexBasisValue: number =+          (currentMousePosition / resizedElementDimension) * 100;++        resizeElementRef.current.style.flexBasis = `${updatedFlexBasisValue}%`;++        clearTimeout(updateLocalStorageTimeoutId.current);++        updateLocalStorageTimeoutId.current = setTimeout(() => {+          if (resizeDirection === RESIZE_DIRECTIONS.HORIZONTAL) {+            setHorizontalPercentage(updatedFlexBasisValue);+          } else {+            setVerticalPercentage(updatedFlexBasisValue);+          }+        }, 500);+      }+    },+    [componentsWrapperRef, resizeElementRef, isResizing],+  );++  useLayoutEffect(() => {+    if (componentsWrapperRef.current !== null) {+      if (getResizeDirection() === RESIZE_DIRECTIONS.HORIZONTAL) {+        setResizeElementStyles({

Sounds fine @hristo-kanchev. Please just ping me again when it's ready for another look!

hristo-kanchev

comment created time in 3 days

pull request commentfacebook/react

Add Component Highlighting to Profiler DevTools

Sounds good @M-Izadmehr. The other PR has been merged!

M-Izadmehr

comment created time in 3 days

PR opened facebook/react

Reviewers
Profiler tooltip tweaks

Minor follow ups to #18048

+35 -44

0 comment

9 changed files

pr created time in 3 days

create barnchbvaughn/react

branch : profiler-tooltip-tweaks

created branch time in 3 days

push eventfacebook/react

Moji Izadmehr

commit sha 44e5f5e6451cf192af5dee3aa1f3a87119fc231e

Add fiber summary tooltip to devtools profiling (#18048) * Add tooltip component * Separate logic of ProfilerWhatChanged to a component * Add hovered Fiber info tooltip component * Add flame graph chart tooltip * Add commit ranked list tooltip * Fix flow issues * Minor improvement in filter * Fix flickering issue * Resolved issues on useCallbacks and mouse event listeners * Fix lints * Remove unnecessary useCallback

view details

push time in 3 days

PR merged facebook/react

Add fiber summary tooltip to devtools profiling CLA Signed

Summary

The purpose of this MR is to address #18014 (when working on a large project/component tree it can be difficult to understand why a large subtree is re-rendering, as a result adding a tooltip fo devtools profiler, which makes the process of debugging and inspecting elements easier.)

As a result, in really small components we don't need to click on them to see the details in sidebar, now it is possible to see the summary in the tooltip.

Results

ezgif com-video-to-gif (1) ezgif com-video-to-gif

+527 -176

9 comments

13 changed files

M-Izadmehr

pr closed time in 3 days

Pull request review commentfacebook/react

Add fiber summary tooltip to devtools profiling

+/** @flow */

Tiny nit, and I'm happy to do this post-merge- BUT! The new Tooltip and WhatChanged components are only used by views in the "Profiler" tab, so they should be in views/Profiler rather than views/Components (which is where views for the "Components" tab live). I would be happy to sort this out with a git mv after the PR merge though!

M-Izadmehr

comment created time in 4 days

Pull request review commentfacebook/react

fix(react-devtools-shared): useDebugValue with complex types

 packages.forEach(name => {   ] = `<rootDir>/build/node_modules/${name}/$1`; }); +// https://jestjs.io/docs/en/webpack#mocking-css-modules+moduleNameMapper['\\.(css|less)$'] = 'identity-obj-proxy';

Was this causing a problem?

eps1lon

comment created time in 3 days

pull request commentfacebook/react

fix(react-devtools-shared): useDebugValue with complex types

Is there a question (for me) on this PR at the moment or...? I'm not sure.

It looks like the test is already working as expected. I'm asking how I can test the actual react components that render this as it seems like the patch in the OP is the actual fix.

You can use the shell? You can add any hooks you want into the example app code the shell uses to verify the behavior in a real browser.

eps1lon

comment created time in 3 days

pull request commentfacebook/react

Add fiber summary tooltip to devtools profiling

Ooh, thanks for the quick turnaround! Let me take another look!

M-Izadmehr

comment created time in 4 days

pull request commentreactjs/rfcs

useMutableSource

I need to ignore this RFC for a couple of hours until I'm able to update the implementation PR (and RFC text) to account for the eager selector evaluation change I'm planning.

bvaughn

comment created time in 4 days

pull request commentreactjs/rfcs

useMutableSource

I feel like we're talking right past each other. :smile:

My previous comment said that if React did an eager snapshot comparison (and only scheduled an update only if the values changed- essentially what you're saying react-redux currently does) then Redux could just use a static subscribe function (to tip off React that something might have changed).

bvaughn

comment created time in 4 days

CommitCommentEvent

pull request commentreactjs/rfcs

useMutableSource

React-redux's useSelector already does this.

I know. This has been mentioned above.

@dai-shi's most recent comment was ambiguous though so I asked for clarification.

@dai-shi mentioned that resubscribing to the store any time the selector changes could be a problem. This can be solved using a selector ref but mutating refs during the render phase causes other issues.

I don't believe you would need to re-subscribe if React handle the snapshot comparison. I believe you could even use a static/global subscribe function in that case, no?

const subscribe = (store, callback) => store.subscribe(callback);
bvaughn

comment created time in 4 days

pull request commentfacebook/react

test(react-devtools-shared): useDebugValue with complex types

CI is failing on lint btw. Run yarn prettier-all.

Not sure what the question is atm on this PR?

eps1lon

comment created time in 4 days

pull request commentreactjs/rfcs

useMutableSource

Either way, react-redux can't depend on the bail-out behavior without scoped subscriptions, because it's likely to be a performance issue.

Can you elaborate on this @dai-shi?

Sounds like you're saying that even comparing previous and current snapshots would be a performance bottleneck. Maybe I'm misunderstanding?

bvaughn

comment created time in 4 days

pull request commentfacebook/react

Add fiber summary tooltip to devtools profiling

Thank YOU for the great work so far :) Much appreciated

M-Izadmehr

comment created time in 4 days

push eventfacebook/react

Brian Vaughn

commit sha 14afeb1033e25942e63787750388972916f20a39

Added missing feature flag

view details

push time in 4 days

push eventfacebook/react

Brian Vaughn

commit sha 691096c95d1019f57e0da2c9a060c5e094b7c586

Split recent passive effects changes into 2 flags (#18030) * Split recent passive effects changes into 2 flags Separate flags can now be used to opt passive effects into: 1) Deferring destroy functions on unmount to subsequent passive effects flush 2) Running all destroy functions (for all fibers) before create functions This allows us to test the less risky feature (2) separately from the more risky one. * deferPassiveEffectCleanupDuringUnmount is ignored unless runAllPassiveEffectDestroysBeforeCreates is true

view details

push time in 4 days

delete branch bvaughn/react

delete branch : split-passive-effect-flags

delete time in 4 days

PR merged facebook/react

Reviewers
Split recent passive effects changes into 2 flags CLA Signed React Core Team

Separate flags can now be used to opt passive effects into:

  1. deferPassiveEffectCleanupDuringUnmount: Defer passive effect destroy functions on unmount to subsequent passive effects flush. (#17925)
  2. runAllPassiveEffectDestroysBeforeCreates: Running all passive effect destroy functions (for all fibers) before create functions. (#17947)

This allows us to test the less risky feature (#17947) separately from the more risky one.

Note that:

  • deferPassiveEffectCleanupDuringUnmount is ignored unless runAllPassiveEffectDestroysBeforeCreates is also enabled.
  • Both flags remain off for the open source release.

I've updated a couple of tests that were impacted by the change to explicitly test with both feature flag values so we don't have blind spots.

Be sure to use the ?w=1 param to see the significant lines changed (+393 −99 rather than +5,471 −5,177).

+5400 -5101

3 comments

12 changed files

bvaughn

pr closed time in 4 days

Pull request review commentfacebook/react

useMutableSource hook

 function prepareFreshStack(root, expirationTime) {   workInProgressRootNextUnprocessedUpdateTime = NoWork;   workInProgressRootHasPendingPing = false; +  resetMutableSourceWorkInProgressVersions(isPrimaryRenderer);

I think this suggested change (adb5a2f) causes problems for high-pri interruptions, since moving this would mean that the WIP version doesn't get reset between a low priority render and a flush sync.

bvaughn

comment created time in 4 days

issue commentfacebook/react

Bug(devtools): complex types ignored sometimes in useDebugValue

I'm not sure where the confusion is.

I'm just saying that the "no hooks at all" component is not a use case I care about us supporting, because that's a "custom hook" that literally only uses the debug value hook and nothing else. That use case is not important to support. (If it works, fine. If it doesn't work, fine.)

eps1lon

comment created time in 4 days

issue commentfacebook/react

Bug(devtools): complex types ignored sometimes in useDebugValue

Yes. There's no need to use such a hook at all, since hooks calls can't be conditional to begin with (without leading to errors).

Just to be clear: There's not conditional call there. It's just not using an built-in hooks other than useDebugValue

I understand. My point though is that- since hooks calls can't be conditional, this could only ever be an "empty" hook- which serves no purpose and so I don't see it as a use case we should ever need to support.

eps1lon

comment created time in 4 days

Pull request review commentfacebook/react

[DevTools] Added resize support for Components panel.

 import portaledContent from '../portaledContent'; import {ModalDialog} from '../ModalDialog'; import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal'; import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';+import {useLocalStorage} from '../hooks';  import styles from './Components.css';  function Components(_: {||}) {-  // TODO Flex wrappers below should be user resizable.   return (     <SettingsModalContextController>       <OwnersListContextController>         <InspectedElementContextController>-          <div className={styles.Components}>-            <div className={styles.TreeWrapper}>-              <Tree />-            </div>-            <div className={styles.SelectedElementWrapper}>-              <NativeStyleContextController>-                <Suspense fallback={<Loading />}>-                  <SelectedElement />-                </Suspense>-              </NativeStyleContextController>-            </div>-            <ModalDialog />-            <SettingsModal />-          </div>+          <ComponentResizer>+            {({resizeElementRef, onResizeStart, resizeElementStyles}) => (+              <Fragment>+                <div+                  ref={resizeElementRef}+                  className={styles.TreeWrapper}+                  style={{+                    ...resizeElementStyles,+                  }}>+                  <Tree />+                </div>+                <div className={styles.ResizeBarWrapper}>+                  <div+                    onMouseDown={onResizeStart}+                    className={styles.ResizeBar}+                  />+                </div>+                <div className={styles.SelectedElementWrapper}>+                  <NativeStyleContextController>+                    <Suspense fallback={<Loading />}>+                      <SelectedElement />+                    </Suspense>+                  </NativeStyleContextController>+                </div>+                <ModalDialog />+                <SettingsModal />+              </Fragment>+            )}+          </ComponentResizer>         </InspectedElementContextController>       </OwnersListContextController>     </SettingsModalContextController>   ); } +type HorizontalResizeDirection = 'HORIZONTAL';+type VerticalResizeDirection = 'VERTICAL';++const RESIZE_DIRECTIONS: {|+  HORIZONTAL: HorizontalResizeDirection,+  VERTICAL: VerticalResizeDirection,+|} = {+  HORIZONTAL: 'HORIZONTAL',+  VERTICAL: 'VERTICAL',+};++const LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_HORIZONTAL_KEY = `React::DevTools::resizedElementPercentage::${RESIZE_DIRECTIONS.HORIZONTAL}`;+const LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_VERTICAL_KEY = `React::DevTools::resizedElementPercentage::${RESIZE_DIRECTIONS.VERTICAL}`;++function ComponentResizer({children}): {|children: Function|} {+  const [isResizing, setIsResizing] = useState<boolean>(false);+  const [+    horizontalPercentage,+    setHorizontalPercentage,+  ] = useLocalStorage<number>(+    LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_HORIZONTAL_KEY,+    65,+  );+  const [verticalPercentage, setVerticalPercentage] = useLocalStorage<number>(+    LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_VERTICAL_KEY,+    50,+  );+  const updateLocalStorageTimeoutId = useRef<number>(null);+  const componentsWrapperRef = useRef<HTMLDivElement>(null);+  const resizeElementRef = useRef<HTMLElement>(null);+  const [resizeElementStyles, setResizeElementStyles] = useState<Object>({});+  const getResizeDirection: Function = useCallback(() => {+    if (componentsWrapperRef.current === null) {+      return RESIZE_DIRECTIONS.HORIZONTAL;+    }+    const VERTICAL_MODE_MAX_WIDTH: number = 600;+    const {width} = componentsWrapperRef.current.getBoundingClientRect();++    return width > VERTICAL_MODE_MAX_WIDTH+      ? RESIZE_DIRECTIONS.HORIZONTAL+      : RESIZE_DIRECTIONS.VERTICAL;+  }, [componentsWrapperRef]);++  const onResizeStart = useCallback(() => {

I don't think useCallback is doing anything here so we can remove it.

hristo-kanchev

comment created time in 4 days

Pull request review commentfacebook/react

[DevTools] Added resize support for Components panel.

 import portaledContent from '../portaledContent'; import {ModalDialog} from '../ModalDialog'; import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal'; import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';+import {useLocalStorage} from '../hooks';  import styles from './Components.css';  function Components(_: {||}) {-  // TODO Flex wrappers below should be user resizable.   return (     <SettingsModalContextController>       <OwnersListContextController>         <InspectedElementContextController>-          <div className={styles.Components}>-            <div className={styles.TreeWrapper}>-              <Tree />-            </div>-            <div className={styles.SelectedElementWrapper}>-              <NativeStyleContextController>-                <Suspense fallback={<Loading />}>-                  <SelectedElement />-                </Suspense>-              </NativeStyleContextController>-            </div>-            <ModalDialog />-            <SettingsModal />-          </div>+          <ComponentResizer>+            {({resizeElementRef, onResizeStart, resizeElementStyles}) => (+              <Fragment>+                <div+                  ref={resizeElementRef}+                  className={styles.TreeWrapper}+                  style={{+                    ...resizeElementStyles,+                  }}>+                  <Tree />+                </div>+                <div className={styles.ResizeBarWrapper}>+                  <div+                    onMouseDown={onResizeStart}+                    className={styles.ResizeBar}+                  />+                </div>+                <div className={styles.SelectedElementWrapper}>+                  <NativeStyleContextController>+                    <Suspense fallback={<Loading />}>+                      <SelectedElement />+                    </Suspense>+                  </NativeStyleContextController>+                </div>+                <ModalDialog />+                <SettingsModal />+              </Fragment>+            )}+          </ComponentResizer>         </InspectedElementContextController>       </OwnersListContextController>     </SettingsModalContextController>   ); } +type HorizontalResizeDirection = 'HORIZONTAL';+type VerticalResizeDirection = 'VERTICAL';++const RESIZE_DIRECTIONS: {|+  HORIZONTAL: HorizontalResizeDirection,+  VERTICAL: VerticalResizeDirection,+|} = {+  HORIZONTAL: 'HORIZONTAL',+  VERTICAL: 'VERTICAL',+};++const LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_HORIZONTAL_KEY = `React::DevTools::resizedElementPercentage::${RESIZE_DIRECTIONS.HORIZONTAL}`;+const LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_VERTICAL_KEY = `React::DevTools::resizedElementPercentage::${RESIZE_DIRECTIONS.VERTICAL}`;++function ComponentResizer({children}): {|children: Function|} {+  const [isResizing, setIsResizing] = useState<boolean>(false);+  const [+    horizontalPercentage,+    setHorizontalPercentage,+  ] = useLocalStorage<number>(+    LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_HORIZONTAL_KEY,+    65,+  );+  const [verticalPercentage, setVerticalPercentage] = useLocalStorage<number>(+    LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_VERTICAL_KEY,+    50,+  );+  const updateLocalStorageTimeoutId = useRef<number>(null);+  const componentsWrapperRef = useRef<HTMLDivElement>(null);+  const resizeElementRef = useRef<HTMLElement>(null);+  const [resizeElementStyles, setResizeElementStyles] = useState<Object>({});+  const getResizeDirection: Function = useCallback(() => {+    if (componentsWrapperRef.current === null) {+      return RESIZE_DIRECTIONS.HORIZONTAL;+    }+    const VERTICAL_MODE_MAX_WIDTH: number = 600;+    const {width} = componentsWrapperRef.current.getBoundingClientRect();++    return width > VERTICAL_MODE_MAX_WIDTH+      ? RESIZE_DIRECTIONS.HORIZONTAL+      : RESIZE_DIRECTIONS.VERTICAL;+  }, [componentsWrapperRef]);++  const onResizeStart = useCallback(() => {+    setIsResizing(true);+  }, [setIsResizing]);++  const onResizeEnd = useCallback(() => {+    setIsResizing(false);+  }, [setIsResizing]);++  const onResize = useCallback(

Same here. (We can remove useCallback)

hristo-kanchev

comment created time in 4 days

Pull request review commentfacebook/react

[DevTools] Added resize support for Components panel.

 import portaledContent from '../portaledContent'; import {ModalDialog} from '../ModalDialog'; import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal'; import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';+import {useLocalStorage} from '../hooks';  import styles from './Components.css';  function Components(_: {||}) {-  // TODO Flex wrappers below should be user resizable.   return (     <SettingsModalContextController>       <OwnersListContextController>         <InspectedElementContextController>-          <div className={styles.Components}>-            <div className={styles.TreeWrapper}>-              <Tree />-            </div>-            <div className={styles.SelectedElementWrapper}>-              <NativeStyleContextController>-                <Suspense fallback={<Loading />}>-                  <SelectedElement />-                </Suspense>-              </NativeStyleContextController>-            </div>-            <ModalDialog />-            <SettingsModal />-          </div>+          <ComponentResizer>+            {({resizeElementRef, onResizeStart, resizeElementStyles}) => (+              <Fragment>+                <div+                  ref={resizeElementRef}+                  className={styles.TreeWrapper}+                  style={{+                    ...resizeElementStyles,+                  }}>+                  <Tree />+                </div>+                <div className={styles.ResizeBarWrapper}>+                  <div+                    onMouseDown={onResizeStart}+                    className={styles.ResizeBar}+                  />+                </div>+                <div className={styles.SelectedElementWrapper}>+                  <NativeStyleContextController>+                    <Suspense fallback={<Loading />}>+                      <SelectedElement />+                    </Suspense>+                  </NativeStyleContextController>+                </div>+                <ModalDialog />+                <SettingsModal />+              </Fragment>+            )}+          </ComponentResizer>         </InspectedElementContextController>       </OwnersListContextController>     </SettingsModalContextController>   ); } +type HorizontalResizeDirection = 'HORIZONTAL';+type VerticalResizeDirection = 'VERTICAL';++const RESIZE_DIRECTIONS: {|+  HORIZONTAL: HorizontalResizeDirection,+  VERTICAL: VerticalResizeDirection,+|} = {+  HORIZONTAL: 'HORIZONTAL',+  VERTICAL: 'VERTICAL',+};++const LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_HORIZONTAL_KEY = `React::DevTools::resizedElementPercentage::${RESIZE_DIRECTIONS.HORIZONTAL}`;+const LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_VERTICAL_KEY = `React::DevTools::resizedElementPercentage::${RESIZE_DIRECTIONS.VERTICAL}`;++function ComponentResizer({children}): {|children: Function|} {+  const [isResizing, setIsResizing] = useState<boolean>(false);+  const [+    horizontalPercentage,+    setHorizontalPercentage,+  ] = useLocalStorage<number>(+    LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_HORIZONTAL_KEY,+    65,+  );+  const [verticalPercentage, setVerticalPercentage] = useLocalStorage<number>(+    LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_VERTICAL_KEY,+    50,+  );+  const updateLocalStorageTimeoutId = useRef<number>(null);+  const componentsWrapperRef = useRef<HTMLDivElement>(null);+  const resizeElementRef = useRef<HTMLElement>(null);+  const [resizeElementStyles, setResizeElementStyles] = useState<Object>({});+  const getResizeDirection: Function = useCallback(() => {+    if (componentsWrapperRef.current === null) {+      return RESIZE_DIRECTIONS.HORIZONTAL;+    }+    const VERTICAL_MODE_MAX_WIDTH: number = 600;+    const {width} = componentsWrapperRef.current.getBoundingClientRect();++    return width > VERTICAL_MODE_MAX_WIDTH+      ? RESIZE_DIRECTIONS.HORIZONTAL+      : RESIZE_DIRECTIONS.VERTICAL;+  }, [componentsWrapperRef]);++  const onResizeStart = useCallback(() => {+    setIsResizing(true);+  }, [setIsResizing]);++  const onResizeEnd = useCallback(() => {+    setIsResizing(false);+  }, [setIsResizing]);++  const onResize = useCallback(+    e => {+      if (+        !isResizing ||+        componentsWrapperRef.current === null ||+        resizeElementRef.current === null+      ) {+        return;+      }++      e.preventDefault();++      const {+        height,+        width,+        left,+        top,+      } = componentsWrapperRef.current.getBoundingClientRect();+      const resizeDirection = getResizeDirection();+      const currentMousePosition: number =+        resizeDirection === RESIZE_DIRECTIONS.HORIZONTAL+          ? e.clientX - left+          : e.clientY - top;+      const BOUNDARY_PADDING: number = 40;+      const boundary: {|+        min: number,+        max: number,+      |} = {+        min: BOUNDARY_PADDING,+        max:+          resizeDirection === RESIZE_DIRECTIONS.HORIZONTAL+            ? width - BOUNDARY_PADDING+            : height - BOUNDARY_PADDING,+      };+      const isMousePositionInBounds: boolean =+        currentMousePosition > boundary.min &&+        currentMousePosition < boundary.max;++      if (isMousePositionInBounds) {+        const resizedElementDimension: number =+          resizeDirection === RESIZE_DIRECTIONS.HORIZONTAL ? width : height;+        const updatedFlexBasisValue: number =+          (currentMousePosition / resizedElementDimension) * 100;++        resizeElementRef.current.style.flexBasis = `${updatedFlexBasisValue}%`;++        clearTimeout(updateLocalStorageTimeoutId.current);++        updateLocalStorageTimeoutId.current = setTimeout(() => {+          if (resizeDirection === RESIZE_DIRECTIONS.HORIZONTAL) {+            setHorizontalPercentage(updatedFlexBasisValue);+          } else {+            setVerticalPercentage(updatedFlexBasisValue);+          }+        }, 500);+      }+    },+    [componentsWrapperRef, resizeElementRef, isResizing],+  );++  useLayoutEffect(() => {+    if (componentsWrapperRef.current !== null) {+      if (getResizeDirection() === RESIZE_DIRECTIONS.HORIZONTAL) {+        setResizeElementStyles({

Updating state inside of a layout effect will cause another synchronous/blocking render. Can we avoid doing this?

I don't think we actually need this piece of state at all, do we? We could just pass through the style based on the current scrolling direction:

const flexBasis = getResizeDirection() === RESIZE_DIRECTIONS.HORIZONTAL
  ? `${horizontalPercentage}%`
  : `${verticalPercentage}%`;

For that matter, we could probably combine all 3 bits of information- direction, horizontal %, and vertical %- into a single piece of state (or a reducer).

Then we could use a (passive) effect with a debounce timeout (similar to how you are currently doing it) that updates local storage to match the current reducer value. What do you think about this?

hristo-kanchev

comment created time in 4 days

Pull request review commentfacebook/react

[DevTools] Added resize support for Components panel.

 import portaledContent from '../portaledContent'; import {ModalDialog} from '../ModalDialog'; import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal'; import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';+import {useLocalStorage} from '../hooks';  import styles from './Components.css';  function Components(_: {||}) {-  // TODO Flex wrappers below should be user resizable.   return (     <SettingsModalContextController>       <OwnersListContextController>         <InspectedElementContextController>-          <div className={styles.Components}>-            <div className={styles.TreeWrapper}>-              <Tree />-            </div>-            <div className={styles.SelectedElementWrapper}>-              <NativeStyleContextController>-                <Suspense fallback={<Loading />}>-                  <SelectedElement />-                </Suspense>-              </NativeStyleContextController>-            </div>-            <ModalDialog />-            <SettingsModal />-          </div>+          <ComponentResizer>+            {({resizeElementRef, onResizeStart, resizeElementStyles}) => (+              <Fragment>+                <div+                  ref={resizeElementRef}+                  className={styles.TreeWrapper}+                  style={{+                    ...resizeElementStyles,+                  }}>+                  <Tree />+                </div>+                <div className={styles.ResizeBarWrapper}>+                  <div+                    onMouseDown={onResizeStart}+                    className={styles.ResizeBar}+                  />+                </div>+                <div className={styles.SelectedElementWrapper}>+                  <NativeStyleContextController>+                    <Suspense fallback={<Loading />}>+                      <SelectedElement />+                    </Suspense>+                  </NativeStyleContextController>+                </div>+                <ModalDialog />+                <SettingsModal />+              </Fragment>+            )}+          </ComponentResizer>         </InspectedElementContextController>       </OwnersListContextController>     </SettingsModalContextController>   ); } +type HorizontalResizeDirection = 'HORIZONTAL';+type VerticalResizeDirection = 'VERTICAL';++const RESIZE_DIRECTIONS: {|+  HORIZONTAL: HorizontalResizeDirection,+  VERTICAL: VerticalResizeDirection,+|} = {+  HORIZONTAL: 'HORIZONTAL',+  VERTICAL: 'VERTICAL',+};++const LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_HORIZONTAL_KEY = `React::DevTools::resizedElementPercentage::${RESIZE_DIRECTIONS.HORIZONTAL}`;+const LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_VERTICAL_KEY = `React::DevTools::resizedElementPercentage::${RESIZE_DIRECTIONS.VERTICAL}`;++function ComponentResizer({children}): {|children: Function|} {+  const [isResizing, setIsResizing] = useState<boolean>(false);+  const [+    horizontalPercentage,+    setHorizontalPercentage,+  ] = useLocalStorage<number>(+    LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_HORIZONTAL_KEY,+    65,+  );+  const [verticalPercentage, setVerticalPercentage] = useLocalStorage<number>(+    LOCAL_STORAGE_RESIZE_ELEMENT_PERCENTAGE_VERTICAL_KEY,+    50,+  );+  const updateLocalStorageTimeoutId = useRef<number>(null);+  const componentsWrapperRef = useRef<HTMLDivElement>(null);+  const resizeElementRef = useRef<HTMLElement>(null);+  const [resizeElementStyles, setResizeElementStyles] = useState<Object>({});+  const getResizeDirection: Function = useCallback(() => {+    if (componentsWrapperRef.current === null) {+      return RESIZE_DIRECTIONS.HORIZONTAL;+    }+    const VERTICAL_MODE_MAX_WIDTH: number = 600;+    const {width} = componentsWrapperRef.current.getBoundingClientRect();++    return width > VERTICAL_MODE_MAX_WIDTH+      ? RESIZE_DIRECTIONS.HORIZONTAL+      : RESIZE_DIRECTIONS.VERTICAL;+  }, [componentsWrapperRef]);++  const onResizeStart = useCallback(() => {+    setIsResizing(true);+  }, [setIsResizing]);++  const onResizeEnd = useCallback(() => {

Same here. (We can remove useCallback)

hristo-kanchev

comment created time in 4 days

issue commentfacebook/react

Bug(devtools): complex types ignored sometimes in useDebugValue

Seems like a use case that I'm not sure if DevTools needs to support.

Because there are no built-in hooks used other than useDebugValue? Would probably agree with that. It's still surprising behavior which I'd like to avoid generally.

Yes. There's no need to use such a hook at all, since hooks calls can't be conditional to begin with (without leading to errors).

@bvaughn I'm quite lost where the tests for these components are located. The data seems to be wired up correctly. It looks like the view is not working but I can't find tests for those.

You'd probably want to add tests to the following places:

  • v16+ - https://github.com/facebook/react/blob/master/packages/react-devtools-shared/src/tests/inspectedElementContext-test.js
  • v5 - https://github.com/facebook/react/blob/master/packages/react-devtools-shared/src/tests/legacy/inspectElement-test.js

Is there some documentation how to test devtools?

No.

eps1lon

comment created time in 4 days

more