Skip to content

Commit ca65475

Browse files
committed
move performance demo to a separate app
1 parent 9337bf1 commit ca65475

File tree

10 files changed

+258
-255
lines changed

10 files changed

+258
-255
lines changed

docs/demos/strictmode/README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
# Strict mode application demo
1+
# Strict mode and performance application demo
2+
3+
Frequent state updates demo. 10K cells table updating 1 cell every millisecond(*).
4+
5+
* - results maybe different depending on a machine performance
26

37
## Workflow
48

59
From the repository root directory:
610

711
- `pnpm install`
8-
- `pnpm nx build strictmode`
9-
- `pnpm nx start strictmode`
10-
- `pnpm nx test strictmode`
12+
- `pnpm nx start strictmode` - slow performance demo, for debugging only
13+
- `pnpm nx build strictmode` - build in release mode
14+
- `pnpm nx serve strictmode` - open in browser and see it running
15+
16+
## Screenshot
17+
18+
![frequent state updates](hookstate-performance-demo.png "Hookstate frequent state updates demo")
141 KB
Loading

docs/demos/strictmode/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
"react": "^18.2.0",
1616
"react-dom": "^18.2.0",
1717
"react-scripts": "5.0.1",
18+
"serve": "14.1.2",
1819
"typescript": "^4.7.4",
1920
"web-vitals": "^2.1.4"
2021
},
2122
"scripts": {
2223
"start": "react-scripts start",
2324
"build": "react-scripts build",
25+
"serve": "serve -s build",
2426
"test": "react-scripts test",
2527
"eject": "react-scripts eject",
2628
"update:deps": "ncu -u"
@@ -43,4 +45,4 @@
4345
"last 1 safari version"
4446
]
4547
}
46-
}
48+
}
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import React from 'react';
22
import { render, screen } from '@testing-library/react';
3-
import App from './App';
3+
import { App } from './App';
44

55
test('renders learn react link', () => {
66
render(<App />);
7-
const linkElement = screen.getByText(/learn react/i);
8-
expect(linkElement).toBeInTheDocument();
97
});

docs/demos/strictmode/src/App.tsx

Lines changed: 157 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,172 @@
1-
import { hookstate, State, useHookstate } from '@hookstate/core';
2-
import { useEffect } from 'react';
3-
import './App.css';
1+
import React, { useEffect } from 'react';
2+
import { useHookstate, State } from '@hookstate/core';
43

