Skip to content

Feeds and routing (migration 4/8) - #692

Open
charityquinn-cognition wants to merge 2 commits into
devin/react-migration-3-ui-shellfrom
devin/react-migration-4-feeds
Open

Feeds and routing (migration 4/8)#692
charityquinn-cognition wants to merge 2 commits into
devin/react-migration-3-ui-shellfrom
devin/react-migration-4-feeds

Conversation

@charityquinn-cognition

@charityquinn-cognition charityquinn-cognition commented Aug 25, 2026

Copy link
Copy Markdown

Summary

Stacked on #691. Ports feeds/feed + feeds/item and replaces PR3's placeholder outlet with the real routes, so the app is now usable end to end for browsing.

Routes mirror app.routes.ts — one parameterized route per feed, default redirect to news/1:

<Route path="" element={<Navigate to="/news/1" replace />} />
{feedNames.map((f) => (
    <Route key={f} path={`${f}/:page`} element={<Feed key={f} feedType={f} />} />
))}
  • The key on the <Feed /> element (not just the <Route />) is load-bearing. Angular destroyed and recreated FeedComponent when the route config changed (news -> ask) but kept it when only :page changed. React Router renders the same component type in the same tree position across all five feed routes, so without the key the previous feed's stories stay on screen while the new feed loads — and hide the error if it fails. Keying on feedName reproduces both halves: remount across feeds, state retained across pages (which is why this isn't fixed by clearing items in the effect — that would also blank the list during pagination).
  • page comes from useParams; the fetch effect is keyed on (feedType, page) and guarded by an ignore flag so a slow response for a previous page can't overwrite the current one (the Angular version re-subscribed per param change and had this race).
  • Pagination keeps the original semantics: rank starts at (page - 1) * 30 + 1, "More" always renders, "prev" only past page 1.
  • Two original quirks are preserved deliberately rather than "fixed": the job-feed header condition and the feedType !== 'new' comparison in the template (the feed name is newest, so that branch never matches — kept as-is to avoid a behavior change inside a migration PR).
  • Item reads the settings context directly for title font size, list spacing and the external-link target/rel, instead of Angular's input-passed settings object.

Verified: yarn react:build passes; all five feeds render live data, pagination and ranks are correct, story links honor the "open in new tab" setting, and switching feeds shows the loader instead of the previous feed's stories while paging keeps the old list visible.

Devin-Org: engineering

Link to Devin session: https://app.devin.ai/sessions/1ff25c6cf2f949458f82cc596fc79c65
Requested by: @charityquinn-cognition


Devin Review

Status Commit
⚪ Not started

Run Devin Review

Devin Review (Staging)
Open in Devin Review

@devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

Open in Devin Review

Comment on lines +22 to +45
useEffect(() => {
let ignore = false;
setErrorMessage('');

fetchFeed(feedType, pageNum)
.then((nextItems) => {
if (ignore) {
return;
}

setItems(nextItems);
setListStart((pageNum - 1) * 30 + 1);
window.scrollTo(0, 0);
})
.catch(() => {
if (!ignore) {
setErrorMessage(`Could not load ${feedType} stories.`);
}
});

return () => {
ignore = true;
};
}, [feedType, pageNum]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Previous feed's stories remain when switching feeds

The fetch effect clears errorMessage but never resets items when feedType changes. React Router reuses the Feed instance across all feed routes, so the previous feed's stories stay visible with no loader until new data arrives; if the new feed fails to load, the stale list stays and the error is hidden.

Suggested change
useEffect(() => {
let ignore = false;
setErrorMessage('');
fetchFeed(feedType, pageNum)
.then((nextItems) => {
if (ignore) {
return;
}
setItems(nextItems);
setListStart((pageNum - 1) * 30 + 1);
window.scrollTo(0, 0);
})
.catch(() => {
if (!ignore) {
setErrorMessage(`Could not load ${feedType} stories.`);
}
});
return () => {
ignore = true;
};
}, [feedType, pageNum]);
useEffect(() => {
let ignore = false;
setItems(undefined);
setErrorMessage('');
fetchFeed(feedType, pageNum)
.then((nextItems) => {
if (ignore) {
return;
}
setItems(nextItems);
setListStart((pageNum - 1) * 30 + 1);
window.scrollTo(0, 0);
})
.catch(() => {
if (!ignore) {
setErrorMessage(`Could not load ${feedType} stories.`);
}
});
return () => {
ignore = true;
};
}, [feedType, pageNum]);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Real bug, fixing it — but not with setItems(undefined) in the effect. Angular recreated FeedComponent when the route config changed (news → ask) while keeping the component and its list when only :page changed, so clearing in the effect would also blank the list on pagination, which the original didn't do. Adding key={feedName} to the <Feed /> route element reproduces both halves: remount (loader, no stale list) across feeds, state retained across pages.

You can also get a job at a YC startup through <a href="https://triplebyte.com/?ref=yc_jobs">Triplebyte</a>.
</p>
)}
{feedType as string !== 'new' && (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Preserved quirk: feedType !== 'new' always true

feedType as string !== 'new' is always true since no feed is named 'new', so the <ol> always renders. Matches the Angular original and is called out as an intentionally preserved quirk.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


function Feed({ feedType }: FeedProps) {
const params = useParams<{ page?: string }>();
const pageNum = params.page ? Number(params.page) : 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Non-numeric page param yields page=NaN request

A non-numeric segment like /news/abc makes Number(params.page) produce NaN, sending ?page=NaN to the API. This matches the Angular original and is not a regression.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration Bot and others added 2 commits August 25, 2026 22:24
Co-Authored-By: Charity Quinn <charity.quinn@cognition.ai>
Co-Authored-By: Charity Quinn <charity.quinn@cognition.ai>
@devin-ai-integration
devin-ai-integration Bot force-pushed the devin/react-migration-4-feeds branch from 287cbd4 to 06c1462 Compare August 25, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant