From 546050ee5fab57d0df37f0a25a8f6dfa7c7754cc Mon Sep 17 00:00:00 2001 From: Maxime Jacquemin <maxime2.jacquemin@gmail.com> Date: Wed, 27 Oct 2021 18:19:30 +0200 Subject: [PATCH] [ivette] Fixing react unmounted component bug The Valuecomponent had lots of weird bugs, triggering the warning <Can't perform a React state update on an unmounted component>. This was caused by an ugly, full of unsafe effect, way to handle the Model used by a lot of components used by the ValueComponent Now, the Model is created only once by the ValueComponent itself, and given as a property to every component that needs it. As a consequence, the useModel() function is now useless. --- ivette/src/frama-c/plugins/eva/index.tsx | 13 +++++++--- ivette/src/frama-c/plugins/eva/model.ts | 24 ++----------------- ivette/src/frama-c/plugins/eva/probeinfos.tsx | 12 +++++----- ivette/src/frama-c/plugins/eva/valueinfos.tsx | 10 ++++---- ivette/src/frama-c/plugins/eva/valuetable.tsx | 15 ++++++------ 5 files changed, 31 insertions(+), 43 deletions(-) diff --git a/ivette/src/frama-c/plugins/eva/index.tsx b/ivette/src/frama-c/plugins/eva/index.tsx index 1d862289c35..46b6fac4e5b 100644 --- a/ivette/src/frama-c/plugins/eva/index.tsx +++ b/ivette/src/frama-c/plugins/eva/index.tsx @@ -28,9 +28,11 @@ import React from 'react'; import * as Dome from 'dome'; import * as Ivette from 'ivette'; +import * as Server from 'frama-c/server'; import { Vfill } from 'dome/layout/boxes'; import { IconButton } from 'dome/controls/buttons'; import { AutoSizer } from 'react-virtualized'; +import { Model } from './model'; // Locals import { ProbeInfos } from './probeinfos'; @@ -45,6 +47,10 @@ import './style.css'; // -------------------------------------------------------------------------- function ValuesComponent() { + const [model] = React.useState(() => new Model()); + model.mount(); + Dome.useUpdate(model.changed, model.laidout); + Server.onShutdown(() => model.unmount()); const [zoom, setZoom] = Dome.useNumberSettings('eva-zoom-factor', 0); return ( <> @@ -61,19 +67,20 @@ function ValuesComponent() { /> </Ivette.TitleBar> <Vfill> - <ProbeInfos /> + <ProbeInfos model={model} /> <Vfill> <AutoSizer> {(dim: Dimension) => ( <ValuesPanel zoom={zoom} + model={model} {...dim} /> )} </AutoSizer> </Vfill> - <AlarmsInfos /> - <StackInfos /> + <AlarmsInfos model={model} /> + <StackInfos model={model} /> </Vfill> </> ); diff --git a/ivette/src/frama-c/plugins/eva/model.ts b/ivette/src/frama-c/plugins/eva/model.ts index a267c0e5de4..9d73106c3d8 100644 --- a/ivette/src/frama-c/plugins/eva/model.ts +++ b/ivette/src/frama-c/plugins/eva/model.ts @@ -280,26 +280,6 @@ export class Model implements ModelCallbacks { } -// -------------------------------------------------------------------------- -// --- EVA Model Hook -// -------------------------------------------------------------------------- - -let MODEL: Model | undefined; - -Server.onShutdown(() => { - if (MODEL) { - MODEL.unmount(); - MODEL = undefined; - } -}); - -export function useModel(): Model { - if (!MODEL) { - MODEL = new Model(); - MODEL.mount(); - } - Dome.useUpdate(MODEL.changed, MODEL.laidout); - return MODEL; +export interface ModelProp { + model: Model; } - -// -------------------------------------------------------------------------- diff --git a/ivette/src/frama-c/plugins/eva/probeinfos.tsx b/ivette/src/frama-c/plugins/eva/probeinfos.tsx index 230afeac91d..cd2686661c1 100644 --- a/ivette/src/frama-c/plugins/eva/probeinfos.tsx +++ b/ivette/src/frama-c/plugins/eva/probeinfos.tsx @@ -37,15 +37,15 @@ import * as States from 'frama-c/states'; // Locals import { SizedArea } from './sized'; import { sizeof } from './cells'; -import { useModel } from './model'; +import { ModelProp } from './model'; import { Stmt } from './valueinfos'; // -------------------------------------------------------------------------- // --- Probe Editor // -------------------------------------------------------------------------- -function ProbeEditor() { - const model = useModel(); +function ProbeEditor(props: ModelProp) { + const { model } = props; const probe = model.getFocused(); if (!probe || !probe.code) return null; const { label } = probe; @@ -105,8 +105,8 @@ function ProbeEditor() { // --- Probe Panel // -------------------------------------------------------------------------- -export function ProbeInfos() { - const model = useModel(); +export function ProbeInfos(props: ModelProp) { + const { model } = props; const probe = model.getFocused(); const fct = probe?.fct; const byCS = fct ? model.isByCallstacks(fct) : false; @@ -117,7 +117,7 @@ export function ProbeInfos() { const vstmt = model.getVstmt(); return ( <Hpack className="eva-probeinfo"> - <ProbeEditor /> + <ProbeEditor model={model} /> <Filler /> <ButtonGroup enabled={!!probe} diff --git a/ivette/src/frama-c/plugins/eva/valueinfos.tsx b/ivette/src/frama-c/plugins/eva/valueinfos.tsx index d474b6ce9f5..0812d0f2b60 100644 --- a/ivette/src/frama-c/plugins/eva/valueinfos.tsx +++ b/ivette/src/frama-c/plugins/eva/valueinfos.tsx @@ -31,11 +31,11 @@ import { Hpack, Vpack } from 'dome/layout/boxes'; import { Code, Cell } from 'dome/controls/labels'; import * as States from 'frama-c/states'; import * as Ast from 'frama-c/api/kernel/ast'; +import { ModelProp } from 'frama-c/plugins/eva/model'; // Locals import { EvaAlarm } from './cells'; import { Callsite } from './stacks'; -import { useModel } from './model'; // -------------------------------------------------------------------------- // --- Stmt Printer @@ -66,8 +66,8 @@ export function Stmt(props: StmtProps) { // --- Alarms Panel // -------------------------------------------------------------------------- -export function AlarmsInfos() { - const model = useModel(); +export function AlarmsInfos(props: ModelProp) { + const { model } = props; const probe = model.getFocused(); if (probe) { const callstack = model.getCallstack(); @@ -94,8 +94,8 @@ export function AlarmsInfos() { // --- Stack Panel // -------------------------------------------------------------------------- -export function StackInfos() { - const model = useModel(); +export function StackInfos(props: ModelProp) { + const { model } = props; const [, setSelection] = States.useSelection(); const focused = model.getFocused(); const callstack = model.getCalls(); diff --git a/ivette/src/frama-c/plugins/eva/valuetable.tsx b/ivette/src/frama-c/plugins/eva/valuetable.tsx index 165e0788d82..577249f0764 100644 --- a/ivette/src/frama-c/plugins/eva/valuetable.tsx +++ b/ivette/src/frama-c/plugins/eva/valuetable.tsx @@ -33,6 +33,7 @@ import { Hpack, Filler } from 'dome/layout/boxes'; import { Icon } from 'dome/controls/icons'; import { Cell } from 'dome/controls/labels'; import { IconButton } from 'dome/controls/buttons'; +import { ModelProp, Model } from 'frama-c/plugins/eva/model'; // Frama-C import * as States from 'frama-c/states'; @@ -44,7 +45,6 @@ import { sizeof, EvaValues, EvaState } from './cells'; import { Probe } from './probes'; import { Row } from './layout'; import { Callsite } from './stacks'; -import { useModel } from './model'; import { Stmt } from './valueinfos'; import './style.css'; @@ -91,7 +91,7 @@ function computeDiffs( // --- Table Cell // -------------------------------------------------------------------------- -interface TableCellProps { +interface TableCellProps extends ModelProp { probe: Probe; row: Row; } @@ -99,9 +99,8 @@ interface TableCellProps { const CELLPADDING = 12; function TableCell(props: TableCellProps) { - const model = useModel(); + const { probe, row, model } = props; const [, setSelection] = States.useSelection(); - const { probe, row } = props; const { kind, callstack } = row; const minWidth = CELLPADDING + WSIZER.dimension(probe.minCols); const maxWidth = CELLPADDING + WSIZER.dimension(probe.maxCols); @@ -276,10 +275,11 @@ function TableHead(props: TableHeadProps) { interface TableRowProps { style: React.CSSProperties; index: number; + data: Model; } function TableRow(props: TableRowProps) { - const model = useModel(); + const model = props.data; const row = model.getRow(props.index); if (!row) return null; const { kind, probes } = row; @@ -319,6 +319,7 @@ function TableRow(props: TableRowProps) { key={probe.marker} probe={probe} row={row} + model={model} /> ); return ( @@ -348,11 +349,11 @@ export interface Dimension { export interface ValuesPanelProps extends Dimension { zoom: number; + model: Model; } export function ValuesPanel(props: ValuesPanelProps) { - const model = useModel(); - const { zoom, width, height } = props; + const { zoom, width, height, model } = props; // --- reset line cache const listRef = React.useRef<VariableSizeList>(null); Dome.useEvent(model.laidout, () => { -- GitLab