mirror of
https://github.com/rive-app/rive-react.git
synced 2026-03-13 08:22:30 +08:00
feat: create useRiveFile hook
This commit is contained in:
38
src/hooks/useRiveFile.ts
Normal file
38
src/hooks/useRiveFile.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import type { UseRiveFileParameters } from '../types';
|
||||
import { RiveFile } from '@rive-app/canvas';
|
||||
|
||||
/**
|
||||
* Custom hook for initializing and managing a RiveFile instance within a component.
|
||||
* It sets up a RiveFile based on provided source parameters (URL or ArrayBuffer) and ensures
|
||||
* proper cleanup to avoid memory leaks when the component unmounts or inputs change.
|
||||
*
|
||||
* @param params - Object containing parameters accepted by the Rive file in the rive-js runtime,
|
||||
*
|
||||
* @returns {RiveFile} Contains the active RiveFile instance (`riveFile`).
|
||||
*/
|
||||
function useRiveFile(params: UseRiveFileParameters) {
|
||||
const [riveFile, setRiveFile] = useState<RiveFile | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let file: RiveFile | null = null;
|
||||
|
||||
const loadRiveFile = async () => {
|
||||
file = new RiveFile(params);
|
||||
setRiveFile(file);
|
||||
};
|
||||
|
||||
loadRiveFile();
|
||||
|
||||
return () => {
|
||||
if (file) {
|
||||
file.cleanup();
|
||||
}
|
||||
};
|
||||
}, [params.src, params.buffer]);
|
||||
|
||||
|
||||
return { riveFile };
|
||||
}
|
||||
|
||||
export default useRiveFile;
|
||||
@@ -2,8 +2,9 @@ import Rive, { RiveProps } from './components/Rive';
|
||||
import useRive from './hooks/useRive';
|
||||
import useStateMachineInput from './hooks/useStateMachineInput';
|
||||
import useResizeCanvas from './hooks/useResizeCanvas';
|
||||
import useRiveFile from './hooks/useRiveFile';
|
||||
|
||||
export default Rive;
|
||||
export { useRive, useStateMachineInput, useResizeCanvas, RiveProps };
|
||||
export { RiveState, UseRiveParameters, UseRiveOptions } from './types';
|
||||
export { useRive, useStateMachineInput, useResizeCanvas, useRiveFile , RiveProps };
|
||||
export { RiveState, UseRiveParameters, UseRiveFileParameters, UseRiveOptions } from './types';
|
||||
export * from '@rive-app/canvas';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { RefCallback, ComponentProps } from 'react';
|
||||
import { Rive, RiveParameters } from '@rive-app/canvas';
|
||||
import { Rive, RiveParameters, RiveFileParameters } from '@rive-app/canvas';
|
||||
|
||||
export type UseRiveParameters = Partial<Omit<RiveParameters, 'canvas'>> | null;
|
||||
|
||||
@@ -36,3 +36,6 @@ export type RiveState = {
|
||||
rive: Rive | null;
|
||||
RiveComponent: (props: ComponentProps<'canvas'>) => JSX.Element;
|
||||
};
|
||||
|
||||
|
||||
export type UseRiveFileParameters = RiveFileParameters;
|
||||
95
test/useRiveFile.test.tsx
Normal file
95
test/useRiveFile.test.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { mocked } from 'jest-mock';
|
||||
|
||||
import useRiveFile from '../src/hooks/useRiveFile';
|
||||
import { RiveFile } from '@rive-app/canvas';
|
||||
|
||||
jest.mock('@rive-app/canvas', () => ({
|
||||
RiveFile: jest.fn().mockImplementation(() => ({
|
||||
cleanup: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
|
||||
describe('useRiveFile', () => {
|
||||
beforeEach(() => {
|
||||
mocked(RiveFile).mockClear();
|
||||
});
|
||||
|
||||
it('initializes RiveFile with provided parameters', async () => {
|
||||
const params = {
|
||||
src: 'file-src',
|
||||
enableRiveAssetCDN: false
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useRiveFile(params));
|
||||
|
||||
expect(RiveFile).toHaveBeenCalledWith(params);
|
||||
expect(result.current.riveFile).toBeDefined();
|
||||
});
|
||||
|
||||
it('cleans up RiveFile on unmount', async () => {
|
||||
const params = {
|
||||
src: 'file-src',
|
||||
enableRiveAssetCDN: false
|
||||
};
|
||||
|
||||
const { result, unmount } = renderHook(() => useRiveFile(params));
|
||||
|
||||
const riveInstance = result.current.riveFile;
|
||||
expect(riveInstance).toBeDefined();
|
||||
|
||||
unmount();
|
||||
|
||||
expect(riveInstance?.cleanup).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not reinitialize RiveFile if params do not change', async () => {
|
||||
const params = {
|
||||
src: 'file-src',
|
||||
enableRiveAssetCDN: false
|
||||
};
|
||||
|
||||
const { rerender } = renderHook(() => useRiveFile(params));
|
||||
|
||||
rerender();
|
||||
|
||||
expect(RiveFile).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reinitializes RiveFile if src changes', async () => {
|
||||
let params = {
|
||||
src: 'file-src',
|
||||
enableRiveAssetCDN: false
|
||||
};
|
||||
|
||||
const { rerender } = renderHook(() => useRiveFile(params));
|
||||
|
||||
params = {
|
||||
src: 'new-file-src',
|
||||
enableRiveAssetCDN: false
|
||||
};
|
||||
|
||||
rerender();
|
||||
|
||||
expect(RiveFile).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('reinitializes RiveFile if buffer changes', async () => {
|
||||
let params = {
|
||||
buffer: new ArrayBuffer(10),
|
||||
enableRiveAssetCDN: false
|
||||
};
|
||||
|
||||
const { rerender } = renderHook(() => useRiveFile(params));
|
||||
|
||||
params = {
|
||||
buffer: new ArrayBuffer(20),
|
||||
enableRiveAssetCDN: false
|
||||
};
|
||||
|
||||
rerender();
|
||||
|
||||
expect(RiveFile).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user