5-
const createId = () => {
6-
return Math.floor(Math.random() * 0xffffff).toString(16);
7-
};
8-
9-
interface IItem {
10-
id: string;
11-
kind: string;
12-
type: string;
4+
const TableCell = (props: { cell: State<number> }) => {
5+
const scopedState = useHookstate(props.cell);
6+
return <>{scopedState.value.toString(16)}</>;
137
}
148

15-
interface IAppState {
16-
items: Record<string, IItem>;
9+
type StatsExtension = {
10+
stats: {
11+
totalSum: number;
12+
totalCalls: number;
13+
startTime: number;
14+
};
1715
}
1816

19-
const appState = hookstate<IAppState>({
20-
items: {
21-
initial: {
22-
id: 'initial',
23-
kind: 'UNKNOWN',
24-
type: 'test',
25-
},
26-
},
27-
});
28-
29-
function App() {
30-
const state = useHookstate(appState);
17+
const MatrixView = (props: {
18+
totalRows: number,
19+
totalColumns: number,
20+
interval: number,
21+
callsPerInterval: number
22+
}) => {
23+
const matrix = useHookstate(
24+
() => Array.from(Array(props.totalRows).keys())
25+
.map(i => Array.from(Array(props.totalColumns).keys()).map(j => 0)),
26+
() => {
27+
const stats = {
28+
totalSum: 0,
29+
totalCalls: 0,
30+
startTime: (new Date()).getTime()
31+
};
3132

33+
let previous = 0;
34+
return {
35+
onCreate: () => ({
36+
stats: () => stats
37+
}),
38+
onPreset: (s) => {
39+
if (s.path.length === 2) {
40+
previous = s.get({ stealth: true });
41+
}
42+
},
43+
onSet: (s) => {
44+
if (s.path.length === 2) {
45+
// new value can be only number in this example
46+
// and path can contain only 2 elements: row and column indexes
47+
stats.totalSum += s.value - previous;
48+
}
49+
stats.totalCalls += 1;
50+
}
51+
}
52+
}
53+
);
54+
// schedule interval updates
3255
useEffect(() => {
33-
console.log('Keys changed.');
34-
}, [state.items.keys]);
56+
const t = setInterval(() => {
57+
function randomInt(min: number, max: number) {
58+
min = Math.ceil(min);
59+
max = Math.floor(max);
60+
return Math.floor(Math.random() * (max - min)) + min;
61+
}
62+
for (let i = 0; i < props.callsPerInterval; i += 1) {
63+
matrix
64+
[randomInt(0, props.totalRows)]
65+
[randomInt(0, props.totalColumns)]
66+
.set(p => p + randomInt(0, 5))
67+
}
68+
}, props.interval)
69+
return () => clearInterval(t);
70+
}, [props.interval, props.callsPerInterval, props.totalRows, props.totalColumns])
71+
72+
return <div style={{ overflow: 'scroll' }}>
73+
<PerformanceMeter matrix={matrix} />
74+
<table
75+
style={{
76+
border: 'solid',
77+
borderWidth: 1,
78+
borderColor: 'grey',
79+
color: '#00FF00',
80+
backgroundColor: 'black'
81+
}}
82+
>
83+
<tbody>
84+
{matrix.map((rowState, rowIndex: number) =>
85+
<tr key={rowIndex}>
86+
{rowState.map((cellState, columnIndex) =>
87+
<td key={columnIndex}>
88+
<TableCell cell={cellState} />
89+
</td>
90+
)}
91+
</tr>
92+
)}
93+
</tbody>
94+
</table>
95+
</div>
96+
}
3597

36-
return (
98+
export const ExampleComponent = () => {
99+
const settings = useHookstate({
100+
rows: 50,
101+
columns: 50,
102+
rate: 50,
103+
timer: 10
104+
})
105+
return <>
37106
<div>
38-
<div>
39-
<button
40-
onClick={() => {
41-
const id = createId();
42-
// state.items.merge({ [id]: { id, kind: 'UNKNOWN', type: 'test' } });
43-
state.items[id].set({ id, kind: 'UNKNOWN', type: 'test' });
44-
}}
45-
>
46-
Add
47-
</button>
48-
</div>
49-
<div>
50-
{state.items.keys.map((itemId) => (
51-
<Item key={itemId} state={state.items[itemId]} />
52-
))}
53-
</div>
107+
<p><span>Total rows: {settings.rows.value} </span>
108+
<button onClick={() =>
109+
settings.rows.set(p => (p - 10) || 10)}>-10</button>
110+
<button onClick={() =>
111+
settings.rows.set(p => p + 10)}>+10</button></p>
112+
<p><span>Total columns: {settings.columns.value} </span>
113+
<button onClick={() =>
114+
settings.columns.set(p => (p - 10) || 10)}>-10</button>
115+
<button onClick={() =>
116+
settings.columns.set(p => p + 10)}>+10</button></p>
117+
<p>Total cells: {settings.columns.value * settings.rows.value}</p>
118+
<p><span>Cells to update per timer interval: {settings.rate.value} </span>
119+
<button onClick={() =>
120+
settings.rate.set(p => (p - 1) || 1)}>-1</button>
121+
<button onClick={() =>
122+
settings.rate.set(p => p + 1)}>+1</button>
123+
<button onClick={() =>
124+
settings.rate.set(p => p > 10 ? (p - 10) : 1)}>-10</button>
125+
<button onClick={() =>
126+
settings.rate.set(p => p + 10)}>+10</button>
127+
</p>
128+
<p><span>Timer interval in ms: {settings.timer.value} </span>
129+
<button onClick={() =>
130+
settings.timer.set(p => p > 1 ? (p - 1) : 1)}>-1</button>
131+
<button onClick={() =>
132+
settings.timer.set(p => p + 1)}>+1</button>
133+
<button onClick={() =>
134+
settings.timer.set(p => p > 10 ? (p - 10) : 1)}>-10</button>
135+
<button onClick={() =>
136+
settings.timer.set(p => p + 10)}>+10</button>
137+
</p>
54138
</div>
55-
);
139+
<MatrixView
140+
key={Math.random()}
141+
totalRows={settings.rows.value}
142+
totalColumns={settings.columns.value}
143+
interval={settings.timer.value}
144+
callsPerInterval={settings.rate.value}
145+
/>
146+
</>;
56147
}
57148

58-
function Item(props: { state: State<IItem> }) {
59-
const state = useHookstate(props.state);
149+
function PerformanceMeter(props: { matrix: State<number[][], StatsExtension> }) {
150+
let stats = props.matrix.stats;
151+
const elapsedMs = () => (new Date()).getTime() - stats.startTime;
152+
const elapsed = () => Math.floor(elapsedMs() / 1000);
153+
const rate = Math.floor(stats.totalCalls / elapsedMs() * 1000);
60154

61-
return (
62-
<div
63-
style={{ padding: 10, backgroundColor: '#' + Math.floor(Math.random() * 0xffffff).toString(16) }}
64-
onClick={() => {
65-
state.type.set(Math.random().toString(16));
66-
}}
67-
>
68-
{JSON.stringify(state.value)}
69-
</div>
70-
);
155+
// this makes rerendering this component every 200ms
156+
let forceRerender = useHookstate(1);
157+
useEffect(() => {
158+
const interval = setInterval(() => {
159+
forceRerender.set(p => p + 1)
160+
}, 200)
161+
return () => clearInterval(interval)
162+
}, [])
163+
164+
return <>
165+
<p><span>Elapsed: {elapsed()}s</span></p>
166+
<p><span>Total cells sum: {stats.totalSum}</span></p>
167+
<p><span>Total matrix state updates: {stats.totalCalls}</span></p>
168+
<p><span>Average update rate: {rate}cells/s</span></p>
169+
</>;
71170
}
72171

73-
export default App;
172+
export const App = ExampleComponent

docs/demos/strictmode/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom/client';
33
import './index.css';
4-
import App from './App';
4+
import { App } from './App';
55
import reportWebVitals from './reportWebVitals';
66

77
const root = ReactDOM.createRoot(

docs/index/docs/23-performance-frequent-updates.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ import { PreviewSample } from '../src/PreviewSample'
88

99
Here is an example of 10000 cells table which achieves an update rate 1+ cells per every millisecond.
1010

11-
<PreviewSample example="performance-demo-large-table" />
11+
Unfortunately, this example cannot run embedded within the documentation. Check it out [here](https://github.com/avkonst/hookstate/tree/master/docs/demos/strictmode).
12+
1213

docs/index/src/examples/Index.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { ExampleComponent as ExampleLocalPrimitive } from './local-getting-start
66
import { ExampleComponent as ExampleAsyncState } from './local-async-state';
77
import { ExampleComponent as ExampleLocalComplexFromDocumentation } from './local-complex-from-documentation';
88
import { ExampleComponent as ExampleLocalComplexTreeStructure } from './local-complex-tree-structure';
9-
import { ExampleComponent as ExamplePerformanceLargeTable } from './performance-demo-large-table';
109
import { ExampleComponent as ExamplePerformanceLargeForm } from './performance-demo-large-form';
1110
import { ExampleComponent as ExampleGlobalMultipleConsumersStateFragment } from './global-multiple-consumers-statefragment';
1211

@@ -31,7 +30,6 @@ ExamplesRepo.set('local-getting-started', <ExampleLocalPrimitive />);
3130
ExamplesRepo.set('local-complex-from-documentation', <ExampleLocalComplexFromDocumentation />);
3231
ExamplesRepo.set('local-async-state', <ExampleAsyncState />);
3332
ExamplesRepo.set('local-complex-tree-structure', <ExampleLocalComplexTreeStructure />);
34-
ExamplesRepo.set('performance-demo-large-table', <ExamplePerformanceLargeTable />);
3533
ExamplesRepo.set('performance-demo-large-form', <ExamplePerformanceLargeForm />);
3634
ExamplesRepo.set('global-multiple-consumers-statefragment', <ExampleGlobalMultipleConsumersStateFragment />);
3735

0 commit comments

Comments
 (0)