-
Notifications
You must be signed in to change notification settings - Fork 52
Feat: add useStateMachineInputs hook #310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
eric-jy-park
wants to merge
8
commits into
rive-app:main
Choose a base branch
from
eric-jy-park:bokdol11859/use-state-machine-inputs
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+239
−2
Open
Changes from 7 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
ec8c4eb
Feat: add useStateMachineInputs hook
eric-jy-park fa9e7de
feat: add useStateMachineInputs to exports
eric-jy-park 2a26db1
feat: update useStateMachineInputs hook with optimizations
eric-jy-park fc0c547
refactor: change state type from Map to Array to remove unnecessary t…
eric-jy-park f25dc49
fix: fix "Maximum update depth exceeded" error
eric-jy-park 79ca848
test: add test cases for useStateMachineInputs hook
eric-jy-park 0fc363e
refactor: move syncInputs function inside useEffect
eric-jy-park 9a7b3ee
chore: remove unused import
eric-jy-park File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import { EventType, StateMachineInput, Rive } from '@rive-app/canvas'; | ||
| import { useCallback, useEffect, useState } from 'react'; | ||
|
|
||
| /** | ||
| * Custom hook for fetching multiple stateMachine inputs from a rive file. | ||
| * Particularly useful for fetching multiple inputs from a variable number of input names. | ||
| * | ||
| * @param rive - Rive instance | ||
| * @param stateMachineName - Name of the state machine | ||
| * @param inputNames - Name and initial value of the inputs | ||
| * @returns StateMachineInput[] | ||
| */ | ||
| export default function useStateMachineInputs( | ||
| rive: Rive | null, | ||
| stateMachineName?: string, | ||
| inputNames?: { | ||
| name: string; | ||
| initialValue?: number | boolean; | ||
| }[] | ||
| ) { | ||
| const [inputs, setInputs] = useState<StateMachineInput[]>([]); | ||
|
|
||
| useEffect(() => { | ||
| const syncInputs = () => { | ||
| if (!rive || !stateMachineName || !inputNames) return; | ||
|
|
||
| const riveInputs = rive.stateMachineInputs(stateMachineName); | ||
| if (!riveInputs) return; | ||
|
|
||
| // To optimize lookup time from O(n) to O(1) in the following loop | ||
| const riveInputLookup = new Map<string, StateMachineInput>( | ||
| riveInputs.map(input => [input.name, input]) | ||
| ); | ||
|
|
||
| setInputs(() => { | ||
| // Iterate over inputNames instead of riveInputs to preserve array order | ||
| return inputNames | ||
| .filter(inputName => riveInputLookup.has(inputName.name)) | ||
| .map(inputName => { | ||
| const riveInput = riveInputLookup.get(inputName.name)!; | ||
|
|
||
| if (inputName.initialValue !== undefined) { | ||
| riveInput.value = inputName.initialValue; | ||
| } | ||
|
|
||
| return riveInput; | ||
| }); | ||
| }); | ||
| }; | ||
|
|
||
| syncInputs(); | ||
| if (rive) { | ||
| rive.on(EventType.Load, syncInputs); | ||
|
|
||
| return () => { | ||
| rive.off(EventType.Load, syncInputs); | ||
| }; | ||
| } | ||
| }, [rive]); | ||
|
|
||
| return inputs; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| import { mocked } from 'jest-mock'; | ||
| import { renderHook } from '@testing-library/react-hooks'; | ||
|
|
||
| import useStateMachineInputs from '../src/hooks/useStateMachineInputs'; | ||
| import { Rive, StateMachineInput } from '@rive-app/canvas'; | ||
|
|
||
| jest.mock('@rive-app/canvas', () => ({ | ||
| Rive: jest.fn().mockImplementation(() => ({ | ||
| on: jest.fn(), | ||
| off: jest.fn(), | ||
| stop: jest.fn(), | ||
| stateMachineInputs: jest.fn(), | ||
| })), | ||
| Layout: jest.fn(), | ||
| Fit: { | ||
| Cover: 'cover', | ||
| }, | ||
| Alignment: { | ||
| Center: 'center', | ||
| }, | ||
| EventType: { | ||
| Load: 'load', | ||
| }, | ||
| StateMachineInputType: { | ||
| Number: 1, | ||
| Boolean: 2, | ||
| Trigger: 3, | ||
| }, | ||
| })); | ||
|
|
||
| function getRiveMock({ | ||
| smiInputs, | ||
| }: { | ||
| smiInputs?: null | StateMachineInput[]; | ||
| } = {}) { | ||
| const riveMock = new Rive({ | ||
| canvas: undefined as unknown as HTMLCanvasElement, | ||
| }); | ||
| if (smiInputs) { | ||
| riveMock.stateMachineInputs = jest.fn().mockReturnValue(smiInputs); | ||
| } | ||
|
|
||
| return riveMock; | ||
| } | ||
|
|
||
| describe('useStateMachineInputs', () => { | ||
| it('returns empty array if there is null rive object passed', () => { | ||
| const { result } = renderHook(() => useStateMachineInputs(null)); | ||
| expect(result.current).toEqual([]); | ||
| }); | ||
|
|
||
| it('returns empty array if there is no state machine name', () => { | ||
| const riveMock = getRiveMock(); | ||
| mocked(Rive).mockImplementation(() => riveMock); | ||
|
|
||
| const { result } = renderHook(() => | ||
| useStateMachineInputs(riveMock, '', [{ name: 'testInput' }]) | ||
| ); | ||
| expect(result.current).toEqual([]); | ||
| }); | ||
|
|
||
| it('returns empty array if there are no input names provided', () => { | ||
| const riveMock = getRiveMock(); | ||
| mocked(Rive).mockImplementation(() => riveMock); | ||
|
|
||
| const { result } = renderHook(() => | ||
| useStateMachineInputs(riveMock, 'smName', []) | ||
| ); | ||
| expect(result.current).toEqual([]); | ||
| }); | ||
|
|
||
| it('returns empty array if there are no inputs for the state machine', () => { | ||
| const riveMock = getRiveMock({ smiInputs: [] }); | ||
| mocked(Rive).mockImplementation(() => riveMock); | ||
|
|
||
| const { result } = renderHook(() => | ||
| useStateMachineInputs(riveMock, 'smName', [{ name: 'testInput' }]) | ||
| ); | ||
| expect(result.current).toEqual([]); | ||
| }); | ||
|
|
||
| it('returns only the inputs that exist in the state machine', () => { | ||
| const smInputs = [ | ||
| { name: 'input1' } as StateMachineInput, | ||
| { name: 'input2' } as StateMachineInput, | ||
| ]; | ||
| const riveMock = getRiveMock({ smiInputs: smInputs }); | ||
| mocked(Rive).mockImplementation(() => riveMock); | ||
|
|
||
| const { result } = renderHook(() => | ||
| useStateMachineInputs(riveMock, 'smName', [ | ||
| { name: 'input1' }, | ||
| { name: 'nonexistent' }, | ||
| { name: 'input2' }, | ||
| ]) | ||
| ); | ||
| expect(result.current).toEqual([smInputs[0], smInputs[1]]); | ||
| }); | ||
|
|
||
| it('sets initial values on the inputs when provided', () => { | ||
| const smInputs = [ | ||
| { name: 'boolInput', value: false } as StateMachineInput, | ||
| { name: 'numInput', value: 0 } as StateMachineInput, | ||
| ]; | ||
| const riveMock = getRiveMock({ smiInputs: smInputs }); | ||
| mocked(Rive).mockImplementation(() => riveMock); | ||
|
|
||
| const { result } = renderHook(() => | ||
| useStateMachineInputs(riveMock, 'smName', [ | ||
| { name: 'boolInput', initialValue: true }, | ||
| { name: 'numInput', initialValue: 42 }, | ||
| ]) | ||
| ); | ||
|
|
||
| expect(result.current[0].value).toBe(true); | ||
| expect(result.current[1].value).toBe(42); | ||
| }); | ||
|
|
||
| it('does not set initial values if not provided', () => { | ||
| const smInputs = [ | ||
| { name: 'boolInput', value: false } as StateMachineInput, | ||
| { name: 'numInput', value: 0 } as StateMachineInput, | ||
| ]; | ||
| const riveMock = getRiveMock({ smiInputs: smInputs }); | ||
| mocked(Rive).mockImplementation(() => riveMock); | ||
|
|
||
| const { result } = renderHook(() => | ||
| useStateMachineInputs(riveMock, 'smName', [ | ||
| { name: 'boolInput' }, | ||
| { name: 'numInput' }, | ||
| ]) | ||
| ); | ||
|
|
||
| expect(result.current[0].value).toBe(false); | ||
| expect(result.current[1].value).toBe(0); | ||
| }); | ||
|
|
||
| it('preserves the order of inputs as specified in inputNames', () => { | ||
| const smInputs = [ | ||
| { name: 'input1' } as StateMachineInput, | ||
| { name: 'input2' } as StateMachineInput, | ||
| { name: 'input3' } as StateMachineInput, | ||
| ]; | ||
| const riveMock = getRiveMock({ smiInputs: smInputs }); | ||
| mocked(Rive).mockImplementation(() => riveMock); | ||
|
|
||
| const { result } = renderHook(() => | ||
| useStateMachineInputs(riveMock, 'smName', [ | ||
| { name: 'input3' }, | ||
| { name: 'input1' }, | ||
| { name: 'input2' }, | ||
| ]) | ||
| ); | ||
|
|
||
| expect(result.current.map((input) => input.name)).toEqual([ | ||
| 'input3', | ||
| 'input1', | ||
| 'input2', | ||
| ]); | ||
| }); | ||
| }); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useCallbackisn't being used.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it!