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
12 changes: 4 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { getWebR, onRestart } from './r/webrClient';
import './App.css';

export default function App() {
const [restarts, setRestarts] = useState(0);
// Spec 3.5: webR starts in the background on first app load, not when the
// first lesson opens. Mounted here rather than in main.tsx so it is testable
// by rendering App. Both calls are memoised, so StrictMode's double mount and
Expand All @@ -29,12 +28,9 @@ export default function App() {
// status that RStatus renders. This only stops an unhandled rejection.
const boot = () => void getWebR().then((r) => prepareSession(r, fetchDataset)).catch(() => {});
boot();
// After "Restart R": remount the page, so every lesson environment, code
// block and console that held the old R starts over on the new one.
return onRestart(() => {
setRestarts((count) => count + 1);
boot();
});
// After "Restart R" boot the new R in the background. The page stays put:
// useLessonSession gives it a fresh environment on the new R.
return onRestart(boot);
}, []);

useEffect(() => {
Expand Down Expand Up @@ -116,7 +112,7 @@ export default function App() {
<main id="main" tabIndex={-1} className="app-main">
<RStatus where="page" />
<div ref={page} className={`page page-${pathname.split('/')[1] || 'home'}`}>
<Routes key={restarts}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/lesson/:lessonId" element={<Lesson />} />
<Route path="/workspace" element={<RWorkspace />} />
Expand Down
8 changes: 6 additions & 2 deletions src/r/useLessonSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { RObject, WebR } from 'webr';
import { createLessonEnv, destroyEnv } from './environments';
import { ensurePackages, fetchDataset, prepareSession } from './session';
import { restoreUploads } from './uploads';
import { getWebR, setStatus } from './webrClient';
import { getWebR, onRestart, setStatus } from './webrClient';

/** What the hook needs from a lesson: its identity, and what it must attach. */
export type SessionLesson = { id: string; packages?: string[] };
Expand All @@ -25,6 +25,10 @@ export function useLessonSession(
const packages = lesson?.packages;
const [webR, setWebR] = useState<WebR | null>(null);
const [env, setEnv] = useState<RObject | null>(null);
// "Restart R" replaces the R instance. Counting restarts reruns the effect
// below on the new R, so the page and the code typed into it stay put.
const [restarts, setRestarts] = useState(0);
useEffect(() => onRestart(() => setRestarts((count) => count + 1)), []);

useEffect(() => {
if (key === null) return;
Expand Down Expand Up @@ -66,7 +70,7 @@ export function useLessonSession(
// After a restart the old R is gone along with its environments.
if (created && createdBy) destroyEnv(createdBy, created).catch(() => {});
};
}, [key]);
}, [key, restarts]);

return { webR, env };
}