Skip to content

Commit ac6de69

Browse files
committed
[compiler] Added check for if the same invalid setSate within an effect is used elsewhere
1 parent 38d50d7 commit ac6de69

File tree

1 file changed

+61
-31
lines changed

1 file changed

+61
-31
lines changed

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type SetStateCall = {
4141
};
4242
type TypeOfValue = 'ignored' | 'fromProps' | 'fromState' | 'fromPropsOrState';
4343

44+
type SetStateName = string | undefined | null;
45+
4446
type DerivationMetadata = {
4547
typeOfValue: TypeOfValue;
4648
// TODO: Rename to place
@@ -52,8 +54,9 @@ type DerivationMetadata = {
5254
type ErrorMetadata = {
5355
errorType: 'HoistState' | 'CalculateInRender';
5456
propInfo: string | undefined;
57+
localStateInfo: string | undefined;
5558
loc: SourceLocation;
56-
setStateId: IdentifierId;
59+
setStateName: SetStateName;
5760
};
5861

5962
function joinValue(
@@ -93,7 +96,7 @@ function updateDerivationMetadata(
9396
function parseInstr(
9497
instr: Instruction,
9598
derivedTuple: Map<IdentifierId, DerivationMetadata>,
96-
setStateCalls: Map<string, Place>,
99+
setStateCalls: Map<SetStateName, Place[]>,
97100
) {
98101
// console.log(printInstruction(instr));
99102
// console.log(instr);
@@ -121,14 +124,17 @@ function parseInstr(
121124
isSetStateType(instr.value.callee.identifier) &&
122125
instr.value.args.length === 1 &&
123126
instr.value.args[0].kind === 'Identifier' &&
124-
instr.value.callee.loc !== GeneratedSource &&
125-
instr.value.callee.loc.identifierName !== undefined &&
126-
instr.value.callee.loc.identifierName !== null
127+
instr.value.callee.loc !== GeneratedSource
127128
) {
128-
setStateCalls.set(
129-
instr.value.callee.loc.identifierName,
130-
instr.value.callee,
131-
);
129+
if (setStateCalls.has(instr.value.callee.loc.identifierName)) {
130+
setStateCalls
131+
.get(instr.value.callee.loc.identifierName)!
132+
.push(instr.value.callee);
133+
} else {
134+
setStateCalls.set(instr.value.callee.loc.identifierName, [
135+
instr.value.callee,
136+
]);
137+
}
132138
}
133139

134140
let sources: DerivationMetadata[] = [];
@@ -246,11 +252,8 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
246252
const locals: Map<IdentifierId, IdentifierId> = new Map();
247253
const derivedTuple: Map<IdentifierId, DerivationMetadata> = new Map();
248254

249-
// Investigating
250-
const effectSetStates: Map<string, Place> = new Map();
251-
const setStateCalls: Map<string, Place> = new Map();
252-
253-
// let shouldCalculateInRender: boolean = true;
255+
const effectSetStates: Map<SetStateName, Place[]> = new Map();
256+
const setStateCalls: Map<SetStateName, Place[]> = new Map();
254257

255258
const errors: ErrorMetadata[] = [];
256259

@@ -343,13 +346,37 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
343346
}
344347
}
345348

346-
console.log('setStateCalls: ', setStateCalls);
347-
console.log('effectSetStates: ', effectSetStates);
348349
const throwableErrors = new CompilerError();
349350
for (const error of errors) {
351+
let reason;
352+
let description = '';
353+
// TODO: Not sure if this is robust enough.
354+
/*
355+
* If we use a setState from an invalid useEffect elsewhere then we probably have to
356+
* hoist state up, else we should calculate in render
357+
*/
358+
if (
359+
setStateCalls.get(error.setStateName)?.length !=
360+
effectSetStates.get(error.setStateName)?.length
361+
) {
362+
reason =
363+
'Consider lifting state up to the parent component to make this a controlled component. (https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes)';
364+
} else {
365+
reason =
366+
'You may not need this effect. Values derived from state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)';
367+
}
368+
369+
if (error.propInfo !== undefined) {
370+
description += error.propInfo;
371+
}
372+
373+
if (error.localStateInfo !== undefined) {
374+
description += error.localStateInfo;
375+
}
376+
350377
throwableErrors.push({
351-
reason: `You may not need an effect. Values derived from state should be calculated in render, not in an effect. `,
352-
description: `You are using a value derived from props${error.propInfo} to update local state in an effect.`,
378+
reason: reason,
379+
description: description,
353380
severity: ErrorSeverity.InvalidReact,
354381
loc: error.loc,
355382
});
@@ -364,7 +391,7 @@ function validateEffect(
364391
effectFunction: HIRFunction,
365392
effectDeps: Array<IdentifierId>,
366393
derivedTuple: Map<IdentifierId, DerivationMetadata>,
367-
effectSetStates: Map<string, Place>,
394+
effectSetStates: Map<SetStateName, Place[]>,
368395
errors: ErrorMetadata[],
369396
): void {
370397
/*
@@ -438,10 +465,15 @@ function validateEffect(
438465
instr.value.callee.loc.identifierName !== undefined &&
439466
instr.value.callee.loc.identifierName !== null
440467
) {
441-
effectSetStates.set(
442-
instr.value.callee.loc.identifierName,
443-
instr.value.callee,
444-
);
468+
if (effectSetStates.has(instr.value.callee.loc.identifierName)) {
469+
effectSetStates
470+
.get(instr.value.callee.loc.identifierName)!
471+
.push(instr.value.callee);
472+
} else {
473+
effectSetStates.set(instr.value.callee.loc.identifierName, [
474+
instr.value.callee,
475+
]);
476+
}
445477
}
446478
switch (instr.value.kind) {
447479
case 'Primitive':
@@ -518,12 +550,6 @@ function validateEffect(
518550
seenBlocks.add(block.id);
519551
}
520552

521-
// need to track if the setState call has been used elsewhere
522-
// if it is then the solution should be to lift the state up to the parent component
523-
// if not the solution should be to calculate the value in rende
524-
//
525-
// If the same setState is used both inside and outside the effect
526-
527553
for (const call of setStateCallsInEffect) {
528554
if (call.invalidDeps != null) {
529555
let propNames = '';
@@ -539,15 +565,19 @@ function validateEffect(
539565
errors.push({
540566
errorType: 'HoistState',
541567
propInfo: propInfo,
568+
localStateInfo: undefined,
542569
loc: call.loc,
543-
setStateId: call.setStateId,
570+
setStateName:
571+
call.loc !== GeneratedSource ? call.loc.identifierName : undefined,
544572
});
545573
} else {
546574
errors.push({
547575
errorType: 'CalculateInRender',
548576
propInfo: undefined,
577+
localStateInfo: undefined,
549578
loc: call.loc,
550-
setStateId: call.setStateId,
579+
setStateName:
580+
call.loc !== GeneratedSource ? call.loc.identifierName : undefined,
551581
});
552582
}
553583
}

0 commit comments

Comments
 (0)