S
SolidJS17mo ago
elite174

[SPA Routing]: How to not call data function for the protected routes

Let's imagine we have the following app
const App = () => (
<AuthService>
<Router>
<Routes>
<Route path="/" data={layoutDataFunction} component={LazyPageLayout}>
<Route path="login" component={LazyLoginPage} />
<Route path="" component={LazyDefaultLayout}>
<Route path="content" component={LazyContentPage} data={contentPageDataFunction} />
{/* Other pages */}
</Route>
</Route>
</Routes>
</Router>
</AuthService>
)
const App = () => (
<AuthService>
<Router>
<Routes>
<Route path="/" data={layoutDataFunction} component={LazyPageLayout}>
<Route path="login" component={LazyLoginPage} />
<Route path="" component={LazyDefaultLayout}>
<Route path="content" component={LazyContentPage} data={contentPageDataFunction} />
{/* Other pages */}
</Route>
</Route>
</Routes>
</Router>
</AuthService>
)
; I handle all auth in the layout data function:
export const layoutDataFunction = ({ navigate, location }: RouteDataFuncArgs) => {
const { isLogged } = useContext(AuthContext);

createComputed(() => {
if (isLogged()) {
if (location.pathname === "/login") navigate(PagePath.Channel);
} else {
if (location.pathname !== "/login") navigate(PagePath.Login);
}
});
};
export const layoutDataFunction = ({ navigate, location }: RouteDataFuncArgs) => {
const { isLogged } = useContext(AuthContext);

createComputed(() => {
if (isLogged()) {
if (location.pathname === "/login") navigate(PagePath.Channel);
} else {
if (location.pathname !== "/login") navigate(PagePath.Login);
}
});
};
When I enter /content page (unlogin state) the redirect hasn't worked yet, so contentPageDataFunction still fetches the data. I want to not load extra data if I don't need it. How to properly cancel the request for the content page in this case? Imagine we have the following contentPageDataFunction function:
export const contentPageDataFunction = ({data}) => {
// I know that here we could get logged state from the parent
// data function and then inside a resource make this check.
// However in this case I'll need to change TS types, because
// it will return <User | null> (if I return null for unlogged state)

// How to deal with that???
const [user] = createResource(async () => {
const result = await myVideosAPI.getMyVideos();

return result;
});

return user;
};
export const contentPageDataFunction = ({data}) => {
// I know that here we could get logged state from the parent
// data function and then inside a resource make this check.
// However in this case I'll need to change TS types, because
// it will return <User | null> (if I return null for unlogged state)

// How to deal with that???
const [user] = createResource(async () => {
const result = await myVideosAPI.getMyVideos();

return result;
});

return user;
};
Is there some RIGHT way to do protect routing for SPA apps?
5 Replies
elite174
elite174OP17mo ago
I don't want to pass the logged state for all data functions which require auth check, because I want to keep the auth logic in one place.
export const contentPageDataFunction = ({data}) => {
const {isLoggedSignal} = data;
const [user] = createResource(isLoggedSignal, async (isLogged) => {
// if I do smth like this
// then I immediately change the state of the resource
// which will affect suspense (the state will instantly be 'ready')
// and TS handling stuff of course...
if (!isLogged) return null;

const result = await myVideosAPI.getMyVideos();

return result;
});

return user;
};
export const contentPageDataFunction = ({data}) => {
const {isLoggedSignal} = data;
const [user] = createResource(isLoggedSignal, async (isLogged) => {
// if I do smth like this
// then I immediately change the state of the resource
// which will affect suspense (the state will instantly be 'ready')
// and TS handling stuff of course...
if (!isLogged) return null;

const result = await myVideosAPI.getMyVideos();

return result;
});

return user;
};
EXAMPLE APP 1/2:
import { Navigate, Outlet, Route, RouteDataFuncArgs, Router, Routes, useRouteData } from "@solidjs/router";
import { ParentComponent, createComputed, createContext, createResource, createSignal, useContext } from "solid-js";

const AuthContext = createContext();

export const AuthService: ParentComponent = (props) => {
const [isLogged, setLogged] = createSignal(false);

return (
<AuthContext.Provider
value={{
isLogged,
logout: () => setLogged(false),
login: () => setLogged(true),
}}
>
{props.children}
</AuthContext.Provider>
);
};

