Skip to content

Commit 9fd23af

Browse files
committed
fix(web): extract search cell component to fix React hooks violation
Move useSearchResultHover hook call from table cell render function into proper SearchResultNameCell component. React hooks must be called at the top level of a component, not inside TanStack Table cell renderers. Fixes "Cannot read properties of null (reading 'useReducer')" error.
1 parent 727fe55 commit 9fd23af

File tree

1 file changed

+50
-49
lines changed

1 file changed

+50
-49
lines changed

apps/web/src/routes/search.lazy.tsx

Lines changed: 50 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,55 @@ const getEntityTypeColor = (entityType: AutocompleteResult["entity_type"]) => {
133133
return "gray";
134134
};
135135

136+
/**
137+
* Name cell component - extracted to use hooks properly
138+
* React hooks must be called at the top level of a component, not in render functions
139+
*/
140+
const SearchResultNameCell = ({ result }: { result: AutocompleteResult }) => {
141+
const entityUrl = convertToRelativeUrl(result.id);
142+
const hover = useSearchResultHover(result);
143+
144+
return (
145+
<>
146+
<div {...hover.props}>
147+
{entityUrl ? (
148+
<Anchor
149+
href={entityUrl}
150+
size="sm"
151+
fw={500}
152+
style={{ textDecoration: "none" }}
153+
aria-label={`View ${result.entity_type} ${result.display_name}`}
154+
>
155+
{result.display_name}
156+
</Anchor>
157+
) : (
158+
<Text fw={500} size="sm">
159+
{result.display_name}
160+
</Text>
161+
)}
162+
{result.hint && (
163+
<Text size="xs" c="dimmed" lineClamp={1}>
164+
{result.hint}
165+
</Text>
166+
)}
167+
{result.external_id && (
168+
<Text size="xs" c="dimmed">
169+
{result.external_id}
170+
</Text>
171+
)}
172+
</div>
173+
174+
{/* Hover preview card */}
175+
<SearchResultPreview
176+
entity={result}
177+
opened={hover.opened}
178+
onToggle={hover.toggle}
179+
targetElement={hover.targetElement}
180+
/>
181+
</>
182+
);
183+
};
184+
136185
// Extract column definitions to reduce complexity
137186
const createSearchColumns = (): ColumnDef<AutocompleteResult>[] => [
138187
{
@@ -155,55 +204,7 @@ const createSearchColumns = (): ColumnDef<AutocompleteResult>[] => [
155204
{
156205
accessorKey: "display_name",
157206
header: "Name",
158-
cell: ({ row }) => {
159-
const result = row.original;
160-
// result.id is already a full URL like "https://openalex.org/T10044"
161-
// Don't prepend another domain prefix
162-
const entityUrl = convertToRelativeUrl(result.id);
163-
164-
// Set up hover functionality
165-
const hover = useSearchResultHover(result);
166-
167-
return (
168-
<>
169-
<div {...hover.props}>
170-
{entityUrl ? (
171-
<Anchor
172-
href={entityUrl}
173-
size="sm"
174-
fw={500}
175-
style={{ textDecoration: "none" }}
176-
aria-label={`View ${result.entity_type} ${result.display_name}`}
177-
>
178-
{result.display_name}
179-
</Anchor>
180-
) : (
181-
<Text fw={500} size="sm">
182-
{result.display_name}
183-
</Text>
184-
)}
185-
{result.hint && (
186-
<Text size="xs" c="dimmed" lineClamp={1}>
187-
{result.hint}
188-
</Text>
189-
)}
190-
{result.external_id && (
191-
<Text size="xs" c="dimmed">
192-
{result.external_id}
193-
</Text>
194-
)}
195-
</div>
196-
197-
{/* Hover preview card */}
198-
<SearchResultPreview
199-
entity={result}
200-
opened={hover.opened}
201-
onToggle={hover.toggle}
202-
targetElement={hover.targetElement}
203-
/>
204-
</>
205-
);
206-
},
207+
cell: ({ row }) => <SearchResultNameCell result={row.original} />,
207208
},
208209
{
209210
accessorKey: "cited_by_count",

0 commit comments

Comments
 (0)