Skip to content

Commit e7c073e

Browse files
authored
Merge pull request #78 from crashmax-dev/use-imperative-handle
feat: added `useImperativeHandle` for `react`, `preact` packages
2 parents c409d81 + 074a6c0 commit e7c073e

File tree

7 files changed

+176
-100
lines changed

7 files changed

+176
-100
lines changed

README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,6 @@ fireworks.start()
106106
npm install @fireworks-js/react
107107
```
108108

109-
> **Warning**\
110-
> StrictMode in React 18 will lead useEffect called twice.\
111-
> Fireworks speed will be broken (in development mode).
112-
113-
114109
#### [`@fireworks-js/preact`](https://github.com/crashmax-dev/fireworks-js/tree/master/examples/with-preact)
115110

116111
```sh

examples/with-preact/src/App.tsx

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1-
import { useState } from 'preact/hooks'
1+
import { useRef } from 'preact/hooks'
22
import { Fireworks } from '@fireworks-js/preact'
3+
import type { FireworksHandlers } from '@fireworks-js/preact'
34

45
export function App() {
5-
const [enabled, setEnabled] = useState(true)
6+
const ref = useRef<FireworksHandlers>(null)
7+
8+
const toggle = () => {
9+
ref.current!.isRunning ? ref.current!.stop() : ref.current!.start()
10+
}
611

712
return (
813
<>
9-
<button
10-
onClick={() => setEnabled(!enabled)}
11-
style={{ position: 'absolute', zIndex: 1 }}
12-
>
13-
{enabled ? 'Enabled' : 'Disabled'}
14-
</button>
15-
{enabled && (
16-
<Fireworks
17-
options={{ opacity: 0.5 }}
18-
style={{
19-
top: 0,
20-
left: 0,
21-
width: '100%',
22-
height: '100%',
23-
position: 'fixed',
24-
background: '#000'
25-
}}
26-
/>
27-
)}
14+
<div style={{ position: 'absolute', zIndex: 1 }}>
15+
<button onClick={() => toggle()}>Toggle</button>
16+
<button onClick={() => ref.current!.clear()}>Clear</button>
17+
</div>
18+
<Fireworks
19+
ref={ref}
20+
options={{ opacity: 0.5 }}
21+
style={{
22+
top: 0,
23+
left: 0,
24+
width: '100%',
25+
height: '100%',
26+
position: 'fixed',
27+
background: '#000'
28+
}}
29+
/>
2830
</>
2931
)
3032
}

examples/with-preact/src/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { render } from 'preact'
2+
import React from 'preact/compat'
23
import { App } from './App'
34

45
const app = document.getElementById('app')!
5-
render(<App />, app)
6+
render(
7+
<React.StrictMode>
8+
<App />
9+
</React.StrictMode>,
10+
app
11+
)

examples/with-react/src/App.tsx

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1-
import { useState } from 'react'
1+
import { useRef } from 'react'
22
import { Fireworks } from '@fireworks-js/react'
3+
import type { FireworksHandlers } from '@fireworks-js/react'
34

45
export function App() {
5-
const [enabled, setEnabled] = useState(true)
6+
const ref = useRef<FireworksHandlers>(null)
7+
8+
const toggle = () => {
9+
ref.current!.isRunning ? ref.current!.stop() : ref.current!.start()
10+
}
611

712
return (
813
<>
9-
<button
10-
onClick={() => setEnabled(!enabled)}
11-
style={{ position: 'absolute', zIndex: 1 }}
12-
>
13-
{enabled ? 'Enabled' : 'Disabled'}
14-
</button>
15-
{enabled && (
16-
<Fireworks
17-
options={{ opacity: 0.5 }}
18-
style={{
19-
top: 0,
20-
left: 0,
21-
width: '100%',
22-
height: '100%',
23-
position: 'fixed',
24-
background: '#000'
25-
}}
26-
/>
27-
)}
14+
<div style={{ position: 'absolute', zIndex: 1 }}>
15+
<button onClick={() => toggle()}>Toggle</button>
16+
<button onClick={() => ref.current!.clear()}>Clear</button>
17+
</div>
18+
<Fireworks
19+
ref={ref}
20+
options={{ opacity: 0.5 }}
21+
style={{
22+
top: 0,
23+
left: 0,
24+
width: '100%',
25+
height: '100%',
26+
position: 'fixed',
27+
background: '#000'
28+
}}
29+
/>
2830
</>
2931
)
3032
}

examples/with-react/src/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import React from 'react'
12
import { createRoot } from 'react-dom/client'
23
import { App } from './App'
34

45
const app = document.querySelector('#app')!
5-
createRoot(app).render(<App />)
6+
createRoot(app).render(
7+
<React.StrictMode>
8+
<App />
9+
</React.StrictMode>
10+
)

packages/preact/src/index.tsx

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,71 @@
11
import { Fireworks as FireworksJs } from 'fireworks-js'
22
import type { FireworksOptions } from 'fireworks-js'
3+
import type { ComponentChildren } from 'preact'
34
import React from 'preact/compat'
4-
import { useEffect, useRef } from 'preact/hooks'
5+
import { useEffect, useImperativeHandle, useRef } from 'preact/hooks'
56

