T
TanStack2y ago
rival-black

Pass context to another root (createRoot)

I'm trying to create a map control using "react-map-gl" which has a tanstack router <Link/> component in it. In order to create a control on the map I have to create a new react root using React.createRoot. That react root doesn't have access to that actual app's <RouterProvider/> context so the <Link/> component doesn't work. It gives some error about state being null. I can't wrap the new root in another <RouterProvider/> because I don't want to render an entire new app/routeTree. I just want to be able to use the <Link/> component. I've also tried using getRouterContext() and routerContext.Provider similar to here: https://github.com/TanStack/router/blob/main/packages/react-router/src/RouterProvider.tsx#L84 but that returns an errors that indicates that it requires a route to be matched in its children.
export interface ControlProps {
className?: string;
position: ControlPosition;
children?: React.ReactNode;
}

function Control({ className, position, children }: ControlProps) {
const context = useContext(MapContext);

const [root, setRoot] = useState<Root | null>(null);

useEffect(() => {
const el = document.createElement("div");
el.className = `maplibregl-ctrl maplibregl-ctrl-group ${className}`;
const root = createRoot(el);
setRoot(root);

const { map } = context;

const ctrl: IControl = {
onAdd() {
return el;
},
onRemove() {
return el;
},
};

map.addControl(ctrl, position);

return () => {
setTimeout(() => root.unmount());
map.removeControl(ctrl);
el.remove();
};
}, [context, position, className]);

const routerContext = getRouterContext();
const router = useRouter();

useEffect(() => {
if (root) {
// Need to wrap this children node with the app's router context
root.render(children);
}
}, [root, children, routerContext, router]);

return null;
}
export interface ControlProps {
className?: string;
position: ControlPosition;
children?: React.ReactNode;
}

function Control({ className, position, children }: ControlProps) {
const context = useContext(MapContext);

const [root, setRoot] = useState<Root | null>(null);

useEffect(() => {
const el = document.createElement("div");
el.className = `maplibregl-ctrl maplibregl-ctrl-group ${className}`;
const root = createRoot(el);
setRoot(root);

const { map } = context;

const ctrl: IControl = {
onAdd() {
return el;
},
onRemove() {
return el;
},
};

map.addControl(ctrl, position);

return () => {
setTimeout(() => root.unmount());
map.removeControl(ctrl);
el.remove();
};
}, [context, position, className]);

const routerContext = getRouterContext();
const router = useRouter();

useEffect(() => {
if (root) {
// Need to wrap this children node with the app's router context
root.render(children);
}
}, [root, children, routerContext, router]);

return null;
}
GitHub
router/packages/react-router/src/RouterProvider.tsx at main · TanSt...
🤖 Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering. - TanStack/router
1 Reply
rival-black
rival-blackOP2y ago
I was able to do the following to fix my issue.
export interface ControlProps {
className?: string;
position: ControlPosition;
children?: React.ReactNode;
}

function Control({ className, position, children }: ControlProps) {
const context = useContext(MapContext);

const [root, setRoot] = useState<Root | null>(null);

useEffect(() => {
const el = document.createElement("div");
el.className = `maplibregl-ctrl maplibregl-ctrl-group ${className}`;
const root = createRoot(el);
setRoot(root);

const { map } = context;

const ctrl: IControl = {
onAdd() {
return el;
},
onRemove() {
return el;
},
};

map.addControl(ctrl, position);

return () => {
setTimeout(() => root.unmount());
map.removeControl(ctrl);
el.remove();
};
}, [context, position, className]);

const routerContext = getRouterContext();
const router = useRouter();
const match = useContext(matchContext);

useEffect(() => {
if (root) {
root.render(
<routerContext.Provider value={router}>
<matchContext.Provider value={match}>
{children}
</matchContext.Provider>
</routerContext.Provider>
);
}
}, [root, children, routerContext, router, match]);

return null;
}
export interface ControlProps {
className?: string;
position: ControlPosition;
children?: React.ReactNode;
}

function Control({ className, position, children }: ControlProps) {
const context = useContext(MapContext);

const [root, setRoot] = useState<Root | null>(null);

useEffect(() => {
const el = document.createElement("div");
el.className = `maplibregl-ctrl maplibregl-ctrl-group ${className}`;
const root = createRoot(el);
setRoot(root);

const { map } = context;

const ctrl: IControl = {
onAdd() {
return el;
},
onRemove() {
return el;
},
};

map.addControl(ctrl, position);

return () => {
setTimeout(() => root.unmount());
map.removeControl(ctrl);
el.remove();
};
}, [context, position, className]);

const routerContext = getRouterContext();
const router = useRouter();
const match = useContext(matchContext);

useEffect(() => {
if (root) {
root.render(
<routerContext.Provider value={router}>
<matchContext.Provider value={match}>
{children}
</matchContext.Provider>
</routerContext.Provider>
);
}
}, [root, children, routerContext, router, match]);

return null;
}

Did you find this page helpful?