link or navigation in Toasts or modals Error : router primitives can be only used inside a Route

I create a modal system. it was so OK! until i use <A/> tag
18 Replies
saeed latifi
saeed latifiOPβ€’5w ago
and Error : router primitives can be only used inside a Route i also check solid-toast to see how it handle it and boom even links in solid-toast has same problem toast.success(<A href="">link</A>); import { useModal } from "~/hooks/useModal"; import { ButtonRound } from "../components/button/ButtonRound"; import { IconLogo } from "../components/icons/IconLogo"; import ModalAccount from "../components/modal/modal.account"; // import { useModal } from "../context/modal.context"; import { useAccount } from "../hooks/useAccount"; import toast from "solid-toast"; import { A } from "@solidjs/router"; export function Account() { const { data } = useAccount(); const { onModal, onClear, modal } = useModal(); function onModalAccount() { onModal(<ModalAccount onCloseModal={onClear} />); toast.success(<A href="">link</A>); } return ( <button class="flex items-center justify-center gap-4" onClick={onModalAccount}> <ButtonRound> <IconLogo /> </ButtonRound> {data() && <p class="text-xs">{data()?.data?.name}</p>} {/* {modal()} */} </button> ); } import { useLocation } from "@solidjs/router"; import { createContext, useContext, JSXElement, ParentComponent, createSignal, createEffect, Show } from "solid-js"; import { Portal } from "solid-js/web"; type ModalContextType = { onModal: (modal: JSXElement) => void; onClear: () => void; rawModal: () => JSXElement | undefined; }; const ModalContext = createContext<ModalContextType>(); export const ModalProvider: ParentComponent = (props) => { const [rawModal, setRawModal] = createSignal<JSXElement | undefined>(); const location = useLocation(); function onModal(modal: JSXElement) { setRawModal(modal); } function onClear() { setRawModal(undefined); } createEffect(() => { location.pathname; onClear(); }); const value: ModalContextType = { onModal, onClear, rawModal, }; return <ModalContext.Provider value={value}>{props.children}</ModalContext.Provider>; }; export function useModal() { const context = useContext(ModalContext); if (!context) { throw new Error("useModal must be used within a ModalProvider"); } const { onClear, onModal } = context; return { onClear, onModal }; } export function ModalInit() { const context = useContext(ModalContext); if (!context) { throw new Error("useModal must be used within a ModalProvider"); } return ( <Portal> <Show when={!!context.rawModal()}>{context.rawModal()}</Show> </Portal> ); } import { ButtonRound } from "./ButtonRound"; import { IconChat } from "../icons/IconChat"; import { useModal } from "~/hooks/useModal"; import { A } from "@solidjs/router"; export default function ButtonChat(props: { route?: string }) { const { onModal, onClear } = useModal(); function onModalChat() { onModal( <div class="flex flex-col gap-4 p-4 py-8 w-full h-full overflow-y-auto bg-white" onClick={(e) => e.stopPropagation()}> <A href="/reserved">my reserved</A> </div> ); } return ( <ButtonRound onClick={onModalChat}> <IconChat /> </ButtonRound> ); }
Madaxen86
Madaxen86β€’5w ago
1. Did you place the ModalProvider inside the router? 2. maybe ModalInit should make use of the children helper to not call the context.rawModal() twice.
saeed latifi
saeed latifiOPβ€’4w ago
yes, i do export default function App() { return ( <Router root={(props) => ( <ModalProvider> <Suspense> <AuthChecker>{props.children}</AuthChecker> {/* <ModalProvider /> */} <Toaster position="top-center" /> </Suspense> <ModalInit /> </ModalProvider> )} > <FileRoutes /> </Router> ); } and my modal work correctly until use <A/> or Navigator inside it! can describe more about it please! "maybe ModalInit should make use of the children helper to not call the context.rawModal() twice."
Madaxen86
Madaxen86β€’4w ago
I can reproduce the error using Solid-UI's toast which uses Kobalte's toast. The children thing does not matter. What I found is if you pass the toast content as a function ts will complain but it'll work with the A from the router and the useprimitives like useLocation
showToast({ description: () => <Content />})
showToast({ description: () => <Content />})
saeed latifi
saeed latifiOPβ€’4w ago
just try this import toast from "solid-toast"; import { A } from "@solidjs/router"; export function Account() { function onModalAccount() { toast.success(<A href="">link</A>); } return ( <button class="flex items-center justify-center gap-4" onClick={onModalAccount}> make toast </button> ); } it use "solid-toast" and when toast <A/> tag reproduce my modal error!??
Madaxen86
Madaxen86β€’4w ago
I can reproduce it. The solution is to pass the message as function instead of the JSX Element
//❌ not working
toast.success(<A href="/">link</A>)

//βœ… working
toast.success(() => <A href="">link</A>)
//❌ not working
toast.success(<A href="/">link</A>)

//βœ… working
toast.success(() => <A href="">link</A>)
saeed latifi
saeed latifiOPβ€’4w ago
it worked many tanks i simplified my modal like react-toast
import { JSX, createRoot } from "solid-js";
import { createStore } from "solid-js/store";

interface Modal {
modal?: JSX.Element;
}

const [store, setStore] = createStore<Modal>({});

