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
36 changes: 35 additions & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@
"react/require-render-return": "error",
"react/style-prop-object": "error",
"react/void-dom-elements-no-children": "error",
"react/error-boundaries": "off",
"react/globals": "off",
"react/immutability": "off",
"react/incompatible-library": "off",
"react/preserve-manual-memoization": "off",
"react/purity": "off",
"react/refs": "off",
"react/set-state-in-effect": "off",
"react/set-state-in-render": "off",
"react/static-components": "off",
"react/unsupported-syntax": "off",
"react/use-memo": "off",
"react/void-use-memo": "off",
"react/checked-requires-onchange-or-readonly": "error",
"react/forward-ref-uses-ref": "error",
"react/iframe-missing-sandbox": "error",
Expand Down Expand Up @@ -124,7 +137,22 @@
"react/button-has-type": "error",
"react/jsx-no-useless-fragment": "error",
"react/no-unstable-nested-components": "error",
"react/react-compiler": "error",
"react/error-boundaries": "error",
"react/globals": "error",
"react/hooks": "error",
"react/immutability": "error",
"react/incompatible-library": "error",
"react/memo-dependencies": "error",
"react/no-deriving-state-in-effects": "error",
Comment thread
carderne marked this conversation as resolved.
"react/preserve-manual-memoization": "error",
"react/purity": "error",
"react/refs": "error",
"react/set-state-in-effect": "error",
"react/set-state-in-render": "error",
"react/static-components": "error",
"react/unsupported-syntax": "error",
"react/use-memo": "error",
"react/void-use-memo": "error",
"react/rules-of-hooks": "error",
"trigger-runops/no-control-plane-run-graph-access": "error",
"trigger-runops/no-control-plane-in-runops-slot": "error"
Expand All @@ -136,6 +164,12 @@
"react/rules-of-hooks": "error"
}
},
{
"files": ["**/*.ts", "**/*.tsx"],
"rules": {
"no-redeclare": "off"
}
},
{
"files": ["apps/webapp/app/**/*.test.ts", "apps/webapp/app/**/*.test.tsx"],
"rules": {
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/AskAI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ function ChatMessages({
// Reset feedback state when conversation is reset
useEffect(() => {
if (conversation.length === 0) {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setFeedbackGivenForQAs(new Set());
}
}, [conversation.length]);
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/app/components/DevPresence.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function DevPresenceProvider({ children, enabled = true }: DevPresencePro
useEffect(() => {
// If disabled or no events
if (!enabled || streamedEvents === null) {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setIsConnected(undefined);
return;
}
Expand Down Expand Up @@ -114,7 +114,7 @@ export function useCrossEngineIsConnected({

useEffect(() => {
if (project.engine === "V2") {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setCrossEngineIsConnected(isConnected);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function Feedback({
useEffect(() => {
const open = searchParams.get("feedbackPanel");
if (open) {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setType(open as FeedbackType);
setOpen(true);
// Clone instead of mutating in place
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/LoginPageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function LoginPageLayout({
const [randomQuote, setRandomQuote] = useState<QuoteType | null>(null);
useEffect(() => {
const randomIndex = Math.floor(Math.random() * quotes.length);
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setRandomQuote(quotes[randomIndex]);
}, []);

Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/TriggerRotatingLogo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function TriggerRotatingLogo() {
useEffect(() => {
// Already registered from a previous render
if (customElements.get("spline-viewer")) {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setIsSplineReady(true);
return;
}
Expand Down
8 changes: 4 additions & 4 deletions apps/webapp/app/components/admin/FeatureFlagsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function FeatureFlagsDialog({
const saveFetcher = useFetcher<ActionData>();
const loadFeatureFlags = loadFetcher.load;
const onOpenChangeRef = useRef(onOpenChange);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
// oxlint-disable-next-line react/refs -- This ref intentionally coordinates an imperative integration outside React state.
onOpenChangeRef.current = onOpenChange;

const [overrides, setOverrides] = useState<Record<string, unknown>>({});
Expand All @@ -68,7 +68,7 @@ export function FeatureFlagsDialog({

useEffect(() => {
if (open && orgId) {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setSaveError(null);
setOverrides({});
setInitialOverrides({});
Expand All @@ -79,7 +79,7 @@ export function FeatureFlagsDialog({
useEffect(() => {
if (loadFetcher.data) {
const loaded = loadFetcher.data.orgFlags ?? {};
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setOverrides({ ...loaded });
setInitialOverrides({ ...loaded });
}
Expand All @@ -89,7 +89,7 @@ export function FeatureFlagsDialog({
if (saveFetcher.data?.success) {
onOpenChangeRef.current(false);
} else if (saveFetcher.data?.error) {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setSaveError(saveFetcher.data.error);
}
}, [saveFetcher.data]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ export function MaxProjectsSection({
const [value, setValue] = useState(String(maximumProjectCount));

useEffect(() => {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
if (hasFieldErrors) setIsEditing(true);
}, [hasFieldErrors]);

useEffect(() => {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
if (savedJustNow && !hasFieldErrors) setIsEditing(false);
}, [savedJustNow, hasFieldErrors]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ export function RateLimitSection({
const [maxTokens, setMaxTokens] = useState(current ? String(current.maxTokens) : "");

useEffect(() => {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
if (hasFieldErrors) setIsEditing(true);
}, [hasFieldErrors]);

useEffect(() => {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
if (savedJustNow && !hasFieldErrors) setIsEditing(false);
}, [savedJustNow, hasFieldErrors]);

Expand Down
8 changes: 4 additions & 4 deletions apps/webapp/app/components/billing/BillingAlertsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export function BillingAlertsSection({
return;
}

// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setShowResetBanner(true);

if (searchParams.get("alertsReset") !== "1") {
Expand All @@ -141,12 +141,12 @@ export function BillingAlertsSection({
);
const maxAlerts = isPercentageMode ? MAX_PERCENTAGE_ALERTS : MAX_ABSOLUTE_ALERTS;

/* oxlint-disable react/react-compiler -- Stable derived thresholds prevent the synchronization effect from resetting local edits. */
/* oxlint-disable react/preserve-manual-memoization -- Stable derived thresholds prevent the synchronization effect from resetting local edits. */
const savedThresholds = useMemo(
() => storedAlertsToThresholds(alerts, billingLimitMode, effectiveLimitCents, planLimitCents),
[alerts, billingLimitMode, effectiveLimitCents, planLimitCents]
);
/* oxlint-enable react/react-compiler */
/* oxlint-enable react/preserve-manual-memoization */
const savedEmails = useMemo(() => alerts.emails, [alerts.emails]);
const hasLegacySpikes = hasLegacySpikeAlertLevels(
alerts,
Expand Down Expand Up @@ -190,7 +190,7 @@ export function BillingAlertsSection({

useEffect(() => {
nextThresholdIdRef.current = savedThresholds.length;
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setThresholdRows(toThresholdRows(savedThresholds));
setEmailValues(savedEmails.length > 0 ? [...savedEmails, ""] : [""]);
}, [savedThresholds, savedEmails]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function BillingLimitConfigSection({
const formRef = useRef<HTMLFormElement>(null);

useEffect(() => {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setMode(resetMode);
setCustomAmount(savedCustomAmount);
setCancelInProgressRuns(savedCancelInProgressRuns);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function BillingLimitRecoveryPanel({
const formRef = useRef<HTMLFormElement>(null);

useEffect(() => {
// oxlint-disable-next-line react/react-compiler -- A refreshed server recommendation intentionally resets this editable amount draft.
// oxlint-disable-next-line react/set-state-in-effect, react/no-deriving-state-in-effects -- A refreshed server recommendation intentionally resets this editable amount draft.
setNewAmount(String(suggestedNewLimitDollars));
}, [suggestedNewLimitDollars]);

Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/code/AIQueryInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function AIQueryInput({
// If mode is edit but there's no current query, switch to new
useEffect(() => {
if (mode === "edit" && !canEdit) {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setMode("new");
}
}, [mode, canEdit]);
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/code/JSONEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export function JSONEditor(opts: JSONEditorProps) {
const editor = useRef<HTMLDivElement>(null);
const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
// oxlint-disable-next-line react/refs -- This ref intentionally coordinates an imperative integration outside React state.
container: editor.current,
extensions,
editable: !readOnly,
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/code/TSQLEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export function TSQLEditor(opts: TSQLEditorProps) {

const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
// oxlint-disable-next-line react/refs -- This ref intentionally coordinates an imperative integration outside React state.
container: editor.current,
extensions,
editable: !readOnly,
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/app/components/code/TSQLResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ const DebouncedInput = forwardRef<
const [value, setValue] = useState(initialValue);

useEffect(() => {
// oxlint-disable-next-line react/react-compiler -- Programmatic filter changes intentionally reset the debounced input draft.
// oxlint-disable-next-line react/set-state-in-effect, react/no-deriving-state-in-effects -- Programmatic filter changes intentionally reset the debounced input draft.
setValue(initialValue);
Comment on lines +227 to 228

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -H -t f -E node_modules -E dist 'oxlint|package\.json' . \
  | xargs -r rg -n 'react(-hooks)?/(refs|set-state-in-effect|no-deriving-state-in-effects|incompatible-library)|jsPlugins|plugins'

pnpm exec oxlint --rules \
  | rg 'react(-hooks)?/(refs|set-state-in-effect|no-deriving-state-in-effects|incompatible-library)'

pnpm exec oxlint \
  apps/webapp/app/components/code/TSQLResultsTable.tsx \
  --report-unused-disable-directives

Repository: triggerdotdev/trigger.dev

Length of output: 1853


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- effective Oxlint configuration ---'
cat -n .oxlintrc.json | sed -n '1,170p'

printf '%s\n' '--- Oxlint version declarations ---'
rg -n '"oxlint"|oxlint@' package.json pnpm-lock.yaml apps/webapp/package.json . 2>/dev/null | head -80

printf '%s\n' '--- CLI availability ---'
pnpm exec oxlint --version 2>&1 || true
pnpm exec oxlint --help 2>&1 | sed -n '1,180p' || true

printf '%s\n' '--- directives in the reviewed file ---'
rg -n 'oxlint-disable|react/(refs|set-state-in-effect|no-deriving-state-in-effects|incompatible-library)' apps/webapp/app/components/code/TSQLResultsTable.tsx

Repository: triggerdotdev/trigger.dev

Length of output: 16510


🏁 Script executed:

#!/bin/bash
set +e

printf '%s\n' '--- registered rule IDs ---'
pnpm exec oxlint --rules 2>&1 \
  | rg -n -C 1 'react/(refs|set-state-in-effect|no-deriving-state-in-effects|incompatible-library)' \
  || true

printf '%s\n' '--- effective file configuration ---'
pnpm exec oxlint --print-config apps/webapp/app/components/code/TSQLResultsTable.tsx 2>&1 \
  | rg -n -C 1 'react/(refs|set-state-in-effect|no-deriving-state-in-effects|incompatible-library)' \
  || true

printf '%s\n' '--- unused-directive check ---'
pnpm exec oxlint \
  apps/webapp/app/components/code/TSQLResultsTable.tsx \
  --report-unused-disable-directives 2>&1 \
  | rg -n -C 2 'unused|directive|react/(refs|set-state-in-effect|no-deriving-state-in-effects|incompatible-library)' \
  || true

Repository: triggerdotdev/trigger.dev

Length of output: 259


🏁 Script executed:

#!/bin/bash
set +e

printf '%s\n' '--- registered rules (raw, filtered by compiler namespace) ---'
pnpm exec oxlint --rules 2>&1 | head -40

printf '%s\n' '--- effective config (raw) ---'
pnpm exec oxlint --print-config apps/webapp/app/components/code/TSQLResultsTable.tsx 2>&1 | head -80

printf '%s\n' '--- file lint (raw) ---'
pnpm exec oxlint apps/webapp/app/components/code/TSQLResultsTable.tsx \
  --report-unused-disable-directives 2>&1 | head -160

Repository: triggerdotdev/trigger.dev

Length of output: 6059


🌐 Web query:

Oxlint 1.79.0 react plugin rules incompatible-library refs set-state-in-effect no-deriving-state-in-effects

💡 Result:

In Oxlint v1.79.0, React Compiler support has been integrated, introducing several specific rules within the react plugin that are powered by the React Compiler's analysis [1][2]. Regarding your specific inquiries: 1. react/incompatible-library: This rule is officially supported in Oxlint [3]. It warns about the usage of library APIs known to be incompatible with manual or automatic memoization (e.g., react-hook-form's watch, TanStack Table's useReactTable) [3]. It is categorized under the "correctness" category [2]. 2. react/refs: This rule is officially supported [4]. It validates the correct usage of refs, specifically ensuring that ref.current is not read or written during the render phase [4]. It is categorized under the "correctness" category [2]. 3. react/set-state-in-effect: This rule is officially supported [5]. It disallows calling setState synchronously inside an effect body, as this triggers unnecessary extra render passes and often indicates an anti-pattern (such as deriving state in an effect) [5][6]. It is categorized under the "correctness" category [2]. 4. no-deriving-state-in-effects: There is no native Oxlint rule with the exact name "no-deriving-state-in-effects" in the core react plugin [2]. However, the behavior of deriving state in effects is targeted by the react/set-state-in-effect rule [5][6]. Additionally, the rule react/no-deriving-state-in-effects is explicitly listed as being "off" and assigned to the "perf" category in Oxlint's recent configuration documentation [2]. If you require specific coverage for these anti-patterns, you may also look into community plugins like oxlint-plugin-react-doctor, which includes rules such as no-derived-state-effect [7]. To enable these rules, you can add them to your.oxlintrc.json or oxlint.config.ts configuration under the react plugin section [3][4][5]. For further details, consult the official Oxlint documentation [8][2].

Citations:


Remove react/no-deriving-state-in-effects. Oxlint 1.79 supports the other three rules, but not this rule. Remove it from the configuration and this directive.

Source: MCP tools

}, [initialValue]);

Expand Down Expand Up @@ -1065,7 +1065,6 @@ function ColumnResizeHandle({ header }: { header: Header<RowData, unknown> }) {
}
/* oxlint-enable jsx-a11y/no-static-element-interactions */

// oxlint-disable-next-line react/react-compiler -- TanStack Table is not compatible with compiler memoization.
export const TSQLResultsTable = memo(function TSQLResultsTable({
rows,
columns,
Expand Down Expand Up @@ -1122,6 +1121,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
// Column resize mode: 'onChange' for real-time feedback, 'onEnd' for performance
const columnResizeMode: ColumnResizeMode = "onChange";

// oxlint-disable-next-line react/incompatible-library -- TanStack Table is not compatible with compiler memoization.
const table = useReactTable({
data: rows,
columns: columnDefs,
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/code/TextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function TextEditor(opts: TextEditorProps) {
const editor = useRef<HTMLDivElement>(null);
const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
// oxlint-disable-next-line react/refs -- This ref intentionally coordinates an imperative integration outside React state.
container: editor.current,
extensions,
editable: !readOnly,
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/dashboard-agent/AgentChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function AgentChart({
// The block can render before `query` has streamed in; an empty query 400s.
if (!block.query) return;
if (!organizationId || !projectId || !environmentId) {
// oxlint-disable-next-line react/react-compiler -- This effect intentionally synchronizes local state after an external or lifecycle change.
// oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change.
setState({ status: "error", error: "No environment context to run the query." });
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function DashboardAgentChat({
// The path this chat last rendered on. React never unmounts on a page teardown, so an
// unmount whose live URL has moved is the router having navigated out from under it.
const renderedPathRef = useRef(location.pathname);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.

renderedPathRef.current = location.pathname;
Comment on lines +126 to 127

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

pnpm exec oxlint \
  apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx \
  --report-unused-disable-directives

Repository: triggerdotdev/trigger.dev

Length of output: 2089


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx"

printf '%s\n' '--- relevant source ---'
cat -n "$file" | sed -n '95,145p;195,225p;345,420p'

printf '%s\n' '--- ref declarations and suppressions ---'
rg -n -C 2 'renderedPathRef|orderRef|navigatedRef|watchProposedRef|teardownRef|messagesRef|react/refs|eslint-disable' "$file"

printf '%s\n' '--- repository lint configuration references ---'
rg -n -S 'react/refs|oxlint|eslint-plugin-react-hooks|react-hooks' \
  --glob '!node_modules/**' \
  --glob '!dist/**' \
  --glob '!build/**' \
  .oxlintrc* package.json pnpm-lock.yaml 2>/dev/null || true

printf '%s\n' '--- package files ---'
git ls-files '*oxlint*' '*eslint*' 'package.json' | sed -n '1,120p'

Repository: triggerdotdev/trigger.dev

Length of output: 15588


🌐 Web query:

React eslint-plugin-react-hooks refs rule official documentation render ref.current

💡 Result:

The official eslint-plugin-react-hooks includes a specific rule, react-hooks/refs, designed to enforce the correct usage of React refs [1][2][3]. This rule prohibits reading or writing to ref.current during the component render phase, as doing so can lead to unpredictable behavior, stale data, or inconsistent application state [1][4][5]. Key points regarding this rule and React ref usage include: 1. Rule Intent: The rule prevents reading or writing ref.current during rendering [1][4][3]. Because refs are meant for storing mutable values that do not trigger re-renders, accessing them during render—where React expects pure operations—breaks established React patterns [1][5][6]. 2. Acceptable Usage: - Effects and Event Handlers: You should read or write ref.current inside useEffect, useLayoutEffect, or event handlers (like onClick), where side effects are intended and allowed [1][4][3]. - Lazy Initialization: A specific, allowed exception is lazy initialization of a ref, such as if (!ref.current) ref.current = new ExpensiveValue, provided it is performed safely during the render phase [1][5][3]. 3. How the Linter Identifies Refs: The plugin uses a name-based heuristic to identify refs [1][4]. If a variable is named ref or ends in Ref, the linter tracks it as a ref object [1][3]. If you are using an object to hold non-ref mutable data and are receiving false positives from this rule, you can resolve it by renaming the variable (e.g., to box or data) so the linter no longer infers it as a React ref [1][4][3]. 4. Distinction from exhaustive-deps: While react-hooks/refs targets direct access to ref.current during render, the separate react-hooks/exhaustive-deps rule warns against including ref.current in dependency arrays [7][8]. This is because refs are mutable and their updates do not trigger re-renders; therefore, they should not be treated as dependencies that require re-running effects [7][8]. For more details, consult the official React documentation on the refs lint rule and the useRef hook [1][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Oxlint configuration ---'
cat -n .oxlintrc.json | sed -n '1,180p'

printf '%s\n' '--- Change that removed the directives ---'
git diff --unified=8 -- apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx

printf '%s\n' '--- Available Oxlint rule/config capabilities ---'
pnpm exec oxlint --version
pnpm exec oxlint --help | sed -n '1,180p'

Repository: triggerdotdev/trigger.dev

Length of output: 15744


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx"

printf '%s\n' '--- parent version around the affected code ---'
git show HEAD^:"$file" 2>/dev/null | nl -ba | sed -n '115,140p;200,220p;350,420p' || true

printf '%s\n' '--- ref-rule suppression conventions ---'
rg -n -C 1 'react/refs|eslint-disable.*refs|oxlint-disable.*refs' \
  --glob '!node_modules/**' \
  --glob '!dist/**' \
  --glob '!build/**' \
  . 2>/dev/null || true

printf '%s\n' '--- locked Oxlint version ---'
sed -n '120,150p;12355,12380p;27365,27385p' pnpm-lock.yaml

Repository: triggerdotdev/trigger.dev

Length of output: 20178


🌐 Web query:

Oxlint 1.79.0 react/refs rule react plugin

💡 Result:

In Oxlint 1.79.0, the react/refs rule is officially supported as part of the React plugin's integration with the React Compiler [1][2][3]. Following a major update in version 1.79.0, Oxlint split the experimental, monolithic react/react-compiler rule into distinct, category-specific rules that align with upstream React Compiler presets [2][3]. The react/refs rule is now one of these native implementations, categorized under correctness [2]. To use the rule, ensure you have enabled the React plugin [1][4]. You can enable the rule via your configuration file or the command line: Configuration file (oxlint.json or equivalent): { "plugins": ["react"], "rules": { "react/refs": "error" } } Command line: oxlint --deny react/refs --react-plugin This rule validates that refs are not read or written during the render phase, which is crucial for preventing stale UI and ensuring component correctness [1]. If you were previously using the experimental react/react-compiler rule, it is recommended to remove it from your configuration and replace it with these specific rules [2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx"

printf '%s\n' '--- parent source at affected ranges ---'
git show HEAD^:"$file" 2>/dev/null | cat -n | sed -n '115,140p;200,220p;350,420p' || true

printf '%s\n' '--- parent-to-current diff ---'
git diff --unified=3 HEAD^ HEAD -- "$file" 2>/dev/null | sed -n '1,240p' || true

printf '%s\n' '--- Oxlint refs rule documentation ---'
curl -fsSL https://oxc.rs/docs/guide/usage/linter/rules/react/refs.html | \
  sed -n '1,220p' | grep -E -i -C 3 'lazy|render|current|disable' || true

Repository: triggerdotdev/trigger.dev

Length of output: 50381


Restore targeted react/refs suppressions for intentional render-time ref access.

react/refs is enabled for this file. Restore the repository’s oxlint-disable-next-line react/refs comments before the six intentional accesses. Keep lazy-initialization writes unsuppressed when they match the rule exception.

Source: MCP tools


const transport = useTriggerChatTransport<typeof dashboardAgent>({
Expand Down Expand Up @@ -210,7 +210,7 @@ export function DashboardAgentChat({
});

const orderRef = useRef(createTranscriptOrder(initialMessages));
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.

const messages = orderTranscript(rawMessages, orderRef.current);

// Read here, not in the panel, so it re-reads as each turn settles.
Expand Down Expand Up @@ -361,7 +361,7 @@ export function DashboardAgentChat({
const navigatedRef = useRef<Set<string> | null>(null);
if (navigatedRef.current === null) {
navigatedRef.current = new Set();
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.

pendingNavigateIntents(initialMessages, navigatedRef.current);
}
useEffect(() => {
Expand All @@ -377,7 +377,7 @@ export function DashboardAgentChat({
const watchProposedRef = useRef<Set<string> | null>(null);
if (watchProposedRef.current === null) {
watchProposedRef.current = new Set();
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.

pendingWatchIntents(initialMessages, watchProposedRef.current);
}
useEffect(() => {
Expand All @@ -392,7 +392,7 @@ export function DashboardAgentChat({
}, [transport, chatId, aiStop]);

const teardownRef = useRef<() => void>(() => {});
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.

teardownRef.current = () => {
if (status !== "streaming" && status !== "submitted") return;
const reason = unmountTeardown({
Expand All @@ -406,7 +406,7 @@ export function DashboardAgentChat({

// Read by the settle effect, which must not re-run when the transcript changes.
const messagesRef = useRef(messages);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.

messagesRef.current = messages;

const prevStatus = useRef(status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ export function winningInvestigationOccurrences(messages: UIMessage[]): Map<stri
function useInvestigationWinners(messages: UIMessage[]): Map<string, string> {
const previous = useRef<Map<string, string>>();
const next = useMemo(() => winningInvestigationOccurrences(messages), [messages]);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
// oxlint-disable-next-line react/refs -- This ref intentionally coordinates an imperative integration outside React state.
previous.current = reuseWinners(previous.current, next);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
// oxlint-disable-next-line react/refs -- This ref intentionally coordinates an imperative integration outside React state.
return previous.current;
}

Expand Down
Loading
Loading