Skip to content

Commit 094890b

Browse files
authored
[labs/react] add react wrapper examples to the playground (lit#875)
* basic working example * remove jsx from tsconfig, missing semicolon * react hello world example with simple-greeting * counter example with mwc button * use count instead of state as varname * react click counter renamed to react counter * react custom event * packages/lit-dev-content/samples * better formatting * better title * custom event examples * pause, three examples: hello world, react events, custom events * different message * consistent deps ordering with react vars * describe deps exports * rename react-hello-world to react-basics * remove unnecessary span * remove slots example, unfinished * add slot example * address some feedback * format examples, indent with spaces 2 * better function syntax * add app.tsx for first three examples * add react to demo imports' * remove callback deps * slot example * split react into own module, same for component * add new lines * wrong module for events example * slots app can be implicitly returned * remove stale comments * refs has a visual pause play reference * less code * update styles * wanderboids plural * more defined triangle shape * update slots to be more direct and flat example * more explicit wording on custome event example * remove faux-react for react * add react module bug to react modules * added integrated timestep to control fps * add slider for fixed timestep * cheap integration * faux react on slots * correct timestep integral tick * use event to announce state to react * use event state as app state * better hooks, more comments * too many right turns, bad maths * separate boid canvas from logic, make it easy * bad return syntax * only carry ctx when needed * associate slots with color * format * use grid, column layout follows code * better comments * public props before private props * update wording on refs * react at top of page * bring back null checks * use ref and effect, focus on refs not adding event listeners * better name for callback * better comment wording in useEffect * better comment wording in useEffect * ref is ready for review, original demo more concise * end of file add return * spelling mistakes, ordering * combine shared properties in ref example * add content-type * content-type * adjust jsx structures * consistent import syntax * add example of wrapping react component with slot * react components can be a string * sync corrections * remove trailing semicolon * hello react * export react immediately * target div with id rather than semantic section * feedback comments * forgot id in basic example * reduce number of files in examples * prioritize life-cycle method order * add eof return * simple-greeting line at end of file * no fragment needed on slot example * use useEffect over bad component patterns * syntax adjustments, use update in refs example * use willupdate to broadcast state * no use effect, willUpdate * use demo-greeting over simple-greeting * wording, spacing * reduce ref example complexity * smaller functions * update event name in description
1 parent 7248265 commit 094890b

26 files changed

+647
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {React, ReactDOM} from './react.js';
2+
import {createComponent} from '@lit-labs/react';
3+
import {DemoGreeting as DemoGreetingWC} from './demo-greeting.js';
4+
5+
const DemoGreeting = createComponent(
6+
React,
7+
'demo-greeting',
8+
DemoGreetingWC
9+
);
10+
11+
const node = document.querySelector('#app');
12+
const root = ReactDOM.createRoot(node!);
13+
14+
root.render(<DemoGreeting name={'React'}></DemoGreeting>);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { html, LitElement } from 'lit';
2+
import { customElement, property } from 'lit/decorators.js';
3+
4+
@customElement('demo-greeting')
5+
export class DemoGreeting extends LitElement {
6+
@property() name = 'Somebody';
7+
8+
render() {
9+
return html`Hello, ${this.name}!`;
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!doctype html>
2+
<head>
3+
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
4+
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
5+
<script type="module" src="./app.js"></script>
6+
</head>
7+
<body>
8+
<div id="app"></div>
9+
</body>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "/samples/base.json",
3+
"order": 0,
4+
"title": "Basics",
5+
"description": "Render web components in React apps.",
6+
"section": "React",
7+
"files": {
8+
"app.tsx": {"contentType": "text/typescript-jsx"},
9+
"demo-greeting.ts": {},
10+
"react.ts": {},
11+
"index.html": {}
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// https://github.com/facebook/react/issues/10021
2+
export const React = (window as any).React;
3+
export const ReactDOM = (window as any).ReactDOM;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type {EventName} from '@lit-labs/react';
2+
3+
import {React, ReactDOM} from './react.js';
4+
import {createComponent} from '@lit-labs/react';
5+
import {SecretButton as SecretButtonWC} from './secret-button.js';
6+
7+
const {useCallback, useState} = React;
8+
9+
const SecretButton = createComponent(
10+
React,
11+
'secret-button',
12+
SecretButtonWC,
13+
{onSecretMessage: 'secret-message' as EventName<CustomEvent<string>>}
14+
);
15+
16+
export const App = () => {
17+
const [message, setMessage] = useState(
18+
`Click the button to recieve a custom event
19+
dispatched by the SecretButton component.`
20+
);
21+
22+
const onSecretMessage = useCallback(
23+
(e: CustomEvent<string>) => setMessage(e.detail),
24+
[]
25+
);
26+
27+
return (
28+
<>
29+
<SecretButton onSecretMessage={onSecretMessage}></SecretButton>
30+
<div>{message}</div>
31+
</>
32+
);
33+
};
34+
35+
const node = document.querySelector('#app');
36+
const root = ReactDOM.createRoot(node!);
37+
38+
root.render(<App></App>);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!doctype html>
2+
<head>
3+
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
4+
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
5+
<script type="module" src="./app.js"></script>
6+
</head>
7+
<body>
8+
<div id="app"></div>
9+
</body>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "/samples/base.json",
3+
"order": 2,
4+
"title": "Custom events",
5+
"description": "Listen to custom events from web components.",
6+
"section": "React",
7+
"files": {
8+
"app.tsx": {"contentType": "text/typescript-jsx"},
9+
"secret-button.ts": {},
10+
"react.ts": {},
11+
"index.html": {}
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// https://github.com/facebook/react/issues/10021
2+
export const React = (window as any).React;
3+
export const ReactDOM = (window as any).ReactDOM;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { html, LitElement } from 'lit';
2+
import { customElement } from 'lit/decorators.js';
3+
4+
const messages: string[] = [
5+
"everything is going to be okay!",
6+
"you're amazing!",
7+
"when you ask for help, everyone wins!",
8+
"there is always sunshine somewhere!",
9+
];
10+
11+
function randomBucket<T>(messages: T[]): T {
12+
const last = messages.length - 1;
13+
const prev = messages[last];
14+
15+
const target = Math.floor(Math.random() * (messages.length - 1));
16+
17+
messages[last] = messages[target];
18+
messages[target] = prev;
19+
20+
return messages[last];
21+
}
22+
23+
@customElement('secret-button')
24+
export class SecretButton extends LitElement {
25+
render() {
26+
return html`
27+
<button @click="${this.onClick}">dispatch secret message event</button>
28+
`;
29+
}
30+
31+
onClick = () => {
32+
this.dispatchEvent(
33+
new CustomEvent(
34+
'secret-message',
35+
{ detail: randomBucket(messages) },
36+
),
37+
);
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {React, ReactDOM} from './react.js';
2+
import {createComponent} from '@lit-labs/react';
3+
import {CounterButton as CounterButtonWC} from './counter-button.js';
4+
5+
const CounterButton = createComponent(
6+
React,
7+
'counter-button',
8+
CounterButtonWC,
9+
);
10+
11+
const {useCallback, useState} = React;
12+
13+
export const App = () => {
14+
const [count, setCount] = useState(0);
15+
16+
const onClick = useCallback(() => setCount(count + 1), [count]);
17+
18+
return <CounterButton onClick={onClick} count={count}></CounterButton>;
19+
};
20+
21+
const node = document.querySelector('#app');
22+
const root = ReactDOM.createRoot(node!);
23+
24+
root.render(<App></App>);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { html, LitElement } from 'lit';
2+
import { customElement, property } from 'lit/decorators.js';
3+
4+
@customElement('counter-button')
5+
export class CounterButton extends LitElement {
6+
@property({ type: Number }) count = 0;
7+
8+
render() {
9+
return html`
10+
<button>click count: ${this.count}</button>
11+
`;
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!doctype html>
2+
<head>
3+
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
4+
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
5+
<script type="module" src="./app.js"></script>
6+
</head>
7+
<body>
8+
<div id="app"></div>
9+
</body>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "/samples/base.json",
3+
"order": 1,
4+
"title": "React events",
5+
"description": "Listen to react events from web components.",
6+
"section": "React",
7+
"files": {
8+
"app.tsx": {"contentType": "text/typescript-jsx"},
9+
"counter-button.ts": {},
10+
"react.ts": {},
11+
"index.html": {}
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// https://github.com/facebook/react/issues/10021
2+
export const React = (window as any).React;
3+
export const ReactDOM = (window as any).ReactDOM;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {React, ReactDOM} from './react.js';
2+
import {createComponent} from '@lit-labs/react';
3+
import {FlyingTriangles as FlyingTrianglesWC} from './flying-triangles.js';
4+
5+
/*
6+
The <flying-triangles> component is stateful and uncontrolled.
7+
8+
A stateful component maintains state independent of React.
9+
Meaning component state must be reconciled with React state.
10+
This is usually accomplished through refs and callbacks.
11+
12+
<flying-triangles> will dispatch a 'playing-change' event when
13+
component state changes.
14+
15+
The 'playing-change' provides React an opportunity to update
16+
UI data based on the properties and attributes of
17+
a <flying-triangles> component.
18+
*/
19+
20+
const { useRef, useState, useCallback } = React;
21+
22+
const FlyingTriangles = createComponent(
23+
React,
24+
'flying-triangles',
25+
FlyingTrianglesWC,
26+
{onPlayingChange: 'playing-change'},
27+
);
28+
29+
export const App = () => {
30+
const ref = useRef(null);
31+
const [isPlaying, setIsPlaying] = useState(false);
32+
33+
// Listen for playing-change events
34+
const onPlayingChange = useCallback(() => {
35+
setIsPlaying(ref.current?.isPlaying);
36+
}, []);
37+
38+
// UI callbacks
39+
const onPlay = useCallback(() => ref.current?.play(), []);
40+
const onPause = useCallback(() => ref.current?.pause(), []);
41+
42+
return (
43+
<>
44+
<FlyingTriangles
45+
ref={ref}
46+
onPlayingChange={onPlayingChange}>
47+
</FlyingTriangles>
48+
<button disabled={isPlaying} onClick={onPlay}>
49+
play
50+
</button>
51+
<button disabled={!isPlaying} onClick={onPause}>
52+
pause
53+
</button>
54+
</>
55+
);
56+
};
57+
58+
const node = document.querySelector('#app');
59+
const root = ReactDOM.createRoot(node!);
60+
61+
root.render(<App></App>);

0 commit comments

Comments
 (0)