feat(properties): edit task properties from list, task code cleanup#992
feat(properties): edit task properties from list, task code cleanup#992
Conversation
Code ReviewFound 1 high-confidence issue: SolidJS Hook Rule ViolationFile: This code violates SolidJS hook rules by calling hooks conditionally inside functions that are re-evaluated on each render. Problems:
In SolidJS, hooks must be called at the top level during component/hook initialization, not inside functions that get re-evaluated. This causes memory leaks, multiple subscriptions, and unpredictable behavior. Comparison with old code: The old code properly called hooks at the top level: const [preview] = useItemPreview({
id: needsPreview() ? entityId() : '',
type: needsPreview() ? getPreviewType() : undefined,
});
const channelName = useChannelName(
entityType().toUpperCase() === 'CHANNEL' ? entityId() : '',
'Unknown Channel'
);Recommended fix: Call all hooks unconditionally at the top level with reactive parameters: // Call hooks at top level with reactive parameters
const [preview] = useItemPreview({
id: () => isPreviewable(entityType()) ? entityId() : '',
type: previewType,
});
const channelName = useChannelName(
() => previewType() === 'channel' ? entityId() : '',
'Channel'
);
const [userName] = useDisplayName(
() => entityType() === 'USER' ? tryMacroId(entityId()) : undefined
);
// Then use these in reactive contexts
const name = createMemo(() => {
return match(entityType())
.with('USER', () => userName())
.with('CHANNEL', () => channelName() ?? 'Channel')
// ...
});This ensures hooks are only instantiated once during initialization, following SolidJS best practices. Review Summary:
|
dev-rb
left a comment
There was a problem hiding this comment.
Looks good overall, just a few nits
js/app/packages/core/component/Properties/component/propertyValue/PropertyTooltip.tsx
Outdated
Show resolved
Hide resolved
js/app/packages/core/component/Properties/component/propertyValue/PropertyValue.tsx
Outdated
Show resolved
Hide resolved
js/app/packages/core/component/Properties/hooks/usePropertyEntityDisplay.tsx
Outdated
Show resolved
Hide resolved
synoet
left a comment
There was a problem hiding this comment.
Very Nice! One small note for onRefresh.
js/app/packages/core/component/Properties/hooks/usePropertyEntityDisplay.tsx
Outdated
Show resolved
Hide resolved
| const saveMutation = useSaveEntityPropertyMutation({ | ||
| onSuccess: () => { | ||
| props.onRefresh?.(); | ||
| }, | ||
| }); |
There was a problem hiding this comment.
We shouldn't need to manually trigger refreshes everywhere. The mutation should optimistically update and invalidate. And since everything is using the solid queries they should just reactively update.
| onPropertyAdded={() => props.onRefresh?.()} | ||
| onPropertyDeleted={() => props.onRefresh?.()} |
| /** | ||
| * Creates a reactive hook manager that properly disposes and recreates hooks | ||
| * when dependencies change. This solves the problem of hooks that take plain | ||
| * values (not accessors) needing to react to changes. | ||
| * @param deps - Accessor that returns dependencies to track | ||
| * @param shouldCreate - Predicate to determine if hook should be created | ||
| * @param createHook - Factory function that creates the hook and returns its value | ||
| * @param defaultValue - Default value when hook is not created | ||
| * @returns Accessor to the current hook value | ||
| */ | ||
| function useReactiveHook<TDeps, TValue>( | ||
| deps: Accessor<TDeps>, | ||
| shouldCreate: (deps: TDeps) => boolean, | ||
| createHook: (deps: TDeps) => TValue, | ||
| defaultValue: TValue | ||
| ): Accessor<TValue> { | ||
| const [value, setValue] = createSignal<TValue>(defaultValue); | ||
| let dispose: (() => void) | undefined; | ||
|
|
||
| createEffect(() => { | ||
| const currentDeps = deps(); | ||
|
|
||
| // Clean up previous hook instance | ||
| dispose?.(); | ||
| dispose = undefined; | ||
|
|
||
| if (shouldCreate(currentDeps)) { | ||
| createRoot((d) => { | ||
| dispose = d; | ||
| const hookValue = createHook(currentDeps); | ||
| setValue(() => hookValue); | ||
| }); | ||
| } else { | ||
| setValue(() => defaultValue); | ||
| } | ||
| }); | ||
|
|
||
| onCleanup(() => dispose?.()); | ||
|
|
||
| return value; | ||
| } | ||
|
|
There was a problem hiding this comment.
This is very complex and seems unnecessary. I think the previous change with the memo was fine but just required small changes. I think Claude thinks this is React 🤦
There was a problem hiding this comment.
yeah fair enough. the memo is not enough to smartly dispose the computations inside the usePreview() hook though right? like the "old" usePreview will still just be disposed with the lifecycle of the calling component?
No description provided.