export const layoutDataFunction = ({ navigate, location }: RouteDataFuncArgs) => {
console.log("[DF]: layout");
const { isLogged } = useContext(AuthContext)!;

createComputed(() => {
if (isLogged()) {
if (location.pathname === "/login") {
console.log("Navigate to /content", isLogged());
navigate("/content");
}
} else {
if (location.pathname !== "/login") {
console.log("Navigate to /login", isLogged());
navigate("/login");
}
}
});
};

export const channelPageDataFunction = () => {
console.log("[DF]: content");
const [data] = createResource(async () => {
console.log("THIS thing is called despite the fact that navigate calls earlier");

await new Promise((resolve) =>
setTimeout(() => {
resolve;
}, 2000),
);

return "data!";
});

return data;
};
import { Navigate, Outlet, Route, RouteDataFuncArgs, Router, Routes, useRouteData } from "@solidjs/router";
import { ParentComponent, createComputed, createContext, createResource, createSignal, useContext } from "solid-js";

const AuthContext = createContext();

export const AuthService: ParentComponent = (props) => {
const [isLogged, setLogged] = createSignal(false);

return (
<AuthContext.Provider
value={{
isLogged,
logout: () => setLogged(false),
login: () => setLogged(true),
}}
>
{props.children}
</AuthContext.Provider>
);
};

export const layoutDataFunction = ({ navigate, location }: RouteDataFuncArgs) => {
console.log("[DF]: layout");
const { isLogged } = useContext(AuthContext)!;

createComputed(() => {
if (isLogged()) {
if (location.pathname === "/login") {
console.log("Navigate to /content", isLogged());
navigate("/content");
}
} else {
if (location.pathname !== "/login") {
console.log("Navigate to /login", isLogged());
navigate("/login");
}
}
});
};

export const channelPageDataFunction = () => {
console.log("[DF]: content");
const [data] = createResource(async () => {
console.log("THIS thing is called despite the fact that navigate calls earlier");

await new Promise((resolve) =>
setTimeout(() => {
resolve;
}, 2000),
);

return "data!";
});

return data;
};
EXAMPLE APP: 2/2
export const App = () => (
<AuthService>
<Router>
<Routes>
<Route
path="/"
data={layoutDataFunction}
component={() => (
<div>
Layout
<Outlet />
</div>
)}
>
<Route
path="login"
component={() => (
<div>
<h1>LOGIN PAGE</h1>
<button type="button" onClick={useContext(AuthContext).login}>
Login
</button>
</div>
)}
/>
<Route path="">
<Route
path="content"
component={() => {
const data = useRouteData();

return (
<div>
<h1>CONTENT PAGE</h1>
<button type="button" onClick={useContext(AuthContext).logout}>
logout
</button>
</div>
);
}}
data={channelPageDataFunction}
/>
{/** Other pages */}
</Route>
<Route path="*" element={<Navigate href="/content" />} />
</Route>
</Routes>
</Router>
</AuthService>
);
export const App = () => (
<AuthService>
<Router>
<Routes>
<Route
path="/"
data={layoutDataFunction}
component={() => (
<div>
Layout
<Outlet />
</div>
)}
>
<Route
path="login"
component={() => (
<div>
<h1>LOGIN PAGE</h1>
<button type="button" onClick={useContext(AuthContext).login}>
Login
</button>
</div>
)}
/>
<Route path="">
<Route
path="content"
component={() => {
const data = useRouteData();

return (
<div>
<h1>CONTENT PAGE</h1>
<button type="button" onClick={useContext(AuthContext).logout}>
logout
</button>
</div>
);
}}
data={channelPageDataFunction}
/>
{/** Other pages */}
</Route>
<Route path="*" element={<Navigate href="/content" />} />
</Route>
</Routes>
</Router>
</AuthService>
);
When I open '/content' page in the browser, I see this:
elite174
elite174OP17mo ago
No description
elite174
elite174OP17mo ago
Here you can see that navigate runs before content DF, however DF is still running! Is it a bug?
DevinRhode2
DevinRhode217mo ago
I haven't read all this yet, but <Route path="" seems invalid. From a readability perspective idk how to interpret it.
elite174
elite174OP17mo ago
It's valid. in my app I use this route for common layout, but I omitted it here. For instance I render there navigation, but I don't need navigation for login page
Want results from more Discord servers?
Add your server