JCat 🎄
JCat 🎄
SSolidJS
Created by JCat 🎄 on 1/7/2024 in #support
Using a function outside of components
Hi all, I've come across an interesting situation. I am currently doing a project using SolidJS. I have a hook (hello React) that handles authentication:
import { createSignal } from "solid-js";

export function useAuth() {
const [isLoaded, setIsLoaded] = createSignal(false);
const [isAuthed, setIsAuthed] = createSignal(false);

const signup = async (login, password) => { /* request... */ };

const login = async (login, password) => {
try {
/// axios request...
setIsAuthed(true);
} catch (error) {
// error handling
}
};

const refresh = async () => {
try {
// axios request...
setIsAuthed(true);
} catch (error) {}
setIsLoaded(true);
};

const logout = async () => {
try {
// axios request...
} catch (error) {}
setIsAuthed(false);
};

return {
register,
login,
refresh,
logout,
isAuthed,
isLoaded,
};
}
import { createSignal } from "solid-js";

export function useAuth() {
const [isLoaded, setIsLoaded] = createSignal(false);
const [isAuthed, setIsAuthed] = createSignal(false);

const signup = async (login, password) => { /* request... */ };

const login = async (login, password) => {
try {
/// axios request...
setIsAuthed(true);
} catch (error) {
// error handling
}
};

const refresh = async () => {
try {
// axios request...
setIsAuthed(true);
} catch (error) {}
setIsLoaded(true);
};

const logout = async () => {
try {
// axios request...
} catch (error) {}
setIsAuthed(false);
};

return {
register,
login,
refresh,
logout,
isAuthed,
isLoaded,
};
}
There is also a generic site template that has a check if the user is authorized or not to show the right menu items:
import { useNavigate } from "@solidjs/router";
import { useAuth } from "../hooks";

export default function Main({ children }) {
const { isAuthed, logout } = useAuth();
const navigate = useNavigate();

const doLogout = () => {
logout();
navigate("/");
};

return (
<>
<header className="flex items-center justify-between py-3">
<a href="/">
<img src="/logo.png" className="w-12" alt="Logo" />
</a>
{isAuthed() ? (
<nav className="flex items-center gap-4 p-4">
<a href="/profile">Profile</a>
<a href="#" onClick={doLogout}>
Sign out
</a>
</nav>
) : (
<nav className="flex items-center gap-4 p-4">
<a href="/login">Sign in</a>
<a href="/signup">Sign up</a>
</nav>
)}
</header>

<main className="h-[calc(100vh-(56px+1.5rem))]">{children}</main>
</>
);
}
import { useNavigate } from "@solidjs/router";
import { useAuth } from "../hooks";

export default function Main({ children }) {
const { isAuthed, logout } = useAuth();
const navigate = useNavigate();

const doLogout = () => {
logout();
navigate("/");
};

return (
<>
<header className="flex items-center justify-between py-3">
<a href="/">
<img src="/logo.png" className="w-12" alt="Logo" />
</a>
{isAuthed() ? (
<nav className="flex items-center gap-4 p-4">
<a href="/profile">Profile</a>
<a href="#" onClick={doLogout}>
Sign out
</a>
</nav>
) : (
<nav className="flex items-center gap-4 p-4">
<a href="/login">Sign in</a>
<a href="/signup">Sign up</a>
</nav>
)}
</header>

<main className="h-[calc(100vh-(56px+1.5rem))]">{children}</main>
</>
);
}
And also the login page itself which calls the login method:
import { useNavigate } from "@solidjs/router";
import { useAuth } from "../hooks/useAuth";
import { failure } from "../services";

export default function Login() {
const { login } = useAuth();
const navigate = useNavigate();

const submit = async (event) => {
event.preventDefault();

// ...
// A part of the code that is not important for this situation
// ...

if (await login(_login, password)) {
navigate("/");
}
};

return (
{ /* Stylized form with data submission in onSubmit */ }
);
}
import { useNavigate } from "@solidjs/router";
import { useAuth } from "../hooks/useAuth";
import { failure } from "../services";

export default function Login() {
const { login } = useAuth();
const navigate = useNavigate();

const submit = async (event) => {
event.preventDefault();

// ...
// A part of the code that is not important for this situation
// ...

if (await login(_login, password)) {
navigate("/");
}
};

return (
{ /* Stylized form with data submission in onSubmit */ }
);
}
So, the problem is that the state seems to change internally, but is not tracked externally (as if each component in which useAuth() is called has its own state of signals). I initially solved the problem by using createContext/useContext. Later I went to see if there is a state manager library for SolidJS, like Recoil for React. Not finding anything I decided to try to write a wrapper over createContext/useContext similar to Recoil. And what is interesting is that I accidentally noticed unusual behavior of createSingal, and in general all "hooks" in SolidJS. In React all useX functions (useState, useEffect, etc.) should be strictly used only in hooks, which is not the case in SolidJS. I.e. you can write the following code and it will work as it should:
import { createSignal } from "solid-js";

const [isLoaded, setIsLoaded] = createSignal(false);
const [isAuthed, setIsAuthed] = createSignal(false);

export function useAuth() {
// ...
}
import { createSignal } from "solid-js";

const [isLoaded, setIsLoaded] = createSignal(false);
const [isAuthed, setIsAuthed] = createSignal(false);

export function useAuth() {
// ...
}
I would like to know if this is considered normal practice in SolidJS, or should I not write this way to avoid bugs in the future?
5 replies