Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "vis-demo",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["--filter", "@spatialdata/vis", "dev:demo"],
"port": 5173
}
Comment thread
xinaesthete marked this conversation as resolved.
]
}
33 changes: 33 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,39 @@ concurrency:
cancel-in-progress: true

jobs:
react-lint:
runs-on: ubuntu-latest
permissions:
contents: read
# Surfaces React Hooks / React Compiler (Rules-of-React) findings for the
# React-shipping packages via eslint-plugin-react-hooks. Informational for
# now: there is an existing backlog of findings, so this must not gate merges.
steps:
- uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false

- uses: actions/setup-node@v4
with:
node-version: '24.14.1'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Lint React packages (react-hooks / react-compiler rules)
# continue-on-error (at the step, not the job) keeps this check green
# while the backlog exists, so it does not read as a failing/required
# check; findings still show in this step's log. Remove this line once
# `pnpm lint:react` is clean to turn it into a required gate.
continue-on-error: true
run: pnpm lint:react

test:
runs-on: ubuntu-latest
steps:
Expand Down
26 changes: 26 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Scoped ESLint setup that exists ONLY to run the React Hooks / React Compiler
// rules from eslint-plugin-react-hooks v7 against the React-shipping packages.
// Biome remains the primary linter/formatter for the repo (see biome.json); this
// covers the Rules-of-React analysis Biome does not implement. Run via `pnpm lint:react`.
import tsParser from '@typescript-eslint/parser';
import reactHooks from 'eslint-plugin-react-hooks';

export default [
{
files: ['packages/react/src/**/*.{ts,tsx}', 'packages/vis/src/**/*.{ts,tsx}'],
plugins: {
'react-hooks': reactHooks,
},
languageOptions: {
parser: tsParser,
parserOptions: {
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
},
// The full recommended set: rules-of-hooks + exhaustive-deps plus the
// granular React Compiler diagnostics (immutability, refs, purity, globals,
// set-state-in-render/effect, static-components, …).
rules: reactHooks.configs['recommended-latest'].rules,
},
];
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"test:proxy": "node scripts/cors-proxy.js",
"validate:datasets:js": "node scripts/validate-datasets-js.js",
"lint": "biome check .",
"lint:react": "eslint \"packages/react/src/**/*.{ts,tsx}\" \"packages/vis/src/**/*.{ts,tsx}\"",
"format": "biome format --write .",
"format:check": "biome format .",
"dev": "pnpm -r --parallel dev",
Expand All @@ -42,7 +43,12 @@
"@biomejs/biome": "^1.9.4",
"@changesets/changelog-github": "^0.7.0",
"@changesets/cli": "^2.31.0",
"@typescript-eslint/parser": "^8.62.0",
"eslint": "^10.6.0",
"eslint-plugin-react-hooks": "^7.1.1",
"@rolldown/plugin-babel": "catalog:",
"@vitejs/plugin-react": "catalog:",
"babel-plugin-react-compiler": "catalog:",
"@zarrita/storage": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
Expand Down
2 changes: 2 additions & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@rolldown/plugin-babel": "catalog:",
"@vitejs/plugin-react": "catalog:",
"babel-plugin-react-compiler": "catalog:",
"jsdom": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
Expand Down
3 changes: 2 additions & 1 deletion packages/react/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { fileURLToPath } from 'node:url';
import { defineViteConfig } from '../../vite.config.base';
import { mergeConfig } from 'vite';
import { defineViteConfig } from '../../vite.config.base';

const pkgRoot = fileURLToPath(new URL('.', import.meta.url));

const baseConfig = defineViteConfig({
pkgRoot,
libName: 'SpatialDataReact',
external: ['@spatialdata/core'],
reactCompiler: true,
});

export default mergeConfig(baseConfig, {
Expand Down
2 changes: 2 additions & 0 deletions packages/vis/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@rolldown/plugin-babel": "catalog:",
"@vitejs/plugin-react": "catalog:",
"babel-plugin-react-compiler": "catalog:",
"jsdom": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
Expand Down
8 changes: 8 additions & 0 deletions packages/vis/src/SpatialCanvas/SpatialCanvasViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ export function useSpatialCanvasRendererFromLayerInputs({
}, [externalDeckLayers, generatedDeckLayers, hostDeckLayers, resolvedLayerOrder, sortDeckLayers]);
const vivLayerProps = useMemo(
() => layerData.getVivLayerProps(),
// useLayerData returns a fresh object every render, so we intentionally depend
// on its stable members (the useCallback'd getter plus the memoized load flags)
// rather than `layerData` itself, which would recompute this on every render.
// eslint-disable-next-line react-hooks/exhaustive-deps
[layerData.getVivLayerProps, vivPassthrough, layerData.isBlocking, layerData.isLoading]
);

Expand Down Expand Up @@ -262,6 +266,10 @@ export function useSpatialCanvasRendererFromLayerInputs({
onViewStateChange(
bounds ? viewStateFromBounds(bounds, width, height) : { target: [0, 0], zoom: 0 }
);
// useLayerData returns a fresh object every render, so we intentionally depend
// on its stable members (memoized flag + useCallback'd bounds getter) rather
// than `layerData` itself, which would re-run this effect on every render.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
autoFit,
hasEnabledLayers,
Expand Down
11 changes: 7 additions & 4 deletions packages/vis/src/Tree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ export default function SpatialDataTree() {
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!spatialData) return <div>No spatial data</div>;
// Resolve the value to display before rendering: JSX is lazy, so wrapping the
// returned <JsonView/> in try/catch would not catch a throw from toJSON().
let value: object = spatialData;
try {
const json = spatialData.toJSON();
if (!json) {
throw new Error('SpatialData.toJSON() falsey, this should never happen');
if (json) {
value = json;
}
return <JsonView value={json} style={darkTheme} collapsed={true} />;
} catch {
return <JsonView value={spatialData} style={darkTheme} collapsed={true} />;
value = spatialData;
}
return <JsonView value={value} style={darkTheme} collapsed={true} />;
}
11 changes: 6 additions & 5 deletions packages/vis/vite.config.demo.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'node:path';
import { createRequire } from 'node:module';
import { createWorkspaceSourceAliases } from '../../vite.config.base';
import path from 'node:path';
import babel from '@rolldown/plugin-babel';
import react, { reactCompilerPreset } from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import { fixtureServerOrigin } from '../../scripts/fixture-server-port.mjs';
import { createWorkspaceSourceAliases } from '../../vite.config.base';

// https://vitejs.dev/config/
const workspaceRoot = path.resolve(__dirname, '../..');
Expand All @@ -13,7 +14,7 @@ const reactDomRoot = path.dirname(require.resolve('react-dom/package.json'));

export default defineConfig({
root: path.resolve(__dirname, 'demo'),
plugins: [react()],
plugins: [react(), babel({ presets: [reactCompilerPreset()] })],
resolve: {
alias: [
...createWorkspaceSourceAliases(workspaceRoot),
Expand Down
1 change: 1 addition & 0 deletions packages/vis/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const baseConfig = defineViteConfig({
'zarrita',
/^zarrextra(?:\/.*)?$/,
],
reactCompiler: true,
});

export default mergeConfig(baseConfig, {
Expand Down
Loading
Loading