Fix useImperativeHandle to have no deps by default (#14801)
* Fix useImperativeHandle to have no deps by default * Save a byte? * Nit: null
Dan Abramov committed
Feb 11, 2019 at 18:42 UTC
f24a0da6e0f59484e5aafd0825bb1a6ed27d7182
2 files changed
+107
-2
packages/react-reconciler/src/ReactFiberHooks.js
+2
-2
@@ -905,7 +905,7 @@ function mountImperativeHandle<T>(
905
906
// TODO: If deps are provided, should we skip comparing the ref itself?
907
const effectDeps =
908
- deps !== null && deps !== undefined ? deps.concat([ref]) : [ref];
908
+ deps !== null && deps !== undefined ? deps.concat([ref]) : null;
909
910
return mountEffectImpl(
911
UpdateEffect,
@@ -931,7 +931,7 @@ function updateImperativeHandle<T>(
931
932
// TODO: If deps are provided, should we skip comparing the ref itself?
933
const effectDeps =
934
- deps !== null && deps !== undefined ? deps.concat([ref]) : [ref];
934
+ deps !== null && deps !== undefined ? deps.concat([ref]) : null;
935
936
return updateEffectImpl(
937
UpdateEffect,
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
+105
@@ -1593,6 +1593,111 @@ describe('ReactHooksWithNoopRenderer', () => {
1593
});
1594
});
1595
1596
+ describe('useImperativeHandle', () => {
1597
+ it('does not update when deps are the same', () => {
1598
+ const INCREMENT = 'INCREMENT';
1599
+
1600
+ function reducer(state, action) {
1601
+ return action === INCREMENT ? state + 1 : state;
1602
+ }
1603
+
1604
+ function Counter(props, ref) {
1605
+ const [count, dispatch] = useReducer(reducer, 0);
1606
+ useImperativeHandle(ref, () => ({count, dispatch}), []);
1607
+ return <Text text={'Count: ' + count} />;
1608
+ }
1609
+
1610
+ Counter = forwardRef(Counter);
1611
+ const counter = React.createRef(null);
1612
+ ReactNoop.render(<Counter ref={counter} />);
1613
+ ReactNoop.flush();
1614
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
1615
+ expect(counter.current.count).toBe(0);
1616
+
1617
+ act(() => {
1618
+ counter.current.dispatch(INCREMENT);
1619
+ });
1620
+ ReactNoop.flush();
1621
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
1622
+ // Intentionally not updated because of [] deps:
1623
+ expect(counter.current.count).toBe(0);
1624
+ });
1625
+
1626
+ // Regression test for https://github.com/facebook/react/issues/14782
1627
+ it('automatically updates when deps are not specified', () => {
1628
+ const INCREMENT = 'INCREMENT';
1629
+
1630
+ function reducer(state, action) {
1631
+ return action === INCREMENT ? state + 1 : state;
1632
+ }
1633
+
1634
+ function Counter(props, ref) {
1635
+ const [count, dispatch] = useReducer(reducer, 0);
1636
+ useImperativeHandle(ref, () => ({count, dispatch}));
1637
+ return <Text text={'Count: ' + count} />;
1638
+ }
1639
+
1640
+ Counter = forwardRef(Counter);
1641
+ const counter = React.createRef(null);
1642
+ ReactNoop.render(<Counter ref={counter} />);
1643
+ ReactNoop.flush();
1644
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
1645
+ expect(counter.current.count).toBe(0);
1646
+
1647
+ act(() => {
1648
+ counter.current.dispatch(INCREMENT);
1649
+ });
1650
+ ReactNoop.flush();
1651
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
1652
+ expect(counter.current.count).toBe(1);
1653
+ });
1654
+
1655
+ it('updates when deps are different', () => {
1656
+ const INCREMENT = 'INCREMENT';
1657
+
1658
+ function reducer(state, action) {
1659
+ return action === INCREMENT ? state + 1 : state;
1660
+ }
1661
+
1662
+ let totalRefUpdates = 0;
1663
+ function Counter(props, ref) {
1664
+ const [count, dispatch] = useReducer(reducer, 0);
1665
+ useImperativeHandle(
1666
+ ref,
1667
+ () => {
1668
+ totalRefUpdates++;
1669
+ return {count, dispatch};
1670
+ },
1671
+ [count],
1672
+ );
1673
+ return <Text text={'Count: ' + count} />;
1674
+ }
1675
+
1676
+ Counter = forwardRef(Counter);
1677
+ const counter = React.createRef(null);
1678
+ ReactNoop.render(<Counter ref={counter} />);
1679
+ ReactNoop.flush();
1680
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
1681
+ expect(counter.current.count).toBe(0);
1682
+ expect(totalRefUpdates).toBe(1);
1683
+
1684
+ act(() => {
1685
+ counter.current.dispatch(INCREMENT);
1686
+ });
1687
+ ReactNoop.flush();
1688
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
1689
+ expect(counter.current.count).toBe(1);
1690
+ expect(totalRefUpdates).toBe(2);
1691
+
1692
+ // Update that doesn't change the ref dependencies
1693
+ ReactNoop.render(<Counter ref={counter} />);
1694
+ ReactNoop.flush();
1695
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
1696
+ expect(counter.current.count).toBe(1);
1697
+ expect(totalRefUpdates).toBe(2); // Should not increase since last time
1698
+ });
1699
+ });
1700
+
1701
describe('progressive enhancement (not supported)', () => {
1702
it('mount additional state', () => {
1703
let updateA;