6-
type FireworksProps = {
7+
interface FireworksProps extends React.HTMLAttributes<HTMLDivElement> {
8+
children?: ComponentChildren
79
options?: FireworksOptions
8-
style?: React.CSSProperties
910
}
1011

11-
const Fireworks: React.FC<FireworksProps> = ({ options, style, children }) => {
12-
const container = useRef<HTMLDivElement>(null)
13-
const fireworks = useRef<FireworksJs | null>(null)
14-
15-
useEffect(() => {
16-
fireworks.current = new FireworksJs(container.current!, options)
17-
fireworks.current.start()
18-
19-
return () => {
20-
fireworks.current!.stop()
21-
}
22-
}, [])
23-
24-
return (
25-
<div
26-
ref={container}
27-
style={style}
28-
>
29-
{children}
30-
</div>
31-
)
12+
interface FireworksHandlers
13+
extends Pick<
14+
FireworksJs,
15+
'isRunning' | 'start' | 'pause' | 'clear' | 'updateOptions' | 'updateSize'
16+
> {
17+
stop(): void
3218
}
3319

20+
const Fireworks = React.forwardRef<FireworksHandlers, FireworksProps>(
21+
({ children, options, ...rest }, ref) => {
22+
const container = useRef<HTMLDivElement>(null)
23+
const fireworks = useRef<FireworksJs | null>(null)
24+
25+
useImperativeHandle(ref, () => ({
26+
get isRunning() {
27+
return fireworks.current!.isRunning
28+
},
29+
start() {
30+
fireworks.current!.start()
31+
},
32+
stop() {
33+
fireworks.current!.stop()
34+
},
35+
pause() {
36+
fireworks.current!.pause()
37+
},
38+
clear() {
39+
fireworks.current!.clear()
40+
},
41+
updateOptions(options) {
42+
fireworks.current!.updateOptions(options)
43+
},
44+
updateSize(size) {
45+
fireworks.current!.updateSize(size)
46+
}
47+
}))
48+
49+
useEffect(() => {
50+
fireworks.current = new FireworksJs(container.current!, options)
51+
fireworks.current.start()
52+
53+
return () => {
54+
fireworks.current!.stop(true)
55+
}
56+
}, [])
57+
58+
return (
59+
<div
60+
ref={container}
61+
{...rest}
62+
>
63+
{children}
64+
</div>
65+
)
66+
}
67+
)
68+
3469
export { Fireworks }
35-
export type { FireworksOptions }
3670
export default Fireworks
71+
export type { FireworksProps, FireworksHandlers, FireworksOptions }

packages/react/src/index.tsx

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,69 @@
11
import { Fireworks as FireworksJs } from 'fireworks-js'
22
import type { FireworksOptions } from 'fireworks-js'
3-
import React, { useEffect, useRef } from 'react'
3+
import React, { useEffect, useImperativeHandle, useRef } from 'react'
44

5-
type FireworksProps = {
5+
interface FireworksProps extends React.HTMLAttributes<HTMLDivElement> {
66
children?: React.ReactNode
77
options?: FireworksOptions
8-
style?: React.CSSProperties
98
}
109

11-
const Fireworks = ({ children, options, style }: FireworksProps) => {
12-
const container = useRef<HTMLDivElement>(null)
13-
const fireworks = useRef<FireworksJs | null>(null)
10+
interface FireworksHandlers
11+
extends Pick<
12+
FireworksJs,
13+
'isRunning' | 'start' | 'pause' | 'clear' | 'updateOptions' | 'updateSize'
14+
> {
15+
stop(): void
16+
}
17+
18+
const Fireworks = React.forwardRef<FireworksHandlers, FireworksProps>(
19+
({ children, options, ...rest }, ref) => {
20+
const container = useRef<HTMLDivElement>(null)
21+
const fireworks = useRef<FireworksJs | null>(null)
22+
23+
useImperativeHandle(ref, () => ({
24+
get isRunning() {
25+
return fireworks.current!.isRunning
26+
},
27+
start() {
28+
fireworks.current!.start()
29+
},
30+
stop() {
31+
fireworks.current!.stop()
32+
},
33+
pause() {
34+
fireworks.current!.pause()
35+
},
36+
clear() {
37+
fireworks.current!.clear()
38+
},
39+
updateOptions(options) {
40+
fireworks.current!.updateOptions(options)
41+
},
42+
updateSize(size) {
43+
fireworks.current!.updateSize(size)
44+
}
45+
}))
1446

15-
useEffect(() => {
16-
if (!fireworks.current) {
47+
useEffect(() => {
1748
fireworks.current = new FireworksJs(container.current!, options)
18-
}
19-
fireworks.current.start()
20-
21-
return () => {
22-
fireworks.current!.stop()
23-
}
24-
}, [])
25-
26-
return (
27-
<div
28-
ref={container}
29-
style={style}
30-
>
31-
{children}
32-
</div>
33-
)
34-
}
49+
fireworks.current.start()
50+
51+
return () => {
52+
fireworks.current!.stop(true)
53+
}
54+
}, [])
55+
56+
return (
57+
<div
58+
ref={container}
59+
{...rest}
60+
>
61+
{children}
62+
</div>
63+
)
64+
}
65+
)
3566

3667
export { Fireworks }
3768
export default Fireworks
38-
export type { FireworksOptions }
69+
export type { FireworksProps, FireworksHandlers, FireworksOptions }

0 commit comments

Comments
 (0)