refactor: optimize codebase for React 19 compiler (v1.2.6)
React 19 compiler auto-optimizes memoization, making manual hooks unnecessary. Changes: - Remove ~60 useCallback/useMemo across 16 files - Remove React.memo from nav-button and return-button - Simplify hydration with useSyncExternalStore (privacy-provider) - Add CHANGELOG.md for version tracking No functional changes - internal optimization only. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { buttonVariants } from "@/components/ui/button";
|
||||
import {
|
||||
formatLocaleValue,
|
||||
@@ -26,9 +26,9 @@ export function useCalculatorState() {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const resetCopiedTimeoutRef = useRef<number | undefined>(undefined);
|
||||
|
||||
const currentValue = useMemo(() => Number(display), [display]);
|
||||
const currentValue = Number(display);
|
||||
|
||||
const resultText = useMemo(() => {
|
||||
const resultText = (() => {
|
||||
if (display === "Erro") {
|
||||
return null;
|
||||
}
|
||||
@@ -39,49 +39,46 @@ export function useCalculatorState() {
|
||||
}
|
||||
|
||||
return formatLocaleValue(normalized);
|
||||
}, [currentValue, display]);
|
||||
})();
|
||||
|
||||
const reset = useCallback(() => {
|
||||
const reset = () => {
|
||||
setDisplay("0");
|
||||
setAccumulator(null);
|
||||
setOperator(null);
|
||||
setOverwrite(false);
|
||||
setHistory(null);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const inputDigit = useCallback(
|
||||
(digit: string) => {
|
||||
// Check conditions before state updates
|
||||
const shouldReset = overwrite || display === "Erro";
|
||||
const inputDigit = (digit: string) => {
|
||||
// Check conditions before state updates
|
||||
const shouldReset = overwrite || display === "Erro";
|
||||
|
||||
setDisplay((prev) => {
|
||||
if (shouldReset) {
|
||||
return digit;
|
||||
}
|
||||
|
||||
if (prev === "0") {
|
||||
return digit;
|
||||
}
|
||||
|
||||
// Limitar a 10 dígitos (excluindo sinal negativo e ponto decimal)
|
||||
const digitCount = prev.replace(/[-.]/g, "").length;
|
||||
if (digitCount >= 10) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return `${prev}${digit}`;
|
||||
});
|
||||
|
||||
// Update related states after display update
|
||||
setDisplay((prev) => {
|
||||
if (shouldReset) {
|
||||
setOverwrite(false);
|
||||
setHistory(null);
|
||||
return digit;
|
||||
}
|
||||
},
|
||||
[overwrite, display],
|
||||
);
|
||||
|
||||
const inputDecimal = useCallback(() => {
|
||||
if (prev === "0") {
|
||||
return digit;
|
||||
}
|
||||
|
||||
// Limitar a 10 dígitos (excluindo sinal negativo e ponto decimal)
|
||||
const digitCount = prev.replace(/[-.]/g, "").length;
|
||||
if (digitCount >= 10) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return `${prev}${digit}`;
|
||||
});
|
||||
|
||||
// Update related states after display update
|
||||
if (shouldReset) {
|
||||
setOverwrite(false);
|
||||
setHistory(null);
|
||||
}
|
||||
};
|
||||
|
||||
const inputDecimal = () => {
|
||||
// Check conditions before state updates
|
||||
const shouldReset = overwrite || display === "Erro";
|
||||
|
||||
@@ -108,40 +105,37 @@ export function useCalculatorState() {
|
||||
setOverwrite(false);
|
||||
setHistory(null);
|
||||
}
|
||||
}, [overwrite, display]);
|
||||
};
|
||||
|
||||
const setNextOperator = useCallback(
|
||||
(nextOperator: Operator) => {
|
||||
if (display === "Erro") {
|
||||
reset();
|
||||
const setNextOperator = (nextOperator: Operator) => {
|
||||
if (display === "Erro") {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const value = currentValue;
|
||||
|
||||
if (accumulator === null || operator === null || overwrite) {
|
||||
setAccumulator(value);
|
||||
} else {
|
||||
const result = performOperation(accumulator, value, operator);
|
||||
const formatted = formatNumber(result);
|
||||
setAccumulator(Number.isFinite(result) ? result : null);
|
||||
setDisplay(formatted);
|
||||
if (!Number.isFinite(result)) {
|
||||
setOperator(null);
|
||||
setOverwrite(true);
|
||||
setHistory(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const value = currentValue;
|
||||
setOperator(nextOperator);
|
||||
setOverwrite(true);
|
||||
setHistory(null);
|
||||
};
|
||||
|
||||
if (accumulator === null || operator === null || overwrite) {
|
||||
setAccumulator(value);
|
||||
} else {
|
||||
const result = performOperation(accumulator, value, operator);
|
||||
const formatted = formatNumber(result);
|
||||
setAccumulator(Number.isFinite(result) ? result : null);
|
||||
setDisplay(formatted);
|
||||
if (!Number.isFinite(result)) {
|
||||
setOperator(null);
|
||||
setOverwrite(true);
|
||||
setHistory(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setOperator(nextOperator);
|
||||
setOverwrite(true);
|
||||
setHistory(null);
|
||||
},
|
||||
[accumulator, currentValue, display, operator, overwrite, reset],
|
||||
);
|
||||
|
||||
const evaluate = useCallback(() => {
|
||||
const evaluate = () => {
|
||||
if (operator === null || accumulator === null || display === "Erro") {
|
||||
return;
|
||||
}
|
||||
@@ -161,9 +155,9 @@ export function useCalculatorState() {
|
||||
setOperator(null);
|
||||
setOverwrite(true);
|
||||
setHistory(operation);
|
||||
}, [accumulator, currentValue, display, operator]);
|
||||
};
|
||||
|
||||
const toggleSign = useCallback(() => {
|
||||
const toggleSign = () => {
|
||||
setDisplay((prev) => {
|
||||
if (prev === "Erro") {
|
||||
return prev;
|
||||
@@ -177,9 +171,9 @@ export function useCalculatorState() {
|
||||
setOverwrite(false);
|
||||
setHistory(null);
|
||||
}
|
||||
}, [overwrite]);
|
||||
};
|
||||
|
||||
const deleteLastDigit = useCallback(() => {
|
||||
const deleteLastDigit = () => {
|
||||
setHistory(null);
|
||||
|
||||
// Check conditions before state updates
|
||||
@@ -209,9 +203,9 @@ export function useCalculatorState() {
|
||||
} else if (overwrite) {
|
||||
setOverwrite(false);
|
||||
}
|
||||
}, [overwrite, display]);
|
||||
};
|
||||
|
||||
const applyPercent = useCallback(() => {
|
||||
const applyPercent = () => {
|
||||
setDisplay((prev) => {
|
||||
if (prev === "Erro") {
|
||||
return prev;
|
||||
@@ -221,9 +215,9 @@ export function useCalculatorState() {
|
||||
});
|
||||
setOverwrite(true);
|
||||
setHistory(null);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const expression = useMemo(() => {
|
||||
const expression = (() => {
|
||||
if (display === "Erro") {
|
||||
return "Erro";
|
||||
}
|
||||
@@ -240,68 +234,57 @@ export function useCalculatorState() {
|
||||
}
|
||||
|
||||
return formatLocaleValue(display);
|
||||
}, [accumulator, display, operator, overwrite]);
|
||||
})();
|
||||
|
||||
const buttons = useMemo(() => {
|
||||
const makeOperatorHandler = (nextOperator: Operator) => () =>
|
||||
setNextOperator(nextOperator);
|
||||
const makeOperatorHandler = (nextOperator: Operator) => () =>
|
||||
setNextOperator(nextOperator);
|
||||
|
||||
return [
|
||||
[
|
||||
{ label: "C", onClick: reset, variant: "destructive" },
|
||||
{ label: "⌫", onClick: deleteLastDigit, variant: "default" },
|
||||
{ label: "%", onClick: applyPercent, variant: "default" },
|
||||
{
|
||||
label: "÷",
|
||||
onClick: makeOperatorHandler("divide"),
|
||||
variant: "outline",
|
||||
},
|
||||
],
|
||||
[
|
||||
{ label: "7", onClick: () => inputDigit("7") },
|
||||
{ label: "8", onClick: () => inputDigit("8") },
|
||||
{ label: "9", onClick: () => inputDigit("9") },
|
||||
{
|
||||
label: "×",
|
||||
onClick: makeOperatorHandler("multiply"),
|
||||
variant: "outline",
|
||||
},
|
||||
],
|
||||
[
|
||||
{ label: "4", onClick: () => inputDigit("4") },
|
||||
{ label: "5", onClick: () => inputDigit("5") },
|
||||
{ label: "6", onClick: () => inputDigit("6") },
|
||||
{
|
||||
label: "-",
|
||||
onClick: makeOperatorHandler("subtract"),
|
||||
variant: "outline",
|
||||
},
|
||||
],
|
||||
[
|
||||
{ label: "1", onClick: () => inputDigit("1") },
|
||||
{ label: "2", onClick: () => inputDigit("2") },
|
||||
{ label: "3", onClick: () => inputDigit("3") },
|
||||
{ label: "+", onClick: makeOperatorHandler("add"), variant: "outline" },
|
||||
],
|
||||
[
|
||||
{ label: "±", onClick: toggleSign, variant: "default" },
|
||||
{ label: "0", onClick: () => inputDigit("0") },
|
||||
{ label: ",", onClick: inputDecimal },
|
||||
{ label: "=", onClick: evaluate, variant: "default" },
|
||||
],
|
||||
] satisfies CalculatorButtonConfig[][];
|
||||
}, [
|
||||
applyPercent,
|
||||
deleteLastDigit,
|
||||
evaluate,
|
||||
inputDecimal,
|
||||
inputDigit,
|
||||
reset,
|
||||
setNextOperator,
|
||||
toggleSign,
|
||||
]);
|
||||
const buttons: CalculatorButtonConfig[][] = [
|
||||
[
|
||||
{ label: "C", onClick: reset, variant: "destructive" },
|
||||
{ label: "⌫", onClick: deleteLastDigit, variant: "default" },
|
||||
{ label: "%", onClick: applyPercent, variant: "default" },
|
||||
{
|
||||
label: "÷",
|
||||
onClick: makeOperatorHandler("divide"),
|
||||
variant: "outline",
|
||||
},
|
||||
],
|
||||
[
|
||||
{ label: "7", onClick: () => inputDigit("7") },
|
||||
{ label: "8", onClick: () => inputDigit("8") },
|
||||
{ label: "9", onClick: () => inputDigit("9") },
|
||||
{
|
||||
label: "×",
|
||||
onClick: makeOperatorHandler("multiply"),
|
||||
variant: "outline",
|
||||
},
|
||||
],
|
||||
[
|
||||
{ label: "4", onClick: () => inputDigit("4") },
|
||||
{ label: "5", onClick: () => inputDigit("5") },
|
||||
{ label: "6", onClick: () => inputDigit("6") },
|
||||
{
|
||||
label: "-",
|
||||
onClick: makeOperatorHandler("subtract"),
|
||||
variant: "outline",
|
||||
},
|
||||
],
|
||||
[
|
||||
{ label: "1", onClick: () => inputDigit("1") },
|
||||
{ label: "2", onClick: () => inputDigit("2") },
|
||||
{ label: "3", onClick: () => inputDigit("3") },
|
||||
{ label: "+", onClick: makeOperatorHandler("add"), variant: "outline" },
|
||||
],
|
||||
[
|
||||
{ label: "±", onClick: toggleSign, variant: "default" },
|
||||
{ label: "0", onClick: () => inputDigit("0") },
|
||||
{ label: ",", onClick: inputDecimal },
|
||||
{ label: "=", onClick: evaluate, variant: "default" },
|
||||
],
|
||||
];
|
||||
|
||||
const copyToClipboard = useCallback(async () => {
|
||||
const copyToClipboard = async () => {
|
||||
if (!resultText) return;
|
||||
|
||||
try {
|
||||
@@ -320,9 +303,9 @@ export function useCalculatorState() {
|
||||
error,
|
||||
);
|
||||
}
|
||||
}, [resultText]);
|
||||
};
|
||||
|
||||
const pasteFromClipboard = useCallback(async () => {
|
||||
const pasteFromClipboard = async () => {
|
||||
if (!navigator.clipboard?.readText) return;
|
||||
|
||||
try {
|
||||
@@ -364,7 +347,7 @@ export function useCalculatorState() {
|
||||
} catch (error) {
|
||||
console.error("Não foi possível colar o valor na calculadora.", error);
|
||||
}
|
||||
}, []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
/**
|
||||
* Hook for managing form state with type-safe field updates
|
||||
@@ -22,26 +22,23 @@ export function useFormState<T extends Record<string, any>>(initialValues: T) {
|
||||
/**
|
||||
* Updates a single field in the form state
|
||||
*/
|
||||
const updateField = useCallback(
|
||||
<K extends keyof T>(field: K, value: T[K]) => {
|
||||
setFormState((prev) => ({ ...prev, [field]: value }));
|
||||
},
|
||||
[],
|
||||
);
|
||||
const updateField = <K extends keyof T>(field: K, value: T[K]) => {
|
||||
setFormState((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets form to initial values
|
||||
*/
|
||||
const resetForm = useCallback(() => {
|
||||
const resetForm = () => {
|
||||
setFormState(initialValues);
|
||||
}, [initialValues]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates multiple fields at once
|
||||
*/
|
||||
const updateFields = useCallback((updates: Partial<T>) => {
|
||||
const updateFields = (updates: Partial<T>) => {
|
||||
setFormState((prev) => ({ ...prev, ...updates }));
|
||||
}, []);
|
||||
};
|
||||
|
||||
return {
|
||||
formState,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { MONTH_NAMES } from "@/lib/utils/period";
|
||||
|
||||
@@ -46,29 +46,23 @@ export function useMonthPeriod() {
|
||||
};
|
||||
}, [periodFromParams, defaultMonth, defaultYear, optionsMeses]);
|
||||
|
||||
const buildHref = useCallback(
|
||||
(month: string, year: string | number) => {
|
||||
const normalizedMonth = normalizeMonth(month);
|
||||
const normalizedYear = String(year).trim();
|
||||
const buildHref = (month: string, year: string | number) => {
|
||||
const normalizedMonth = normalizeMonth(month);
|
||||
const normalizedYear = String(year).trim();
|
||||
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set(PERIOD_PARAM, `${normalizedMonth}-${normalizedYear}`);
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set(PERIOD_PARAM, `${normalizedMonth}-${normalizedYear}`);
|
||||
|
||||
return `${pathname}?${params.toString()}`;
|
||||
},
|
||||
[pathname, searchParams],
|
||||
);
|
||||
return `${pathname}?${params.toString()}`;
|
||||
};
|
||||
|
||||
const replacePeriod = useCallback(
|
||||
(target: string) => {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const replacePeriod = (target: string) => {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
router.replace(target, { scroll: false });
|
||||
},
|
||||
[router],
|
||||
);
|
||||
router.replace(target, { scroll: false });
|
||||
};
|
||||
|
||||
return {
|
||||
monthNames: optionsMeses,
|
||||
|
||||
Reference in New Issue
Block a user