[SPA Routing]: How to not call data function for the protected routes
Let's imagine we have the following app
;
I handle all auth in the layout data function:
When I enter
Is there some RIGHT way to do protect routing for SPA apps?
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>
)
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);
}
});
};
/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;
};
5 Replies
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.
EXAMPLE APP 1/2:
EXAMPLE APP: 2/2
When I open '/content' page in the browser, I see this:
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;
};
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;
};
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>
);
Here you can see that navigate runs before content DF, however DF is still running! Is it a bug?
I haven't read all this yet, but
<Route path=""
seems invalid. From a readability perspective idk how to interpret it.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