| 1 | /* eslint-disable react/react-in-jsx-scope, react/jsx-no-undef */ |
| 2 | /* global React ReactCache ReactDOM SchedulerTracing ScheduleTracing */ |
| 3 | |
| 4 | const apps = []; |
| 5 | |
| 6 | const pieces = React.version.split('.'); |
| 7 | const major = |
| 8 | pieces[0] === '0' ? parseInt(pieces[1], 10) : parseInt(pieces[0], 10); |
| 9 | const minor = |
| 10 | pieces[0] === '0' ? parseInt(pieces[2], 10) : parseInt(pieces[1], 10); |
| 11 | |
| 12 | // Convenience wrapper to organize API features in DevTools. |
| 13 | function Feature({children, label, version}) { |
| 14 | return ( |
| 15 | <div className="Feature"> |
| 16 | <div className="FeatureHeader"> |
| 17 | <code className="FeatureCode">{label}</code> |
| 18 | <small>{version}</small> |
| 19 | </div> |
| 20 | {children} |
| 21 | </div> |
| 22 | ); |
| 23 | } |
| 24 | |
| 25 | // Simplify interaction tracing for tests below. |
| 26 | let trace = null; |
| 27 | if (typeof SchedulerTracing !== 'undefined') { |
| 28 | trace = SchedulerTracing.unstable_trace; |
| 29 | } else if (typeof ScheduleTracing !== 'undefined') { |
| 30 | trace = ScheduleTracing.unstable_trace; |
| 31 | } else { |
| 32 | trace = (_, __, callback) => callback(); |
| 33 | } |
| 34 | |
| 35 | // https://github.com/facebook/react/blob/main/CHANGELOG.md |
| 36 | switch (major) { |
| 37 | case 16: |
| 38 | switch (minor) { |
| 39 | case 7: |
| 40 | if (typeof React.useState === 'function') { |
| 41 | // Hooks |
| 42 | function Hooks() { |
| 43 | const [count, setCount] = React.useState(0); |
| 44 | const incrementCount = React.useCallback( |
| 45 | () => setCount(count + 1), |
| 46 | [count] |
| 47 | ); |
| 48 | return ( |
| 49 | <div> |
| 50 | count: {count}{' '} |
| 51 | <button onClick={incrementCount}>increment</button> |
| 52 | </div> |
| 53 | ); |
| 54 | } |
| 55 | apps.push( |
| 56 | <Feature key="Hooks" label="Hooks" version="16.7+"> |
| 57 | <Hooks /> |
| 58 | </Feature> |
| 59 | ); |
| 60 | } |
| 61 | case 6: |
| 62 | // memo |
| 63 | function LabelComponent({label}) { |
| 64 | return <label>{label}</label>; |
| 65 | } |
| 66 | const AnonymousMemoized = React.memo(({label}) => ( |
| 67 | <label>{label}</label> |
| 68 | )); |
| 69 | const Memoized = React.memo(LabelComponent); |
| 70 | const CustomMemoized = React.memo(LabelComponent); |
| 71 | CustomMemoized.displayName = 'MemoizedLabelFunction'; |
| 72 | apps.push( |
| 73 | <Feature key="memo" label="memo" version="16.6+"> |
| 74 | <AnonymousMemoized label="AnonymousMemoized" /> |
| 75 | <Memoized label="Memoized" /> |
| 76 | <CustomMemoized label="CustomMemoized" /> |
| 77 | </Feature> |
| 78 | ); |
| 79 | |
| 80 | // Suspense |
| 81 | const loadResource = ([text, ms]) => { |
| 82 | return new Promise((resolve, reject) => { |
| 83 | setTimeout(() => { |
| 84 | resolve(text); |
| 85 | }, ms); |
| 86 | }); |
| 87 | }; |
| 88 | const getResourceKey = ([text, ms]) => text; |
| 89 | const Resource = ReactCache.unstable_createResource( |
| 90 | loadResource, |
| 91 | getResourceKey |
| 92 | ); |
| 93 | class Suspending extends React.Component { |
| 94 | state = {useSuspense: false}; |
| 95 | useSuspense = () => this.setState({useSuspense: true}); |
| 96 | render() { |
| 97 | if (this.state.useSuspense) { |
| 98 | const text = Resource.read(['loaded', 2000]); |
| 99 | return text; |
| 100 | } else { |
| 101 | return <button onClick={this.useSuspense}>load data</button>; |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | apps.push( |
| 106 | <Feature key="Suspense" label="Suspense" version="16.6+"> |
| 107 | <React.Suspense fallback={<div>loading...</div>}> |
| 108 | <Suspending /> |
| 109 | </React.Suspense> |
| 110 | </Feature> |
| 111 | ); |
| 112 | |
| 113 | // lazy |
| 114 | const LazyWithDefaultProps = React.lazy( |
| 115 | () => |
| 116 | new Promise(resolve => { |
| 117 | function FooWithDefaultProps(props) { |
| 118 | return ( |
| 119 | <h1> |
| 120 | {props.greeting}, {props.name} |
| 121 | </h1> |
| 122 | ); |
| 123 | } |
| 124 | FooWithDefaultProps.defaultProps = { |
| 125 | name: 'World', |
| 126 | greeting: 'Bonjour', |
| 127 | }; |
| 128 | resolve({ |
| 129 | default: FooWithDefaultProps, |
| 130 | }); |
| 131 | }) |
| 132 | ); |
| 133 | apps.push( |
| 134 | <Feature key="lazy" label="lazy" version="16.6+"> |
| 135 | <React.Suspense fallback={<div>loading...</div>}> |
| 136 | <LazyWithDefaultProps greeting="Hello" /> |
| 137 | </React.Suspense> |
| 138 | </Feature> |
| 139 | ); |
| 140 | case 5: |
| 141 | case 4: |
| 142 | // unstable_Profiler |
| 143 | class ProfilerChild extends React.Component { |
| 144 | state = {count: 0}; |
| 145 | incrementCount = () => |
| 146 | this.setState(prevState => ({count: prevState.count + 1})); |
| 147 | render() { |
| 148 | return ( |
| 149 | <div> |
| 150 | count: {this.state.count}{' '} |
| 151 | <button onClick={this.incrementCount}>increment</button> |
| 152 | </div> |
| 153 | ); |
| 154 | } |
| 155 | } |
| 156 | const onRender = (...args) => {}; |
| 157 | const Profiler = React.unstable_Profiler || React.Profiler; |
| 158 | apps.push( |
| 159 | <Feature |
| 160 | key="unstable_Profiler" |
| 161 | label="unstable_Profiler" |
| 162 | version="16.4+"> |
| 163 | <Profiler id="count" onRender={onRender}> |
| 164 | <div> |
| 165 | <ProfilerChild /> |
| 166 | </div> |
| 167 | </Profiler> |
| 168 | </Feature> |
| 169 | ); |
| 170 | case 3: |
| 171 | // createContext() |
| 172 | const LocaleContext = React.createContext(); |
| 173 | LocaleContext.displayName = 'LocaleContext'; |
| 174 | const ThemeContext = React.createContext(); |
| 175 | apps.push( |
| 176 | <Feature key="createContext" label="createContext" version="16.3+"> |
| 177 | <ThemeContext.Provider value="blue"> |
| 178 | <ThemeContext.Consumer> |
| 179 | {theme => <div>theme: {theme}</div>} |
| 180 | </ThemeContext.Consumer> |
| 181 | </ThemeContext.Provider> |
| 182 | <LocaleContext.Provider value="en-US"> |
| 183 | <LocaleContext.Consumer> |
| 184 | {locale => <div>locale: {locale}</div>} |
| 185 | </LocaleContext.Consumer> |
| 186 | </LocaleContext.Provider> |
| 187 | </Feature> |
| 188 | ); |
| 189 | |
| 190 | // forwardRef() |
| 191 | const AnonymousFunction = React.forwardRef((props, ref) => ( |
| 192 | <div ref={ref}>{props.children}</div> |
| 193 | )); |
| 194 | const NamedFunction = React.forwardRef(function named(props, ref) { |
| 195 | return <div ref={ref}>{props.children}</div>; |
| 196 | }); |
| 197 | const CustomName = React.forwardRef((props, ref) => ( |
| 198 | <div ref={ref}>{props.children}</div> |
| 199 | )); |
| 200 | CustomName.displayName = 'CustomNameForwardRef'; |
| 201 | apps.push( |
| 202 | <Feature key="forwardRef" label="forwardRef" version="16.3+"> |
| 203 | <AnonymousFunction>AnonymousFunction</AnonymousFunction> |
| 204 | <NamedFunction>NamedFunction</NamedFunction> |
| 205 | <CustomName>CustomName</CustomName> |
| 206 | </Feature> |
| 207 | ); |
| 208 | |
| 209 | // StrictMode |
| 210 | class StrictModeChild extends React.Component { |
| 211 | render() { |
| 212 | return 'StrictModeChild'; |
| 213 | } |
| 214 | } |
| 215 | apps.push( |
| 216 | <Feature key="StrictMode" label="StrictMode" version="16.3+"> |
| 217 | <React.StrictMode> |
| 218 | <StrictModeChild /> |
| 219 | </React.StrictMode> |
| 220 | </Feature> |
| 221 | ); |
| 222 | |
| 223 | // unstable_AsyncMode (later renamed to unstable_ConcurrentMode, then ConcurrentMode) |
| 224 | const ConcurrentMode = |
| 225 | React.ConcurrentMode || |
| 226 | React.unstable_ConcurrentMode || |
| 227 | React.unstable_AsyncMode; |
| 228 | apps.push( |
| 229 | <Feature |
| 230 | key="AsyncMode/ConcurrentMode" |
| 231 | label="AsyncMode/ConcurrentMode" |
| 232 | version="16.3+"> |
| 233 | <ConcurrentMode> |
| 234 | <div> |
| 235 | unstable_AsyncMode was added in 16.3, renamed to |
| 236 | unstable_ConcurrentMode in 16.5, and then renamed to |
| 237 | ConcurrentMode in 16.7 |
| 238 | </div> |
| 239 | </ConcurrentMode> |
| 240 | </Feature> |
| 241 | ); |
| 242 | case 2: |
| 243 | // Fragment |
| 244 | apps.push( |
| 245 | <Feature key="Fragment" label="Fragment" version="16.4+"> |
| 246 | <React.Fragment> |
| 247 | <div>one</div> |
| 248 | <div>two</div> |
| 249 | </React.Fragment> |
| 250 | </Feature> |
| 251 | ); |
| 252 | case 1: |
| 253 | case 0: |
| 254 | default: |
| 255 | break; |
| 256 | } |
| 257 | break; |
| 258 | case 15: |
| 259 | break; |
| 260 | case 14: |
| 261 | break; |
| 262 | default: |
| 263 | break; |
| 264 | } |
| 265 | |
| 266 | function Even() { |
| 267 | return <small>(even)</small>; |
| 268 | } |
| 269 | |
| 270 | // Simple stateful app shared by all React versions |
| 271 | class SimpleApp extends React.Component { |
| 272 | state = {count: 0}; |
| 273 | incrementCount = () => { |
| 274 | const updaterFn = prevState => ({count: prevState.count + 1}); |
| 275 | trace('Updating count', performance.now(), () => this.setState(updaterFn)); |
| 276 | }; |
| 277 | render() { |
| 278 | const {count} = this.state; |
| 279 | return ( |
| 280 | <div> |
| 281 | {count % 2 === 0 ? ( |
| 282 | <span> |
| 283 | count: {count} <Even /> |
| 284 | </span> |
| 285 | ) : ( |
| 286 | <span>count: {count}</span> |
| 287 | )}{' '} |
| 288 | <button onClick={this.incrementCount}>increment</button> |
| 289 | </div> |
| 290 | ); |
| 291 | } |
| 292 | } |
| 293 | apps.push( |
| 294 | <Feature key="Simple stateful app" label="Simple stateful app" version="any"> |
| 295 | <SimpleApp /> |
| 296 | </Feature> |
| 297 | ); |
| 298 | |
| 299 | // This component, with the version prop, helps organize DevTools at a glance. |
| 300 | function TopLevelWrapperForDevTools({version}) { |
| 301 | let header = <h1>React {version}</h1>; |
| 302 | if (version.includes('canary')) { |
| 303 | const commitSha = version.match(/.+canary-(.+)/)[1]; |
| 304 | header = ( |
| 305 | <h1> |
| 306 | React canary{' '} |
| 307 | <a href={`https://github.com/facebook/react/commit/${commitSha}`}> |
| 308 | {commitSha} |
| 309 | </a> |
| 310 | </h1> |
| 311 | ); |
| 312 | } else if (version.includes('alpha')) { |
| 313 | header = <h1>React next</h1>; |
| 314 | } |
| 315 | |
| 316 | return ( |
| 317 | <div> |
| 318 | {header} |
| 319 | {apps} |
| 320 | </div> |
| 321 | ); |
| 322 | } |
| 323 | TopLevelWrapperForDevTools.displayName = 'React'; |
| 324 | |
| 325 | ReactDOM.render( |
| 326 | <TopLevelWrapperForDevTools version={React.version} />, |
| 327 | document.getElementById('root') |
| 328 | ); |