diff --git a/examples/tabstatesync.bundle.js b/examples/tabstatesync.bundle.js index 24b3f76..9422df1 100644 --- a/examples/tabstatesync.bundle.js +++ b/examples/tabstatesync.bundle.js @@ -1177,8 +1177,12 @@ var import_react = __toESM(require_react()); function useTabStateSync(key, initialValue, options) { const [state, setState] = (0, import_react.useState)(initialValue); const syncRef = (0, import_react.useRef)(null); + const optionsRef = (0, import_react.useRef)(options); (0, import_react.useEffect)(() => { - syncRef.current = new TabStateSync(key, options); + optionsRef.current = options; + }, [options]); + (0, import_react.useEffect)(() => { + syncRef.current = new TabStateSync(key, optionsRef.current); const handleChange = (value) => setState(value); syncRef.current.subscribe(handleChange); return () => { @@ -1187,11 +1191,11 @@ function useTabStateSync(key, initialValue, options) { syncRef.current.destroy(); } }; - }, [key, options]); - const set = (value) => { + }, [key]); + const set = (0, import_react.useCallback)((value) => { setState(value); syncRef.current?.set(value); - }; + }, []); return [state, set]; } diff --git a/package.json b/package.json index 7ca7c4c..7e001fe 100644 --- a/package.json +++ b/package.json @@ -28,16 +28,20 @@ "license": "MIT", "devDependencies": { "@playwright/test": "^1.52.0", + "@testing-library/react": "^16.3.0", "@types/jest": "^29.5.14", "@types/jsdom": "^21.1.7", "@types/react": "^19.1.4", + "@types/react-dom": "^19.0.0", "esbuild": "^0.25.4", "jsdom": "^26.1.0", "playwright": "^1.52.0", "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-test-renderer": "^19.1.0", "standard-version": "^9.5.0", "typescript": "^5.8.3", - "vitest": "^3.1.3" + "vitest": "^3.1.4" }, "directories": { "example": "examples", diff --git a/src/__tests__/useTabStateSync.test.tsx b/src/__tests__/useTabStateSync.test.tsx new file mode 100644 index 0000000..168f150 --- /dev/null +++ b/src/__tests__/useTabStateSync.test.tsx @@ -0,0 +1,65 @@ +import { describe, it, expect, vi } from 'vitest'; +import { useTabStateSync } from '../useTabStateSync'; +import { renderHook } from '@testing-library/react-hooks'; +import * as React from 'react'; +import { useState, useEffect, useRef, useCallback } from 'react'; + +// Mock de useState, useRef, useEffect e useCallback +vi.mock('react', () => { + const originalReact = vi.importActual('react'); + const mockState = ['initial-value', vi.fn()]; + const mockRef = { current: null }; + + return { + ...originalReact, + useState: vi.fn().mockReturnValue(mockState), + useRef: vi.fn().mockReturnValue(mockRef), + useEffect: vi.fn(), + useCallback: vi.fn((fn) => fn), + }; +}); + +// Mock TabStateSync +vi.mock('../TabStateSync', () => { + return { + TabStateSync: vi.fn().mockImplementation(() => ({ + set: vi.fn(), + subscribe: vi.fn(), + unsubscribe: vi.fn(), + destroy: vi.fn() + })) + }; +}); + +// Mock do renderHook +vi.mock('@testing-library/react-hooks', () => ({ + renderHook: vi.fn((callback) => ({ + result: { current: callback() }, + rerender: vi.fn() + })) +})); + +describe('useTabStateSync', () => { + it('should initialize with the provided initial value', () => { + // Configurações dos mocks + const initialValue = 'test-value'; + (useState as any).mockReturnValue([initialValue, vi.fn()]); + + // Chamada do hook + const result = useTabStateSync('test-key', initialValue); + + // Verificação + expect(result[0]).toBe(initialValue); + }); + + it('should memoize the set function with useCallback', () => { + // Configurando spy no useCallback + const callbackSpy = vi.spyOn(React, 'useCallback'); + + // Chamada do hook + useTabStateSync('test-key', 'value'); + + // Verificação + expect(callbackSpy).toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/src/useTabStateSync.ts b/src/useTabStateSync.ts index bb6f550..91952e9 100644 --- a/src/useTabStateSync.ts +++ b/src/useTabStateSync.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, useCallback } from 'react'; import { TabStateSync, TabStateSyncOptions } from './TabStateSync'; /** @@ -15,9 +15,15 @@ export function useTabStateSync( ): [T, (v: T) => void] { const [state, setState] = useState(initialValue); const syncRef = useRef | null>(null); + const optionsRef = useRef(options); + + // Update options ref when options change + useEffect(() => { + optionsRef.current = options; + }, [options]); useEffect(() => { - syncRef.current = new TabStateSync(key, options); + syncRef.current = new TabStateSync(key, optionsRef.current); const handleChange = (value: T) => setState(value); syncRef.current.subscribe(handleChange); return () => { @@ -26,12 +32,13 @@ export function useTabStateSync( syncRef.current.destroy(); } }; - }, [key, options]); + }, [key]); // Remove options from dependencies - const set = (value: T) => { + // Memoize the set function to prevent unnecessary re-renders + const set = useCallback((value: T) => { setState(value); syncRef.current?.set(value); - }; + }, []); return [state, set]; } \ No newline at end of file