export function useModal() {
function onModal(modal: () => JSX.Element) {
// Use createRoot to preserve context
const modalElement = createRoot(() => modal());
setStore("modal", modalElement);
}

function onClear() {
setStore("modal", undefined);
}

return { modal: () => store.modal, onModal, onClear };
}

export function ModalRoot() {
return (
<div>
<style>{`.sldt-active{z-index:9999;}.sldt-active>*{pointer-events:auto;}`}</style>
{store.modal}
</div>
);
}
import { JSX, createRoot } from "solid-js";
import { createStore } from "solid-js/store";

interface Modal {
modal?: JSX.Element;
}

const [store, setStore] = createStore<Modal>({});

export function useModal() {
function onModal(modal: () => JSX.Element) {
// Use createRoot to preserve context
const modalElement = createRoot(() => modal());
setStore("modal", modalElement);
}

function onClear() {
setStore("modal", undefined);
}

return { modal: () => store.modal, onModal, onClear };
}

export function ModalRoot() {
return (
<div>
<style>{`.sldt-active{z-index:9999;}.sldt-active>*{pointer-events:auto;}`}</style>
{store.modal}
</div>
);
}
but Error still exist <A> and 'use' router primitives can be only used inside a Route. can help me fix it
export function Test() {
const {} = useAccount();
const { onModal } = useModal();

function onShowModal() {
onModal(() => (
<div >
<A href="/history">:m:y issue</A>

<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
));
}

return (
<div>
<button class="flex items-center justify-center gap-4" onClick={onShowModal}></button>
</div>
);
}
export function Test() {
const {} = useAccount();
const { onModal } = useModal();

function onShowModal() {
onModal(() => (
<div >
<A href="/history">:m:y issue</A>

<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
));
}

return (
<div>
<button class="flex items-center justify-center gap-4" onClick={onShowModal}></button>
</div>
);
}
Madaxen86
Madaxen86β€’4w ago
createRoot causes this. The new root breaks the router context.
saeed latifi
saeed latifiOPβ€’4w ago
in the first, I try without createRoot.
import { JSX } from "solid-js";
import { createStore } from "solid-js/store";

interface Modal {
modal?: JSX.Element;
}

const [store, setStore] = createStore<Modal>({});

export function useModal() {
function onModal(modal: () => JSX.Element) {
setStore("modal", modal());
}

function onClear() {
setStore("modal", undefined);
}

return { modal: () => store.modal, onModal, onClear };
}

export function ModalRoot() {
return (
<div>
<style>{`.sldt-active{z-index:9999;}.sldt-active>*{pointer-events:auto;}`}</style>
{store.modal}
</div>
);
}
import { JSX } from "solid-js";
import { createStore } from "solid-js/store";

interface Modal {
modal?: JSX.Element;
}

const [store, setStore] = createStore<Modal>({});

export function useModal() {
function onModal(modal: () => JSX.Element) {
setStore("modal", modal());
}

function onClear() {
setStore("modal", undefined);
}

return { modal: () => store.modal, onModal, onClear };
}

export function ModalRoot() {
return (
<div>
<style>{`.sldt-active{z-index:9999;}.sldt-active>*{pointer-events:auto;}`}</style>
{store.modal}
</div>
);
}
but it dos not work, then look at solid-toast and they use createRoot so I try this I found some tricks against use <A/> or navigation to make my app works, but is not a real solution for this . I am react experienced but new to solid. please help me solve and more important understand it πŸ™
Madaxen86
Madaxen86β€’4w ago
What’s the relation between Modal and Solid-Toast? I am afk. But I suppose it could be the initialisation of createStore outside a component that could cause errors. Try moving it inside a component and share the state with a Context . This will also prevent memory leaks.
Madaxen86
Madaxen86β€’4w ago
GitHub
kobalte/packages/core/src/dialog at main Β· kobaltedev/kobalte
A UI toolkit for building accessible web apps and design systems with SolidJS. - kobaltedev/kobalte
Brendonovich
Brendonovichβ€’4w ago
The problem is you're still creating the JSX element outside of ModalRoot, try making the store hold a () => JSX.Element
saeed latifi
saeed latifiOPβ€’4w ago
πŸ‘ I tried context first. And use toast just for refrence as part of app behive like modals. Thanks i will read kobalte OK! And thanks 😁 Can give an example please?
Brendonovich
Brendonovichβ€’4w ago
function onModal(modal: () => JSX.Element) {
setStore("modal", () => modal);
}

...

export function ModalRoot() {
return (
<div>
<style>{`.sldt-active{z-index:9999;}.sldt-active>*{pointer-events:auto;}`}</style>
{store.modal?.()}
</div>
);
}
function onModal(modal: () => JSX.Element) {
setStore("modal", () => modal);
}

...

export function ModalRoot() {
return (
<div>
<style>{`.sldt-active{z-index:9999;}.sldt-active>*{pointer-events:auto;}`}</style>
{store.modal?.()}
</div>
);
}
saeed latifi
saeed latifiOPβ€’4w ago
I don't have much xp in solidπŸ˜…
Brendonovich
Brendonovichβ€’4w ago
the () => modal is necessary to stop setStore from executing the modal function itself
saeed latifi
saeed latifiOPβ€’4w ago
Thanks πŸ‘ I will try this way
Brendonovich
Brendonovichβ€’4w ago
the key thing is that the <A> (which does useContext internally) needs to be evaluated underneath the Router. with all your other setups the JSX was being evaluated in the event handler

Did you find this page helpful?