diff --git a/shells/dev/app/DeeplyNestedComponents/index.js b/shells/dev/app/DeeplyNestedComponents/index.js new file mode 100644 index 0000000000..61deec3754 --- /dev/null +++ b/shells/dev/app/DeeplyNestedComponents/index.js @@ -0,0 +1,34 @@ +// @flow + +import React, { Fragment } from 'react'; + +function wrapWithHoc(Component, index) { + function HOC() { + return ; + } + HOC.displayName = `HOC-${index}`; + return HOC; +} + +function wrapWithNested(Component, times) { + for (let i = 0; i < times; i++) { + Component = wrapWithHoc(Component, i); + } + + return Component; +} + +function Nested() { + return
Deeply nested div
; +} + +const DeeplyNested = wrapWithNested(Nested, 100); + +export default function DeeplyNestedComponents() { + return ( + +

Deeply nested component

+ +
+ ); +} diff --git a/shells/dev/app/EditableProps/EditableProps.js b/shells/dev/app/EditableProps/EditableProps.js deleted file mode 100644 index c48a78dc8c..0000000000 --- a/shells/dev/app/EditableProps/EditableProps.js +++ /dev/null @@ -1,132 +0,0 @@ -// @flow - -import React, { - createContext, - Component, - Fragment, - useCallback, - useDebugValue, - useEffect, - useReducer, - useState, -} from 'react'; -import styles from './EditableProps.css'; - -const initialData = { foo: 'FOO', bar: 'BAR' }; - -function reducer(state, action) { - switch (action.type) { - case 'swap': - return { foo: state.bar, bar: state.foo }; - default: - throw new Error(); - } -} - -type StatefulFunctionProps = {| name: string |}; - -function StatefulFunction({ name }: StatefulFunctionProps) { - const [count, updateCount] = useState(0); - const debouncedCount = useDebounce(count, 1000); - const handleUpdateCountClick = useCallback(() => updateCount(count + 1), [ - count, - ]); - - const [data, dispatch] = useReducer(reducer, initialData); - const handleUpdateReducerClick = useCallback( - () => dispatch({ type: 'swap' }), - [] - ); - - return ( - -
  • Name: {name}
  • -
  • - -
  • -
  • - Reducer state: foo "{data.foo}", bar "{data.bar}" -
  • -
  • - -
  • -
    - ); -} - -const BoolContext = createContext(true); -BoolContext.displayName = 'BoolContext'; - -type Props = {| name: string, toggle: boolean |}; -type State = {| cities: Array, state: string |}; - -class StatefulClass extends Component { - static contextType = BoolContext; - - state: State = { - cities: ['San Francisco', 'San Jose'], - state: 'California', - }; - - handleChange = ({ target }) => - this.setState({ - state: target.value, - }); - - render() { - return ( - -
  • Name: {this.props.name}
  • -
  • Toggle: {this.props.toggle ? 'true' : 'false'}
  • -
  • - State: -
  • -
  • Cities: {this.state.cities.join(', ')}
  • -
  • Context: {this.context ? 'true' : 'false'}
  • -
    - ); - } -} - -export default function EditableProps() { - return ( -
    -
    Editable props
    -
      - - -
    -
    - ); -} - -// Below copied from https://usehooks.com/ -function useDebounce(value, delay) { - // State and setters for debounced value - const [debouncedValue, setDebouncedValue] = useState(value); - - // Show the value in DevTools - useDebugValue(debouncedValue); - - useEffect( - () => { - // Update debounced value after delay - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - - // Cancel the timeout if value changes (also on delay change or unmount) - // This is how we prevent debounced value from updating if value is changed ... - // .. within the delay period. Timeout gets cleared and restarted. - return () => { - clearTimeout(handler); - }; - }, - [value, delay] // Only re-call effect if value or delay changes - ); - - return debouncedValue; -} -// Above copied from https://usehooks.com/ diff --git a/shells/dev/app/EditableProps/index.js b/shells/dev/app/EditableProps/index.js index a57026743b..c86d717ee3 100644 --- a/shells/dev/app/EditableProps/index.js +++ b/shells/dev/app/EditableProps/index.js @@ -1,5 +1,131 @@ // @flow -import EditableProps from './EditableProps'; +import React, { + createContext, + Component, + Fragment, + useCallback, + useDebugValue, + useEffect, + useReducer, + useState, +} from 'react'; -export default EditableProps; +const initialData = { foo: 'FOO', bar: 'BAR' }; + +function reducer(state, action) { + switch (action.type) { + case 'swap': + return { foo: state.bar, bar: state.foo }; + default: + throw new Error(); + } +} + +type StatefulFunctionProps = {| name: string |}; + +function StatefulFunction({ name }: StatefulFunctionProps) { + const [count, updateCount] = useState(0); + const debouncedCount = useDebounce(count, 1000); + const handleUpdateCountClick = useCallback(() => updateCount(count + 1), [ + count, + ]); + + const [data, dispatch] = useReducer(reducer, initialData); + const handleUpdateReducerClick = useCallback( + () => dispatch({ type: 'swap' }), + [] + ); + + return ( + +
  • Name: {name}
  • +
  • + +
  • +
  • + Reducer state: foo "{data.foo}", bar "{data.bar}" +
  • +
  • + +
  • +
    + ); +} + +const BoolContext = createContext(true); +BoolContext.displayName = 'BoolContext'; + +type Props = {| name: string, toggle: boolean |}; +type State = {| cities: Array, state: string |}; + +class StatefulClass extends Component { + static contextType = BoolContext; + + state: State = { + cities: ['San Francisco', 'San Jose'], + state: 'California', + }; + + handleChange = ({ target }) => + this.setState({ + state: target.value, + }); + + render() { + return ( + +
  • Name: {this.props.name}
  • +
  • Toggle: {this.props.toggle ? 'true' : 'false'}
  • +
  • + State: +
  • +
  • Cities: {this.state.cities.join(', ')}
  • +
  • Context: {this.context ? 'true' : 'false'}
  • +
    + ); + } +} + +export default function EditableProps() { + return ( + +

    Editable props

    +
      + + +
    +
    + ); +} + +// Below copied from https://usehooks.com/ +function useDebounce(value, delay) { + // State and setters for debounced value + const [debouncedValue, setDebouncedValue] = useState(value); + + // Show the value in DevTools + useDebugValue(debouncedValue); + + useEffect( + () => { + // Update debounced value after delay + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + // Cancel the timeout if value changes (also on delay change or unmount) + // This is how we prevent debounced value from updating if value is changed ... + // .. within the delay period. Timeout gets cleared and restarted. + return () => { + clearTimeout(handler); + }; + }, + [value, delay] // Only re-call effect if value or delay changes + ); + + return debouncedValue; +} +// Above copied from https://usehooks.com/ diff --git a/shells/dev/app/InspectableElements/InspectableElements.css b/shells/dev/app/InspectableElements/InspectableElements.css deleted file mode 100644 index b1ebb8ee71..0000000000 --- a/shells/dev/app/InspectableElements/InspectableElements.css +++ /dev/null @@ -1,13 +0,0 @@ -.App { - /* GitHub.com frontend fonts */ - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, - sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; - font-size: 14px; - line-height: 1.5; -} - -.Header { - font-size: 1.5rem; - font-weight: bold; - margin-bottom: 0.5rem; -} diff --git a/shells/dev/app/InspectableElements/InspectableElements.js b/shells/dev/app/InspectableElements/InspectableElements.js index b7cbbf2937..d22fc29f52 100644 --- a/shells/dev/app/InspectableElements/InspectableElements.js +++ b/shells/dev/app/InspectableElements/InspectableElements.js @@ -1,20 +1,19 @@ // @flow -import React from 'react'; +import React, { Fragment } from 'react'; import Contexts from './Contexts'; import CustomHooks from './CustomHooks'; import NestedProps from './NestedProps'; -import styles from './InspectableElements.css'; // TODO Add Immutable JS example export default function InspectableElements() { return ( -
    -
    Inspectable elements
    + +

    Inspectable elements

    -
    + ); } diff --git a/shells/dev/app/ToDoList/List.css b/shells/dev/app/ToDoList/List.css index ae39496000..0583402840 100644 --- a/shells/dev/app/ToDoList/List.css +++ b/shells/dev/app/ToDoList/List.css @@ -1,17 +1,3 @@ -.App { - /* GitHub.com frontend fonts */ - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, - sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; - font-size: 14px; - line-height: 1.5; -} - -.Header { - font-size: 1.5rem; - font-weight: bold; - margin-bottom: 0.5rem; -} - .Input { font-size: 1rem; padding: 0.25rem; diff --git a/shells/dev/app/ToDoList/List.js b/shells/dev/app/ToDoList/List.js index a3fe76f388..b8a2a4636a 100644 --- a/shells/dev/app/ToDoList/List.js +++ b/shells/dev/app/ToDoList/List.js @@ -1,6 +1,6 @@ // @flow -import React, { useCallback, useState } from 'react'; +import React, { Fragment, useCallback, useState } from 'react'; import ListItem from './ListItem'; import styles from './List.css'; @@ -77,8 +77,8 @@ export default function List(props: Props) { ); return ( -
    -
    List
    + +

    List

    ))} -
    + ); } diff --git a/shells/dev/app/index.js b/shells/dev/app/index.js index 77a8093b42..ea813a28fe 100644 --- a/shells/dev/app/index.js +++ b/shells/dev/app/index.js @@ -4,11 +4,14 @@ import { createElement } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import DeeplyNestedComponents from './DeeplyNestedComponents'; import EditableProps from './EditableProps'; import ElementTypes from './ElementTypes'; import InspectableElements from './InspectableElements'; import ToDoList from './ToDoList'; +import './styles.css'; + const containers = []; function mountHelper(App) { @@ -26,6 +29,7 @@ function mountTestApp() { mountHelper(InspectableElements); mountHelper(ElementTypes); mountHelper(EditableProps); + mountHelper(DeeplyNestedComponents); } function unmountTestApp() { diff --git a/shells/dev/app/EditableProps/EditableProps.css b/shells/dev/app/styles.css similarity index 94% rename from shells/dev/app/EditableProps/EditableProps.css rename to shells/dev/app/styles.css index b1ebb8ee71..2a995314c1 100644 --- a/shells/dev/app/EditableProps/EditableProps.css +++ b/shells/dev/app/styles.css @@ -1,4 +1,4 @@ -.App { +body { /* GitHub.com frontend fonts */ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; @@ -6,7 +6,7 @@ line-height: 1.5; } -.Header { +h1 { font-size: 1.5rem; font-weight: bold; margin-bottom: 0.5rem; diff --git a/src/devtools/views/Element.js b/src/devtools/views/Element.js index 8b0cebe6c3..c87b11b480 100644 --- a/src/devtools/views/Element.js +++ b/src/devtools/views/Element.js @@ -1,6 +1,13 @@ // @flow -import React, { Fragment, useCallback, useContext, useEffect, useMemo, useRef } from 'react'; +import React, { + Fragment, + useCallback, + useContext, + useEffect, + useMemo, + useRef, +} from 'react'; import { ElementTypeClass, ElementTypeFunction } from 'src/devtools/types'; import { createRegExp } from './utils'; import { TreeContext } from './TreeContext'; @@ -26,6 +33,7 @@ export default function ElementView({ index, style }: Props) { const element = getElementAtIndex(index); const id = element === null ? null : element.id; + const isSelected = selectedElementID === id; const handleDoubleClick = useCallback(() => { if (id !== null) { @@ -33,7 +41,7 @@ export default function ElementView({ index, style }: Props) { } }, [id, selectOwner]); - const ref = useRef(); + const ref = useRef(null); useEffect(() => { if (isSelected) { @@ -64,7 +72,6 @@ export default function ElementView({ index, style }: Props) { const { depth, displayName, key, type } = ((element: any): Element); - const isSelected = selectedElementID === id; const showDollarR = isSelected && (type === ElementTypeClass || type === ElementTypeFunction);