mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: integrate deferred loading for saving state in DotEnvFileEditor (#7463)
This commit is contained in:
@@ -4,6 +4,7 @@ import { uuid } from 'utils/common';
|
||||
import { useFormik } from 'formik';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
import toast from 'react-hot-toast';
|
||||
import useDeferredLoading from 'hooks/useDeferredLoading';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import DotEnvTableView from './DotEnvTableView';
|
||||
@@ -31,6 +32,7 @@ const DotEnvFileEditor = ({
|
||||
const [rawValue, setRawValue] = useState(initialRawValue);
|
||||
const [prevViewMode, setPrevViewMode] = useState(viewMode);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const showSaving = useDeferredLoading(isSaving, 200);
|
||||
|
||||
const formikRef = useRef(null);
|
||||
|
||||
@@ -311,7 +313,7 @@ const DotEnvFileEditor = ({
|
||||
onChange={handleRawChange}
|
||||
onSave={handleSaveRaw}
|
||||
onReset={handleReset}
|
||||
isSaving={isSaving}
|
||||
isSaving={showSaving}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
);
|
||||
@@ -335,7 +337,7 @@ const DotEnvFileEditor = ({
|
||||
onRemoveVar={handleRemoveVar}
|
||||
onSave={handleSave}
|
||||
onReset={handleReset}
|
||||
isSaving={isSaving}
|
||||
isSaving={showSaving}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
39
packages/bruno-app/src/hooks/useDeferredLoading/index.js
Normal file
39
packages/bruno-app/src/hooks/useDeferredLoading/index.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* A hook that defers showing loading state until a minimum delay has passed.
|
||||
* This prevents flickering UI for fast operations.
|
||||
*
|
||||
* @param {boolean} isLoading - The actual loading state
|
||||
* @param {number} delay - Minimum time (ms) before showing loading state (default: 200ms)
|
||||
* @returns {boolean} - The deferred loading state
|
||||
*/
|
||||
function useDeferredLoading(isLoading, delay = 200) {
|
||||
const [showLoading, setShowLoading] = useState(false);
|
||||
const timerRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
timerRef.current = setTimeout(() => {
|
||||
setShowLoading(true);
|
||||
}, delay);
|
||||
} else {
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
setShowLoading(false);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [isLoading, delay]);
|
||||
|
||||
return showLoading;
|
||||
}
|
||||
|
||||
export default useDeferredLoading;
|
||||
109
packages/bruno-app/src/hooks/useDeferredLoading/index.spec.js
Normal file
109
packages/bruno-app/src/hooks/useDeferredLoading/index.spec.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const { describe, it, expect, beforeEach, afterEach } = require('@jest/globals');
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import useDeferredLoading from './index';
|
||||
|
||||
describe('useDeferredLoading', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should return false initially when isLoading is false', () => {
|
||||
const { result } = renderHook(() => useDeferredLoading(false));
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
|
||||
it('should not show loading immediately when isLoading becomes true', () => {
|
||||
const { result } = renderHook(() => useDeferredLoading(true, 200));
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
|
||||
it('should show loading after the delay has passed', () => {
|
||||
const { result } = renderHook(() => useDeferredLoading(true, 200));
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(200);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
it('should not show loading if isLoading becomes false before delay', () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ isLoading }) => useDeferredLoading(isLoading, 200),
|
||||
{ initialProps: { isLoading: true } }
|
||||
);
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(100);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
|
||||
rerender({ isLoading: false });
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(200);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
|
||||
it('should reset to false immediately when isLoading becomes false', () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ isLoading }) => useDeferredLoading(isLoading, 200),
|
||||
{ initialProps: { isLoading: true } }
|
||||
);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(200);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
|
||||
rerender({ isLoading: false });
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
|
||||
it('should use default delay of 200ms', () => {
|
||||
const { result } = renderHook(() => useDeferredLoading(true));
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(199);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
it('should respect custom delay values', () => {
|
||||
const { result } = renderHook(() => useDeferredLoading(true, 500));
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(400);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(100);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user