diff --git a/src/Augmentation/ui/AugmentationsRoot.tsx b/src/Augmentation/ui/AugmentationsRoot.tsx index 7f19ca46c..045ca7a6e 100644 --- a/src/Augmentation/ui/AugmentationsRoot.tsx +++ b/src/Augmentation/ui/AugmentationsRoot.tsx @@ -2,7 +2,7 @@ * Root React component for the Augmentations UI page that display all of your * owned and purchased Augmentations and Source-Files. */ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { InstalledAugmentations } from "./InstalledAugmentations"; import { PlayerMultipliers } from "./PlayerMultipliers"; @@ -26,6 +26,7 @@ import { formatNumberNoSuffix } from "../../ui/formatNumber"; import { Info } from "@mui/icons-material"; import { Link } from "@mui/material"; import { AlertEvents } from "../../ui/React/AlertManager"; +import { useRerender } from "../../ui/React/hooks"; const NeuroFluxDisplay = (): React.ReactElement => { const level = Player.augmentations.find((e) => e.name === AugmentationNames.NeuroFluxGovernor)?.level ?? 0; @@ -84,14 +85,7 @@ interface IProps { export function AugmentationsRoot(props: IProps): React.ReactElement { const [installOpen, setInstallOpen] = useState(false); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((o) => !o); - } - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); + const rerender = useRerender(200); function doExport(): void { props.exportGameFn(); diff --git a/src/Augmentation/ui/InstalledAugmentations.tsx b/src/Augmentation/ui/InstalledAugmentations.tsx index 7d802284c..02455b9e3 100644 --- a/src/Augmentation/ui/InstalledAugmentations.tsx +++ b/src/Augmentation/ui/InstalledAugmentations.tsx @@ -15,9 +15,10 @@ import { Settings } from "../../Settings/Settings"; import { Player } from "@player"; import { StaticAugmentations } from "../StaticAugmentations"; import { AugmentationNames } from "../data/AugmentationNames"; +import { useRerender } from "../../ui/React/hooks"; export function InstalledAugmentations(): React.ReactElement { - const setRerender = useState(true)[1]; + const rerender = useRerender(); const sourceAugs = Player.augmentations.slice().filter((aug) => aug.name !== AugmentationNames.NeuroFluxGovernor); const [selectedAug, setSelectedAug] = useState(sourceAugs[0]); @@ -28,10 +29,6 @@ export function InstalledAugmentations(): React.ReactElement { }); } - function rerender(): void { - setRerender((old) => !old); - } - function sortByAcquirementTime(): void { Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime; rerender(); diff --git a/src/Bladeburner/ui/BlackOpElem.tsx b/src/Bladeburner/ui/BlackOpElem.tsx index f403f73bd..c2ba8f2dc 100644 --- a/src/Bladeburner/ui/BlackOpElem.tsx +++ b/src/Bladeburner/ui/BlackOpElem.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { formatNumberNoSuffix } from "../../ui/formatNumber"; import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { ActionTypes } from "../data/ActionTypes"; @@ -14,6 +14,7 @@ import { StartButton } from "./StartButton"; import Typography from "@mui/material/Typography"; import Paper from "@mui/material/Paper"; +import { useRerender } from "../../ui/React/hooks"; interface IProps { bladeburner: Bladeburner; @@ -21,10 +22,7 @@ interface IProps { } export function BlackOpElem(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(); const isCompleted = props.bladeburner.blackops[props.action.name] != null; if (isCompleted) { return ( diff --git a/src/Bladeburner/ui/BladeburnerRoot.tsx b/src/Bladeburner/ui/BladeburnerRoot.tsx index 463690700..1209b1561 100644 --- a/src/Bladeburner/ui/BladeburnerRoot.tsx +++ b/src/Bladeburner/ui/BladeburnerRoot.tsx @@ -1,22 +1,14 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import { Stats } from "./Stats"; import { Console } from "./Console"; import { AllPages } from "./AllPages"; import { Player } from "@player"; import Box from "@mui/material/Box"; +import { useRerender } from "../../ui/React/hooks"; export function BladeburnerRoot(): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); - + useRerender(200); const bladeburner = Player.bladeburner; if (!bladeburner) return <>; return ( diff --git a/src/Bladeburner/ui/Console.tsx b/src/Bladeburner/ui/Console.tsx index fec48d64c..12f5537ae 100644 --- a/src/Bladeburner/ui/Console.tsx +++ b/src/Bladeburner/ui/Console.tsx @@ -11,6 +11,7 @@ import TextField from "@mui/material/TextField"; import { Theme } from "@mui/material/styles"; import makeStyles from "@mui/styles/makeStyles"; import createStyles from "@mui/styles/createStyles"; +import { useRerender } from "../../ui/React/hooks"; interface ILineProps { content: React.ReactNode; @@ -54,8 +55,8 @@ interface IProps { export function Console(props: IProps): React.ReactElement { const classes = useStyles(); const [command, setCommand] = useState(""); - const setRerender = useState(false)[1]; const consoleInput = useRef(null); + useRerender(1000); function handleCommandChange(event: React.ChangeEvent): void { setCommand(event.target.value); @@ -63,17 +64,6 @@ export function Console(props: IProps): React.ReactElement { const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length); - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 1000); - return () => { - clearInterval(id); - }; - }, []); - function handleKeyDown(event: React.KeyboardEvent): void { if (event.key === KEY.ENTER) { event.preventDefault(); diff --git a/src/Bladeburner/ui/ContractElem.tsx b/src/Bladeburner/ui/ContractElem.tsx index 2cfabae01..91ee0874a 100644 --- a/src/Bladeburner/ui/ContractElem.tsx +++ b/src/Bladeburner/ui/ContractElem.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { ActionTypes } from "../data/ActionTypes"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; @@ -14,6 +14,7 @@ import { StartButton } from "./StartButton"; import { formatNumberNoSuffix, formatBigNumber } from "../../ui/formatNumber"; import Typography from "@mui/material/Typography"; import Paper from "@mui/material/Paper"; +import { useRerender } from "../../ui/React/hooks"; interface IProps { bladeburner: Bladeburner; @@ -21,10 +22,7 @@ interface IProps { } export function ContractElem(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(); const isActive = props.bladeburner.action.type === ActionTypes["Contract"] && props.action.name === props.bladeburner.action.name; const computedActionTimeCurrent = Math.min( diff --git a/src/Bladeburner/ui/GeneralActionElem.tsx b/src/Bladeburner/ui/GeneralActionElem.tsx index fdd07fb48..240c299c9 100644 --- a/src/Bladeburner/ui/GeneralActionElem.tsx +++ b/src/Bladeburner/ui/GeneralActionElem.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { ActionTypes } from "../data/ActionTypes"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { formatNumberNoSuffix } from "../../ui/formatNumber"; @@ -14,6 +14,7 @@ import { StartButton } from "./StartButton"; import Typography from "@mui/material/Typography"; import Box from "@mui/material/Box"; import Paper from "@mui/material/Paper"; +import { useRerender } from "../../ui/React/hooks"; interface IProps { bladeburner: Bladeburner; @@ -21,10 +22,7 @@ interface IProps { } export function GeneralActionElem(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(); const isActive = props.action.name === props.bladeburner.action.name; const computedActionTimeCurrent = Math.min( props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, diff --git a/src/Bladeburner/ui/OperationElem.tsx b/src/Bladeburner/ui/OperationElem.tsx index 866b63643..9ad22b982 100644 --- a/src/Bladeburner/ui/OperationElem.tsx +++ b/src/Bladeburner/ui/OperationElem.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { ActionTypes } from "../data/ActionTypes"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; @@ -15,6 +15,7 @@ import { CopyableText } from "../../ui/React/CopyableText"; import { formatNumberNoSuffix, formatBigNumber } from "../../ui/formatNumber"; import Typography from "@mui/material/Typography"; import Paper from "@mui/material/Paper"; +import { useRerender } from "../../ui/React/hooks"; interface IProps { bladeburner: Bladeburner; @@ -22,10 +23,7 @@ interface IProps { } export function OperationElem(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(); const isActive = props.bladeburner.action.type === ActionTypes["Operation"] && props.action.name === props.bladeburner.action.name; const computedActionTimeCurrent = Math.min( diff --git a/src/Corporation/ui/CorporationRoot.tsx b/src/Corporation/ui/CorporationRoot.tsx index cffe84a5a..c87775145 100644 --- a/src/Corporation/ui/CorporationRoot.tsx +++ b/src/Corporation/ui/CorporationRoot.tsx @@ -1,7 +1,7 @@ // React Components for the Corporation UI's navigation tabs // These are the tabs at the top of the UI that let you switch to different // divisions, see an overview of your corporation, or create a new industry -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { MainPanel } from "./MainPanel"; import { IndustryType } from "../data/Enums"; import { ExpandIndustryTab } from "./ExpandIndustryTab"; @@ -11,22 +11,16 @@ import { Overview } from "./Overview"; import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; +import { useRerender } from "../../ui/React/hooks"; export function CorporationRoot(): React.ReactElement { + const rerender = useRerender(200); const corporation = Player.corporation; if (corporation === null) return <>; - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } const [divisionName, setDivisionName] = useState("Overview"); function handleChange(event: React.SyntheticEvent, tab: string | number): void { setDivisionName(tab); } - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); const canExpand = Object.values(IndustryType).filter( diff --git a/src/Corporation/ui/modals/ExportModal.tsx b/src/Corporation/ui/modals/ExportModal.tsx index 7ea4428ef..478336e09 100644 --- a/src/Corporation/ui/modals/ExportModal.tsx +++ b/src/Corporation/ui/modals/ExportModal.tsx @@ -14,6 +14,7 @@ import Box from "@mui/material/Box"; import MenuItem from "@mui/material/MenuItem"; import Select, { SelectChangeEvent } from "@mui/material/Select"; import { CityName } from "../../../Enums"; +import { useRerender } from "../../../ui/React/hooks"; interface IProps { open: boolean; @@ -32,11 +33,7 @@ export function ExportModal(props: IProps): React.ReactElement { const [industry, setIndustry] = useState(defaultDivision.name); const [city, setCity] = useState(Object.keys(defaultDivision.warehouses)[0] as CityName); const [amt, setAmt] = useState(""); - const setRerender = useState(false)[1]; - - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(); function onCityChange(event: SelectChangeEvent): void { setCity(event.target.value as CityName); diff --git a/src/Corporation/ui/modals/MaterialMarketTaModal.tsx b/src/Corporation/ui/modals/MaterialMarketTaModal.tsx index 265202193..db02f8d52 100644 --- a/src/Corporation/ui/modals/MaterialMarketTaModal.tsx +++ b/src/Corporation/ui/modals/MaterialMarketTaModal.tsx @@ -8,6 +8,7 @@ import TextField from "@mui/material/TextField"; import FormControlLabel from "@mui/material/FormControlLabel"; import Switch from "@mui/material/Switch"; import Tooltip from "@mui/material/Tooltip"; +import { useRerender } from "../../../ui/React/hooks"; interface IMarketTA2Props { mat: Material; @@ -17,10 +18,7 @@ function MarketTA2(props: IMarketTA2Props): React.ReactElement { const division = useDivision(); if (!division.hasResearch("Market-TA.II")) return <>; const [newCost, setNewCost] = useState(props.mat.bCost); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(); const markupLimit = props.mat.getMarkupLimit(); function onChange(event: React.ChangeEvent): void { diff --git a/src/Corporation/ui/modals/ProductMarketTaModal.tsx b/src/Corporation/ui/modals/ProductMarketTaModal.tsx index 09e2e9e69..de0ea2ada 100644 --- a/src/Corporation/ui/modals/ProductMarketTaModal.tsx +++ b/src/Corporation/ui/modals/ProductMarketTaModal.tsx @@ -8,6 +8,7 @@ import TextField from "@mui/material/TextField"; import FormControlLabel from "@mui/material/FormControlLabel"; import Switch from "@mui/material/Switch"; import Tooltip from "@mui/material/Tooltip"; +import { useRerender } from "../../../ui/React/hooks"; interface ITa2Props { product: Product; @@ -18,10 +19,7 @@ function MarketTA2(props: ITa2Props): React.ReactElement { if (!division.hasResearch("Market-TA.II")) return <>; const markupLimit = props.product.rat / props.product.mku; const [value, setValue] = useState(props.product.pCost); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(); function onChange(event: React.ChangeEvent): void { setValue(parseFloat(event.target.value)); diff --git a/src/Corporation/ui/modals/SmartSupplyModal.tsx b/src/Corporation/ui/modals/SmartSupplyModal.tsx index 455ec6634..399e70476 100644 --- a/src/Corporation/ui/modals/SmartSupplyModal.tsx +++ b/src/Corporation/ui/modals/SmartSupplyModal.tsx @@ -10,6 +10,7 @@ import FormControlLabel from "@mui/material/FormControlLabel"; import Switch from "@mui/material/Switch"; import { CorpMaterialName } from "@nsdefs"; import { materialNames } from "../../data/Constants"; +import { useRerender } from "../../../ui/React/hooks"; interface ILeftoverProps { matName: CorpMaterialName; @@ -49,10 +50,7 @@ interface IProps { export function SmartSupplyModal(props: IProps): React.ReactElement { const division = useDivision(); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(); // Smart Supply Checkbox function smartSupplyOnChange(e: React.ChangeEvent): void { diff --git a/src/CotMG/ui/StaneksGiftRoot.tsx b/src/CotMG/ui/StaneksGiftRoot.tsx index d4c091ff8..8057b9045 100644 --- a/src/CotMG/ui/StaneksGiftRoot.tsx +++ b/src/CotMG/ui/StaneksGiftRoot.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useEffect } from "react"; import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { CONSTANTS } from "../../Constants"; import { StaneksGiftEvents } from "../StaneksGiftEvents"; @@ -11,16 +11,14 @@ import { ActiveFragment } from "../ActiveFragment"; import { Fragments } from "../Fragment"; import { DummyGrid } from "./DummyGrid"; import Container from "@mui/material/Container"; +import { useRerender } from "../../ui/React/hooks"; type IProps = { staneksGift: StaneksGift; }; export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement { - const setRerender = useState(true)[1]; - function rerender(): void { - setRerender((o) => !o); - } + const rerender = useRerender(); useEffect(() => StaneksGiftEvents.subscribe(rerender), []); return ( diff --git a/src/Faction/ui/AugmentationsPage.tsx b/src/Faction/ui/AugmentationsPage.tsx index 2b5f7ae68..11c530200 100644 --- a/src/Faction/ui/AugmentationsPage.tsx +++ b/src/Faction/ui/AugmentationsPage.tsx @@ -1,5 +1,5 @@ import { Box, Button, Tooltip, Typography, Paper, Container } from "@mui/material"; -import React, { useState } from "react"; +import React from "react"; import { StaticAugmentations } from "../../Augmentation/StaticAugmentations"; import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers"; @@ -15,6 +15,7 @@ import { FactionNames } from "../data/FactionNames"; import { Faction } from "../Faction"; import { getFactionAugmentationsFiltered, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers"; import { CONSTANTS } from "../../Constants"; +import { useRerender } from "../../ui/React/hooks"; type IProps = { faction: Faction; @@ -23,11 +24,7 @@ type IProps = { /** Root React Component for displaying a faction's "Purchase Augmentations" page */ export function AugmentationsPage(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(); function getAugs(): string[] { return getFactionAugmentationsFiltered(props.faction); diff --git a/src/Faction/ui/FactionRoot.tsx b/src/Faction/ui/FactionRoot.tsx index d774625a6..68c446ec4 100644 --- a/src/Faction/ui/FactionRoot.tsx +++ b/src/Faction/ui/FactionRoot.tsx @@ -3,7 +3,7 @@ * This is the component for displaying a single faction's UI, not the list of all * accessible factions */ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { AugmentationsPage } from "./AugmentationsPage"; import { DonateOption } from "./DonateOption"; @@ -25,6 +25,7 @@ import { FactionNames } from "../data/FactionNames"; import { GangButton } from "./GangButton"; import { FactionWork } from "../../Work/FactionWork"; import { FactionWorkType } from "../../Enums"; +import { useRerender } from "../../ui/React/hooks"; type IProps = { faction: Faction; @@ -152,18 +153,9 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea } export function FactionRoot(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; + const rerender = useRerender(200); const [purchasingAugs, setPurchasingAugs] = useState(props.augPage); - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); - const faction = props.faction; if (!Player.factions.includes(faction.name)) { diff --git a/src/Faction/ui/FactionsRoot.tsx b/src/Faction/ui/FactionsRoot.tsx index da9e44904..4717d031d 100644 --- a/src/Faction/ui/FactionsRoot.tsx +++ b/src/Faction/ui/FactionsRoot.tsx @@ -1,6 +1,6 @@ import { Explore, Info, LastPage, LocalPolice, NewReleases, Report, SportsMma } from "@mui/icons-material"; import { Box, Button, Container, Paper, Tooltip, Typography, useTheme } from "@mui/material"; -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; import { Player } from "@player"; import { Settings } from "../../Settings/Settings"; import { formatFavor, formatReputation } from "../../ui/formatNumber"; @@ -9,6 +9,7 @@ import { FactionNames } from "../data/FactionNames"; import { Faction } from "../Faction"; import { getFactionAugmentationsFiltered, joinFaction } from "../FactionHelpers"; import { Factions } from "../Factions"; +import { useRerender } from "../../ui/React/hooks"; export const InvitationsSeen: string[] = []; @@ -166,15 +167,7 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => { export function FactionsRoot(): React.ReactElement { const theme = useTheme(); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); - + const rerender = useRerender(200); useEffect(() => { Player.factionInvitations.forEach((faction) => { if (InvitationsSeen.includes(faction)) return; diff --git a/src/Faction/ui/Info.tsx b/src/Faction/ui/Info.tsx index d49f37b4b..7f5958499 100644 --- a/src/Faction/ui/Info.tsx +++ b/src/Faction/ui/Info.tsx @@ -2,7 +2,7 @@ * React component for general information about the faction. This includes the * factions "motto", reputation, favor, and gameplay instructions */ -import React, { useState, useEffect } from "react"; +import React from "react"; import { Faction } from "../Faction"; import { FactionInfo } from "../FactionInfo"; @@ -16,6 +16,7 @@ import createStyles from "@mui/styles/createStyles"; import Typography from "@mui/material/Typography"; import Tooltip from "@mui/material/Tooltip"; import Box from "@mui/material/Box"; +import { useRerender } from "../../ui/React/hooks"; type IProps = { faction: Faction; @@ -43,16 +44,7 @@ function DefaultAssignment(): React.ReactElement { } export function Info(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); - + useRerender(200); const classes = useStyles(); const Assignment = props.factionInfo.assignment ?? DefaultAssignment; diff --git a/src/Gang/ui/EquipmentsSubpage.tsx b/src/Gang/ui/EquipmentsSubpage.tsx index 234017036..64cfff11e 100644 --- a/src/Gang/ui/EquipmentsSubpage.tsx +++ b/src/Gang/ui/EquipmentsSubpage.tsx @@ -19,6 +19,7 @@ import { UpgradeType } from "../data/upgrades"; import { Player } from "@player"; import { Settings } from "../../Settings/Settings"; import { StatsRow } from "../../ui/React/StatsRow"; +import { useRerender } from "../../ui/React/hooks"; interface INextRevealProps { upgrades: string[]; @@ -86,13 +87,9 @@ interface IPanelProps { function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement { const gang = useGang(); - const setRerender = useState(false)[1]; + const rerender = useRerender(); const [currentCategory, setCurrentCategory] = useState("Weapons"); - function rerender(): void { - setRerender((old) => !old); - } - function filterUpgrades(list: string[], type: UpgradeType): GangMemberUpgrade[] { return Object.keys(GangMemberUpgrades) .filter((upgName: string) => { diff --git a/src/Hacknet/ui/HacknetRoot.tsx b/src/Hacknet/ui/HacknetRoot.tsx index 72ea23f49..20867d22b 100644 --- a/src/Hacknet/ui/HacknetRoot.tsx +++ b/src/Hacknet/ui/HacknetRoot.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { GeneralInfo } from "./GeneralInfo"; import { HacknetNodeElem } from "./HacknetNodeElem"; @@ -25,21 +25,14 @@ import Typography from "@mui/material/Typography"; import Grid from "@mui/material/Grid"; import Button from "@mui/material/Button"; import { Box } from "@mui/material"; +import { useRerender } from "../../ui/React/hooks"; /** Root React Component for the Hacknet Node UI */ export function HacknetRoot(): React.ReactElement { const [open, setOpen] = useState(false); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(200); const [purchaseMultiplier, setPurchaseMultiplier] = useState(PurchaseMultipliers.x1); - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); - let totalProduction = 0; for (let i = 0; i < Player.hacknetNodes.length; ++i) { const node = Player.hacknetNodes[i]; diff --git a/src/Hacknet/ui/HashUpgradeModal.tsx b/src/Hacknet/ui/HashUpgradeModal.tsx index 9a30a22da..5ba1fe402 100644 --- a/src/Hacknet/ui/HashUpgradeModal.tsx +++ b/src/Hacknet/ui/HashUpgradeModal.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import { HashUpgrades } from "../HashUpgrades"; @@ -7,6 +7,7 @@ import { HacknetUpgradeElem } from "./HacknetUpgradeElem"; import { Modal } from "../../ui/React/Modal"; import { Player } from "@player"; import Typography from "@mui/material/Typography"; +import { useRerender } from "../../ui/React/hooks"; interface IProps { open: boolean; @@ -15,15 +16,7 @@ interface IProps { /** Create the pop-up for purchasing upgrades with hashes */ export function HashUpgradeModal(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(() => setRerender((old) => !old), 200); - return () => clearInterval(id); - }, []); + const rerender = useRerender(200); const hashManager = Player.hashManager; if (!hashManager) { diff --git a/src/Locations/ui/CompanyLocation.tsx b/src/Locations/ui/CompanyLocation.tsx index 9c6aa90b1..94c5e24ac 100644 --- a/src/Locations/ui/CompanyLocation.tsx +++ b/src/Locations/ui/CompanyLocation.tsx @@ -3,7 +3,7 @@ * * This subcomponent renders all of the buttons for applying to jobs at a company */ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import Tooltip from "@mui/material/Tooltip"; @@ -25,6 +25,7 @@ import { Page } from "../../ui/Router"; import { Player } from "@player"; import { QuitJobModal } from "../../Company/ui/QuitJobModal"; import { CompanyWork } from "../../Work/CompanyWork"; +import { useRerender } from "../../ui/React/hooks"; type IProps = { locName: LocationName; @@ -32,15 +33,8 @@ type IProps = { export function CompanyLocation(props: IProps): React.ReactElement { const [quitOpen, setQuitOpen] = useState(false); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(200); - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); /** * We'll keep a reference to the Company that this component is being rendered for, * so we don't have to look it up every time diff --git a/src/Locations/ui/HospitalLocation.tsx b/src/Locations/ui/HospitalLocation.tsx index 9039831bf..afb5ffe72 100644 --- a/src/Locations/ui/HospitalLocation.tsx +++ b/src/Locations/ui/HospitalLocation.tsx @@ -12,20 +12,12 @@ import { getHospitalizationCost } from "../../Hospital/Hospital"; import { Money } from "../../ui/React/Money"; import { dialogBoxCreate } from "../../ui/React/DialogBox"; -import { useEffect, useState } from "react"; +import { useRerender } from "../../ui/React/hooks"; export function HospitalLocation(): React.ReactElement { /** Stores button styling that sets them all to block display */ const btnStyle = { display: "block" }; - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); + const rerender = useRerender(200); function getHealed(e: React.MouseEvent): void { if (!e.isTrusted) { diff --git a/src/Locations/ui/PurchaseServerModal.tsx b/src/Locations/ui/PurchaseServerModal.tsx index 86e273b2f..0b62d8060 100644 --- a/src/Locations/ui/PurchaseServerModal.tsx +++ b/src/Locations/ui/PurchaseServerModal.tsx @@ -14,7 +14,6 @@ interface IProps { onClose: () => void; ram: number; cost: number; - rerender: () => void; } /** React Component for the popup used to purchase a new server. */ diff --git a/src/Locations/ui/SlumsLocation.tsx b/src/Locations/ui/SlumsLocation.tsx index dab0cb369..b1c341998 100644 --- a/src/Locations/ui/SlumsLocation.tsx +++ b/src/Locations/ui/SlumsLocation.tsx @@ -3,7 +3,7 @@ * * This subcomponent renders all of the buttons for committing crimes */ -import React, { useState, useEffect } from "react"; +import React from "react"; import Button from "@mui/material/Button"; import Tooltip from "@mui/material/Tooltip"; @@ -15,15 +15,11 @@ import { Page } from "../../ui/Router"; import { Player } from "@player"; import { Box } from "@mui/material"; import { Crime } from "../../Crime/Crime"; +import { useRerender } from "../../ui/React/hooks"; export function SlumsLocation(): React.ReactElement { - const setRerender = useState(false)[1]; - const rerender = () => setRerender((o) => !o); + useRerender(1000); const crimes = Object.values(Crimes); - useEffect(() => { - const timerId = setInterval(() => rerender(), 1000); - return () => clearInterval(timerId); - }); function doCrime(e: React.MouseEvent, crime: Crime) { if (!e.isTrusted) return; diff --git a/src/Locations/ui/TechVendorLocation.tsx b/src/Locations/ui/TechVendorLocation.tsx index fef6a3274..5328bd989 100644 --- a/src/Locations/ui/TechVendorLocation.tsx +++ b/src/Locations/ui/TechVendorLocation.tsx @@ -3,7 +3,7 @@ * * This subcomponent renders all of the buttons for purchasing things from tech vendors */ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; @@ -19,13 +19,9 @@ import { Player } from "@player"; import { PurchaseServerModal } from "./PurchaseServerModal"; import { formatRam } from "../../ui/formatNumber"; import { Box } from "@mui/material"; +import { useRerender } from "../../ui/React/hooks"; -interface IServerProps { - ram: number; - rerender: () => void; -} - -function ServerButton(props: IServerProps): React.ReactElement { +function ServerButton(props: { ram: number }): React.ReactElement { const [open, setOpen] = useState(false); const cost = getPurchaseServerCost(props.ram); return ( @@ -34,35 +30,17 @@ function ServerButton(props: IServerProps): React.ReactElement { Purchase {formatRam(props.ram)} Server -  - setOpen(false)} - ram={props.ram} - cost={cost} - rerender={props.rerender} - /> + setOpen(false)} ram={props.ram} cost={cost} /> ); } -type IProps = { - loc: Location; -}; - -export function TechVendorLocation(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 1000); - return () => clearInterval(id); - }, []); +export function TechVendorLocation(props: { loc: Location }): React.ReactElement { + const rerender = useRerender(1000); const purchaseServerButtons: React.ReactNode[] = []; for (let i = props.loc.techVendorMinRam; i <= props.loc.techVendorMaxRam; i *= 2) { - purchaseServerButtons.push(); + purchaseServerButtons.push(); } return ( diff --git a/src/Locations/ui/TravelAgencyRoot.tsx b/src/Locations/ui/TravelAgencyRoot.tsx index f4d963736..4062bed99 100644 --- a/src/Locations/ui/TravelAgencyRoot.tsx +++ b/src/Locations/ui/TravelAgencyRoot.tsx @@ -3,7 +3,7 @@ * * TThis subcomponent renders all of the buttons for traveling to different cities */ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { CityName } from "../../Enums"; import { TravelConfirmationModal } from "./TravelConfirmationModal"; @@ -21,6 +21,7 @@ import { dialogBoxCreate } from "../../ui/React/DialogBox"; import Typography from "@mui/material/Typography"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; +import { useRerender } from "../../ui/React/hooks"; function travel(to: CityName): void { const cost = CONSTANTS.TravelCost; @@ -35,17 +36,9 @@ function travel(to: CityName): void { } export function TravelAgencyRoot(): React.ReactElement { - const setRerender = useState(false)[1]; const [open, setOpen] = useState(false); const [destination, setDestination] = useState(CityName.Sector12); - function rerender(): void { - setRerender((o) => !o); - } - - useEffect(() => { - const id = setInterval(rerender, 1000); - return () => clearInterval(id); - }, []); + useRerender(1000); function startTravel(city: CityName): void { const cost = CONSTANTS.TravelCost; diff --git a/src/PersonObjects/Grafting/ui/GraftingRoot.tsx b/src/PersonObjects/Grafting/ui/GraftingRoot.tsx index dc03b91db..3df795e52 100644 --- a/src/PersonObjects/Grafting/ui/GraftingRoot.tsx +++ b/src/PersonObjects/Grafting/ui/GraftingRoot.tsx @@ -1,6 +1,6 @@ import { CheckBox, CheckBoxOutlineBlank, Construction } from "@mui/icons-material"; import { Box, Button, Container, List, ListItemButton, Paper, Typography } from "@mui/material"; -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { GraftingWork } from "../../../Work/GraftingWork"; import { Augmentation } from "../../../Augmentation/Augmentation"; import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames"; @@ -20,6 +20,7 @@ import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFun import { Player } from "@player"; import { GraftableAugmentation } from "../GraftableAugmentation"; import { calculateGraftingTimeWithBonus, getGraftingAvailableAugs } from "../GraftingHelpers"; +import { useRerender } from "../../../ui/React/hooks"; export const GraftableAugmentations = (): Record => { const gAugs: Record = {}; @@ -65,11 +66,7 @@ export const GraftingRoot = (): React.ReactElement => { const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs()[0]); const [graftOpen, setGraftOpen] = useState(false); const selectedAugmentation = StaticAugmentations[selectedAug]; - - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + const rerender = useRerender(200); const getAugsSorted = (): string[] => { const augs = getGraftingAvailableAugs(); @@ -86,11 +83,6 @@ export const GraftingRoot = (): React.ReactElement => { rerender(); }; - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); - return ( diff --git a/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx b/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx index d1ec90b17..3bde0955c 100644 --- a/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx +++ b/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx @@ -2,7 +2,7 @@ * Root React component for the popup that lets player purchase Duplicate * Sleeves and Sleeve-related upgrades from The Covenant */ -import React, { useState } from "react"; +import React from "react"; import { CovenantSleeveMemoryUpgrade } from "./CovenantSleeveMemoryUpgrade"; @@ -17,6 +17,7 @@ import { dialogBoxCreate } from "../../../ui/React/DialogBox"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import { FactionNames } from "../../../Faction/data/FactionNames"; +import { useRerender } from "../../../ui/React/hooks"; interface IProps { open: boolean; @@ -24,18 +25,13 @@ interface IProps { } export function CovenantPurchasesRoot(props: IProps): React.ReactElement { - const [update, setUpdate] = useState(0); + const rerender = useRerender(); /** Get the cost to purchase a new Duplicate Sleeve */ function purchaseCost(): number { return Math.pow(10, Player.sleevesFromCovenant) * BaseCostPerSleeve; } - /** Force a rerender by just changing an arbitrary state value */ - function rerender(): void { - setUpdate(update + 1); - } - // Purchasing a new Duplicate Sleeve let purchaseDisabled = false; if (!Player.canAfford(purchaseCost())) { diff --git a/src/PersonObjects/Sleeve/ui/SleeveAugmentationsModal.tsx b/src/PersonObjects/Sleeve/ui/SleeveAugmentationsModal.tsx index a840526b7..faa1491e4 100644 --- a/src/PersonObjects/Sleeve/ui/SleeveAugmentationsModal.tsx +++ b/src/PersonObjects/Sleeve/ui/SleeveAugmentationsModal.tsx @@ -1,9 +1,10 @@ import { Container, Typography, Paper } from "@mui/material"; -import React, { useEffect, useState } from "react"; +import React from "react"; import { PurchasableAugmentations } from "../../../Augmentation/ui/PurchasableAugmentations"; import { Player } from "@player"; import { Modal } from "../../../ui/React/Modal"; import { Sleeve } from "../Sleeve"; +import { useRerender } from "../../../ui/React/hooks"; interface IProps { open: boolean; @@ -12,15 +13,7 @@ interface IProps { } export function SleeveAugmentationsModal(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 150); - return () => clearInterval(id); - }, []); + const rerender = useRerender(150); // Array of all owned Augmentations. Names only const ownedAugNames = props.sleeve.augmentations.map((e) => e.name); diff --git a/src/PersonObjects/Sleeve/ui/SleeveRoot.tsx b/src/PersonObjects/Sleeve/ui/SleeveRoot.tsx index 99baec319..b7416df85 100644 --- a/src/PersonObjects/Sleeve/ui/SleeveRoot.tsx +++ b/src/PersonObjects/Sleeve/ui/SleeveRoot.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { Box, Typography, Button, Container } from "@mui/material"; @@ -6,18 +6,11 @@ import { Player } from "@player"; import { SleeveElem } from "./SleeveElem"; import { FAQModal } from "./FAQModal"; +import { useRerender } from "../../../ui/React/hooks"; export function SleeveRoot(): React.ReactElement { const [FAQOpen, setFAQOpen] = useState(false); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); + const rerender = useRerender(200); return ( <> diff --git a/src/Programs/ui/ProgramsRoot.tsx b/src/Programs/ui/ProgramsRoot.tsx index cd2aa0c1e..13ba4fd09 100644 --- a/src/Programs/ui/ProgramsRoot.tsx +++ b/src/Programs/ui/ProgramsRoot.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useEffect } from "react"; import { find } from "lodash"; import { Box, Typography, Button, Container, Paper } from "@mui/material"; @@ -11,14 +11,12 @@ import { Settings } from "../../Settings/Settings"; import { Programs } from "../Programs"; import { CreateProgramWork } from "../../Work/CreateProgramWork"; +import { useRerender } from "../../ui/React/hooks"; export const ProgramsSeen: string[] = []; export function ProgramsRoot(): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } + useRerender(200); const programs = [...Object.values(Programs)] .filter((prog) => { @@ -42,11 +40,6 @@ export function ProgramsRoot(): React.ReactElement { }); }, []); - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); - const getHackingLevelRemaining = (lvl: number): number => { return Math.ceil(Math.max(lvl - (Player.skills.hacking + Player.skills.intelligence / 2), 0)); }; diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx index 870838032..cf3b8a2ca 100644 --- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx +++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx @@ -45,6 +45,7 @@ import { Modal } from "../../ui/React/Modal"; import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts"; import { TextField, Tooltip } from "@mui/material"; +import { useRerender } from "../../ui/React/hooks"; interface IProps { // Map of filename -> code @@ -106,10 +107,7 @@ let currentScript: OpenScript | null = null; // Called every time script editor is opened export function Root(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((o) => !o); - } + const rerender = useRerender(); const editorRef = useRef(null); const monacoRef = useRef(null); const vimStatusRef = useRef(null); diff --git a/src/ScriptEditor/ui/ThemeEditorModal.tsx b/src/ScriptEditor/ui/ThemeEditorModal.tsx index 69bf2aa32..03d35f14d 100644 --- a/src/ScriptEditor/ui/ThemeEditorModal.tsx +++ b/src/ScriptEditor/ui/ThemeEditorModal.tsx @@ -4,6 +4,7 @@ import IconButton from "@mui/material/IconButton"; import _ from "lodash"; import { Color, ColorPicker } from "material-ui-color"; import React, { useState } from "react"; +import { useRerender } from "../../ui/React/hooks"; import { Settings } from "../../Settings/Settings"; import { Modal } from "../../ui/React/Modal"; import { OptionSwitch } from "../../ui/React/OptionSwitch"; @@ -65,10 +66,7 @@ function ColorEditor({ label, themePath, onColorChange, color, defaultColor }: I } export function ThemeEditorModal(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((o) => !o); - } + const rerender = useRerender(); // Need to deep copy the object since it has nested attributes const [themeCopy, setThemeCopy] = useState(JSON.parse(JSON.stringify(Settings.EditorTheme))); diff --git a/src/Sidebar/ui/SidebarRoot.tsx b/src/Sidebar/ui/SidebarRoot.tsx index 3bea96d98..0a0d1565c 100644 --- a/src/Sidebar/ui/SidebarRoot.tsx +++ b/src/Sidebar/ui/SidebarRoot.tsx @@ -54,6 +54,7 @@ import { ProgramsSeen } from "../../Programs/ui/ProgramsRoot"; import { InvitationsSeen } from "../../Faction/ui/FactionsRoot"; import { hash } from "../../hash/hash"; import { Locations } from "../../Locations/Locations"; +import { useRerender } from "../../ui/React/hooks"; const RotatedDoubleArrowIcon = React.forwardRef((props: { color: "primary" | "secondary" | "error" }, __ref) => ( @@ -108,15 +109,7 @@ interface IProps { } export function SidebarRoot(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); + useRerender(200); let flash: Page | null = null; switch (ITutorial.currStep) { diff --git a/src/StockMarket/ui/StockMarketRoot.tsx b/src/StockMarket/ui/StockMarketRoot.tsx index 8fd8ce176..79d4b3615 100644 --- a/src/StockMarket/ui/StockMarketRoot.tsx +++ b/src/StockMarket/ui/StockMarketRoot.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import { InfoAndPurchases } from "./InfoAndPurchases"; import { StockTickers } from "./StockTickers"; @@ -6,6 +6,7 @@ import { StockTickers } from "./StockTickers"; import { IStockMarket } from "../IStockMarket"; import { Player } from "@player"; +import { useRerender } from "../../ui/React/hooks"; type IProps = { stockMarket: IStockMarket; @@ -13,15 +14,7 @@ type IProps = { /** Root React component for the Stock Market UI */ export function StockMarketRoot(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); + const rerender = useRerender(200); return ( <> diff --git a/src/StockMarket/ui/StockTickers.tsx b/src/StockMarket/ui/StockTickers.tsx index 7a7f0a124..71a17aad0 100644 --- a/src/StockMarket/ui/StockTickers.tsx +++ b/src/StockMarket/ui/StockTickers.tsx @@ -10,13 +10,14 @@ import { StockTickersConfig, TickerDisplayMode } from "./StockTickersConfig"; import { IStockMarket } from "../IStockMarket"; import { Stock } from "../Stock"; +import { useRerender } from "../../ui/React/hooks"; type IProps = { stockMarket: IStockMarket; }; export function StockTickers(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; + const rerender = useRerender(); const [tickerDisplayMode, setTickerDisplayMode] = useState(TickerDisplayMode.AllStocks); const [watchlistSymbols, setWatchlistSymbols] = useState([]); @@ -39,10 +40,6 @@ export function StockTickers(props: IProps): React.ReactElement { } } - function rerender(): void { - setRerender((old) => !old); - } - const tickers: React.ReactElement[] = []; for (const stockMarketProp of Object.keys(props.stockMarket)) { const val = props.stockMarket[stockMarketProp]; diff --git a/src/Terminal/ui/TerminalRoot.tsx b/src/Terminal/ui/TerminalRoot.tsx index 7f983b0af..f7e8ebaeb 100644 --- a/src/Terminal/ui/TerminalRoot.tsx +++ b/src/Terminal/ui/TerminalRoot.tsx @@ -16,6 +16,7 @@ import { CodingContractModal } from "../../ui/React/CodingContractModal"; import _ from "lodash"; import { ANSIITypography } from "../../ui/React/ANSIITypography"; +import { useRerender } from "../../ui/React/hooks"; function ActionTimer(): React.ReactElement { return ( @@ -44,11 +45,8 @@ const useStyles = makeStyles((theme: Theme) => export function TerminalRoot(): React.ReactElement { const scrollHook = useRef(null); - const setRerender = useState(0)[1]; + const rerender = useRerender(); const [key, setKey] = useState(0); - function rerender(): void { - setRerender((old) => old + 1); - } function clear(): void { setKey((key) => key + 1); diff --git a/src/ui/ActiveScripts/ActiveScriptsRoot.tsx b/src/ui/ActiveScripts/ActiveScriptsRoot.tsx index fb606a9d7..b62d4ee33 100644 --- a/src/ui/ActiveScripts/ActiveScriptsRoot.tsx +++ b/src/ui/ActiveScripts/ActiveScriptsRoot.tsx @@ -2,30 +2,23 @@ * Root React Component for the "Active Scripts" UI page. This page displays * and provides information about all of the player's scripts that are currently running */ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; import { ActiveScriptsPage } from "./ActiveScriptsPage"; import { RecentScriptsPage } from "./RecentScriptsPage"; import { WorkerScript } from "../../Netscript/WorkerScript"; +import { useRerender } from "../React/hooks"; interface IProps { workerScripts: Map; } export function ActiveScriptsRoot(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); - const [tab, setTab] = useState<"active" | "recent">("active"); + useRerender(200); + function handleChange(event: React.SyntheticEvent, tab: "active" | "recent"): void { setTab(tab); } diff --git a/src/ui/ActiveScripts/ServerAccordions.tsx b/src/ui/ActiveScripts/ServerAccordions.tsx index c1a569bb0..b092e31c2 100644 --- a/src/ui/ActiveScripts/ServerAccordions.tsx +++ b/src/ui/ActiveScripts/ServerAccordions.tsx @@ -16,6 +16,7 @@ import { BaseServer } from "../../Server/BaseServer"; import { Settings } from "../../Settings/Settings"; import { TablePaginationActionsAll } from "../React/TablePaginationActionsAll"; import SearchIcon from "@mui/icons-material/Search"; +import { useRerender } from "../React/hooks"; // Map of server hostname -> all workerscripts on that server for all active scripts interface IServerData { @@ -35,7 +36,7 @@ export function ServerAccordions(props: IProps): React.ReactElement { const [filter, setFilter] = useState(""); const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(Settings.ActiveScriptsServerPageSize); - const setRerender = useState(false)[1]; + const rerender = useRerender(); const handleChangePage = (event: unknown, newPage: number): void => { setPage(newPage); @@ -78,10 +79,6 @@ export function ServerAccordions(props: IProps): React.ReactElement { (data.server.hostname.includes(filter) || data.server.runningScripts.find((s) => s.filename.includes(filter))), ); - function rerender(): void { - setRerender((old) => !old); - } - useEffect(() => WorkerScriptStartStopEventEmitter.subscribe(rerender)); return ( diff --git a/src/ui/CharacterStats.tsx b/src/ui/CharacterStats.tsx index 68eecf604..2d6a1a12b 100644 --- a/src/ui/CharacterStats.tsx +++ b/src/ui/CharacterStats.tsx @@ -1,6 +1,6 @@ import { Paper, Table, TableBody, Box, IconButton, Typography, Container, Tooltip } from "@mui/material"; import { MoreHoriz, Info } from "@mui/icons-material"; -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { BitNodes, defaultMultipliers, getBitNodeMultipliers } from "../BitNode/BitNode"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliersDisplay } from "../BitNode/ui/BitnodeMultipliersDescription"; @@ -16,6 +16,7 @@ import { Money } from "./React/Money"; import { StatsRow } from "./React/StatsRow"; import { StatsTable } from "./React/StatsTable"; import { isEqual } from "lodash"; +import { useRerender } from "./React/hooks"; interface EmployersModalProps { open: boolean; @@ -199,15 +200,7 @@ function MoneyModal({ open, onClose }: IMoneyModalProps): React.ReactElement { export function CharacterStats(): React.ReactElement { const [moneyOpen, setMoneyOpen] = useState(false); const [employersOpen, setEmployersOpen] = useState(false); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, 200); - return () => clearInterval(id); - }, []); + useRerender(200); const timeRows = [ ["Since last Augmentation installation", convertTimeMsToTimeElapsedString(Player.playtimeSinceLastAug)], diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 8b2a3d8fc..9b53a9bd7 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -77,6 +77,7 @@ import { Apr1 } from "./Apr1"; import { isFactionWork } from "../Work/FactionWork"; import { V2Modal } from "../utils/V2Modal"; import { MathJaxContext } from "better-react-mathjax"; +import { useRerender } from "./React/hooks"; const htmlLocation = location; @@ -127,7 +128,7 @@ export function GameRoot(): React.ReactElement { const classes = useStyles(); const [{ files, vim }, setEditorOptions] = useState({ files: {}, vim: false }); const [page, setPage] = useState(determineStartPage()); - const setRerender = useState(0)[1]; + const rerender = useRerender(); const [augPage, setAugPage] = useState(false); const [faction, setFaction] = useState( isFactionWork(Player.currentWork) ? Factions[Player.currentWork.factionName] : (undefined as unknown as Faction), @@ -155,9 +156,6 @@ export function GameRoot(): React.ReactElement { setErrorBoundaryKey(errorBoundaryKey + 1); } - function rerender(): void { - setRerender((old) => old + 1); - } useEffect(() => { return ITutorialEvents.subscribe(rerender); }, []); diff --git a/src/ui/InteractiveTutorial/InteractiveTutorialRoot.tsx b/src/ui/InteractiveTutorial/InteractiveTutorialRoot.tsx index b20f9c124..c61549372 100644 --- a/src/ui/InteractiveTutorial/InteractiveTutorialRoot.tsx +++ b/src/ui/InteractiveTutorial/InteractiveTutorialRoot.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useEffect } from "react"; import Paper from "@mui/material/Paper"; import Typography from "@mui/material/Typography"; @@ -27,6 +27,7 @@ import { iTutorialSteps, iTutorialEnd, } from "../../InteractiveTutorial"; +import { useRerender } from "../React/hooks"; interface IContent { content: React.ReactElement; @@ -47,6 +48,7 @@ const useStyles = makeStyles((theme: Theme) => export function InteractiveTutorialRoot(): React.ReactElement { const classes = useStyles(); + const rerender = useRerender(); const tutorialScriptName = `n00dles.js`; @@ -556,11 +558,6 @@ export function InteractiveTutorialRoot(): React.ReactElement { }, }; - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - useEffect(() => { return ITutorialEvents.subscribe(rerender); }, []); diff --git a/src/ui/React/LogBoxManager.tsx b/src/ui/React/LogBoxManager.tsx index a9a95bf52..edb7d1232 100644 --- a/src/ui/React/LogBoxManager.tsx +++ b/src/ui/React/LogBoxManager.tsx @@ -19,6 +19,7 @@ import { debounce } from "lodash"; import { Settings } from "../../Settings/Settings"; import { ANSIITypography } from "./ANSIITypography"; import { ScriptArg } from "../../Netscript/ScriptArg"; +import { useRerender } from "./hooks"; let layerCounter = 0; @@ -53,10 +54,7 @@ interface Log { let logs: Log[] = []; export function LogBoxManager(): React.ReactElement { - const setRerender = useState(true)[1]; - function rerender(): void { - setRerender((o) => !o); - } + const rerender = useRerender(); useEffect( () => LogBoxEvents.subscribe((script: RunningScript) => { @@ -140,12 +138,9 @@ function LogWindow(props: IProps): React.ReactElement { const classes = useStyles(); const container = useRef(null); const textArea = useRef(null); - const setRerender = useState(false)[1]; + const rerender = useRerender(1000); const [size, setSize] = useState<[number, number]>([500, 500]); const [minimized, setMinimized] = useState(false); - function rerender(): void { - setRerender((old) => !old); - } const textAreaKeyDown = (e: React.KeyboardEvent) => { if (e.ctrlKey && e.key === "a") { @@ -214,8 +209,6 @@ function LogWindow(props: IProps): React.ReactElement { useEffect(() => { updateLayer(); - const id = setInterval(rerender, 1000); - return () => clearInterval(id); }, []); function kill(): void { diff --git a/src/ui/React/hooks.ts b/src/ui/React/hooks.ts new file mode 100644 index 000000000..e682c855b --- /dev/null +++ b/src/ui/React/hooks.ts @@ -0,0 +1,15 @@ +import { useEffect, useState } from "react"; + +/** Hook that returns a function for the component. Optionally set an interval to rerender the component. + * @param autoRerenderTime: Optional. If provided and nonzero, used as the ms interval to automatically call the rerender function. + */ +export function useRerender(autoRerenderTime?: number) { + const setRerender = useState(false)[1]; + const rerender = () => setRerender((old) => !old); + useEffect(() => { + if (!autoRerenderTime) return; + const intervalID = setInterval(rerender, autoRerenderTime); + return () => clearInterval(intervalID); + }, []); + return rerender; +} diff --git a/src/ui/WorkInProgressRoot.tsx b/src/ui/WorkInProgressRoot.tsx index 22e95377f..cdc4e8b11 100644 --- a/src/ui/WorkInProgressRoot.tsx +++ b/src/ui/WorkInProgressRoot.tsx @@ -2,7 +2,7 @@ import { Box, Container, Paper, Table, TableBody, Tooltip } from "@mui/material" import Button from "@mui/material/Button"; import Typography from "@mui/material/Typography"; import { uniqueId } from "lodash"; -import React, { useEffect, useState } from "react"; +import React from "react"; import { Companies } from "../Company/Companies"; import { CONSTANTS } from "../Constants"; import { LocationName } from "../Enums"; @@ -27,6 +27,7 @@ import { isGraftingWork } from "../Work/GraftingWork"; import { isFactionWork } from "../Work/FactionWork"; import { FactionWorkType } from "../Enums"; import { isCompanyWork } from "../Work/CompanyWork"; +import { useRerender } from "./React/hooks"; const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle; @@ -193,15 +194,7 @@ function CrimeExpRows(rate: WorkStats): React.ReactElement[] { } export function WorkInProgressRoot(): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender((old) => !old); - } - - useEffect(() => { - const id = setInterval(rerender, CONSTANTS.MilliPerCycle); - return () => clearInterval(id); - }, []); + useRerender(CONSTANTS.MilliPerCycle); let workInfo: IWorkInfo = { buttons: {