S
SolidJS•12mo ago
Andre

Is there a better way of doing this sub and unsub? I fell like I am just translating React code xD

function AuthProvider({ children }: { children: JSXElement }) {
const [user, setUser] = createSignal<User | null>(null);
const [loading, setLoading] = createSignal(true);

let unsubscribe: () => void;
onMount(() => {
unsubscribe = auth.onAuthStateChanged((user) => {
console.log("User changed");
if (user) {
console.log("User is signed in, setting user");
setUser(user);
} else setUser(null);
console.log("Finished loading user");
setLoading(false);
});
});
onCleanup(() => {
unsubscribe();
});

// Handler object (login, logout, etc)

return <AuthContext.Provider value={{ user, loading }}>{children}</AuthContext.Provider>;
}
function AuthProvider({ children }: { children: JSXElement }) {
const [user, setUser] = createSignal<User | null>(null);
const [loading, setLoading] = createSignal(true);

let unsubscribe: () => void;
onMount(() => {
unsubscribe = auth.onAuthStateChanged((user) => {
console.log("User changed");
if (user) {
console.log("User is signed in, setting user");
setUser(user);
} else setUser(null);
console.log("Finished loading user");
setLoading(false);
});
});
onCleanup(() => {
unsubscribe();
});

// Handler object (login, logout, etc)

return <AuthContext.Provider value={{ user, loading }}>{children}</AuthContext.Provider>;
}
Hey! I was wondering id there would be a better way of handling this subscription on mount and unsubscription on unmount that I am not aware off. Overall for this piece of code I feel like I could be using more the Solid tools, maybe using a store instead of signals for the user and loading, but I will take a better looks at the docs/tutorial to see how I could change that part
20 Replies
REEEEE
REEEEE•12mo ago
Your onCleanup doesn't have to be outside the onMount. You could use a store with reconcile for the user but that's up to you. loading being a signal is fine. Also, don't destructure props
lxsmnsyc
lxsmnsyc•12mo ago
onMount(() => {
onCleanup(auth.onAuthStateChanged((user) => {
console.log("User changed");
if (user) {
console.log("User is signed in, setting user");
setUser(user);
} else setUser(null);
console.log("Finished loading user");
setLoading(false);
}));
});
onMount(() => {
onCleanup(auth.onAuthStateChanged((user) => {
console.log("User changed");
if (user) {
console.log("User is signed in, setting user");
setUser(user);
} else setUser(null);
console.log("Finished loading user");
setLoading(false);
}));
});
Andre
AndreOP•12mo ago
Ohh, nice to know that, thanks! Also, for the props destructuring I have just noticed that I did that when reading the code now Probably did it without thinking because I was trying to ""translate"" some react code that I had here But is there a reason for not doing that or just for convention? Tyy, I wouldn't have noticed I could do it this way haha
// user.context.tsx
import { JSXElement, createContext, createSignal, useContext } from "solid-js";
import { createStore, reconcile } from "solid-js/store";

const AuthContext = createContext();

export default function AuthProvider(props: { children?: JSXElement }) {
const [userStore, setUserStore] = createStore({ user: "test" });
const [loading, setLoading] = createSignal(true);

const handler = {
signInExample: (user: any) => {
setUserStore(reconcile({ user: user }));
console.log(userStore.user);
},
};

return <AuthContext.Provider value={{ user: userStore, loading, handler }}>{props.children}</AuthContext.Provider>;
}

export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("Hook 'useAuth' must be used within an 'AuthProvider'.");
}
return context;
}

// Home.tsx
function Home() {
const { userStore, loading, handler } = useAuth();
console.log(user);

return (
<>
<button
onClick={() => {
handler.signInExample("123");
console.log(userStore.user);
}}
>
Sign in with 123
</button>
<div>{userStore.user}</div>
</>
);
}
// user.context.tsx
import { JSXElement, createContext, createSignal, useContext } from "solid-js";
import { createStore, reconcile } from "solid-js/store";

const AuthContext = createContext();

export default function AuthProvider(props: { children?: JSXElement }) {
const [userStore, setUserStore] = createStore({ user: "test" });
const [loading, setLoading] = createSignal(true);

const handler = {
signInExample: (user: any) => {
setUserStore(reconcile({ user: user }));
console.log(userStore.user);
},
};

return <AuthContext.Provider value={{ user: userStore, loading, handler }}>{props.children}</AuthContext.Provider>;
}

export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("Hook 'useAuth' must be used within an 'AuthProvider'.");
}
return context;
}

// Home.tsx
function Home() {
const { userStore, loading, handler } = useAuth();
console.log(user);

return (
<>
<button
onClick={() => {
handler.signInExample("123");
console.log(userStore.user);
}}
>
Sign in with 123
</button>
<div>{userStore.user}</div>
</>
);
}
Wouldn't using a store require me to access it with userStore.user every time? I did a version where a passed userStore.user as the "user" value in the provider but that made it so the Home.tsx didn't listen to updates in the user. This what the code that didn't work:
export default function AuthProvider(props: { children?: JSXElement }) {
const [userStore, setUserStore] = createStore({ user: "test" });
const [loading, setLoading] = createSignal(true);

const handler = {
signInExample: (user: any) => {
setUserStore(reconcile({ user: user }));
console.log(userStore.user); // Logs "123" after the button is clicked
},
};

return (
<AuthContext.Provider value={{ user: userStore.user, loading, handler }}>{props.children}</AuthContext.Provider>
);
}

function Home() {
const { user, loading, handler } = useAuth();
console.log(user);

return (
<>
<button
onClick={() => {
handler.signInExample("123");
console.log(user); // Logs "test" after the button is clicked
}}
>
Sign in with 123
</button>
<div>{user}</div>
</>
);
}
export default function AuthProvider(props: { children?: JSXElement }) {
const [userStore, setUserStore] = createStore({ user: "test" });
const [loading, setLoading] = createSignal(true);

const handler = {
signInExample: (user: any) => {
setUserStore(reconcile({ user: user }));
console.log(userStore.user); // Logs "123" after the button is clicked
},
};

return (
<AuthContext.Provider value={{ user: userStore.user, loading, handler }}>{props.children}</AuthContext.Provider>
);
}

function Home() {
const { user, loading, handler } = useAuth();
console.log(user);

return (
<>
<button
onClick={() => {
handler.signInExample("123");
console.log(user); // Logs "test" after the button is clicked
}}
>
Sign in with 123
</button>
<div>{user}</div>
</>
);
}
REEEEE
REEEEE•12mo ago
It breaks reactivity when you destructure The reason its not reactive, afaik, is because you're reading the userStore.user in the value prop of the context which isn't reactive. You could call the store user and define all the properties of the store there. Then just pass the store to the context and you'll be able to do user.name etc..
Andre
AndreOP•12mo ago
Ohh, that makes sense!! Ty for the warning Hey @lxsmnsyc 🤖, I got an "Unrecognized value. Skipped inserting" error and I saw you have answered some issues related to this on github Can you tell what could be wrong in this code:
export default function AuthProvider(props: { children?: JSXElement }) {
const [user, setUser] = createSignal<User | null>(null); // User is as firebase User type (it an object)
const [loading, setLoading] = createSignal(true);

onMount(() => {
onCleanup(
auth.onAuthStateChanged((user) => {
console.log("User changed");
if (user) {
console.log("User is signed in, setting user");
setUser(user); // WARNING LOG HERE
} else setUser(null);
console.log("Finished loading user");
setLoading(false);
}),
);
});

const handler = {};

return <AuthContext.Provider value={{ user, loading, handler }}>{props.children}</AuthContext.Provider>;
}
export default function AuthProvider(props: { children?: JSXElement }) {
const [user, setUser] = createSignal<User | null>(null); // User is as firebase User type (it an object)
const [loading, setLoading] = createSignal(true);

onMount(() => {
onCleanup(
auth.onAuthStateChanged((user) => {
console.log("User changed");
if (user) {
console.log("User is signed in, setting user");
setUser(user); // WARNING LOG HERE
} else setUser(null);
console.log("Finished loading user");
setLoading(false);
}),
);
});

const handler = {};

return <AuthContext.Provider value={{ user, loading, handler }}>{props.children}</AuthContext.Provider>;
}
Andre
AndreOP•12mo ago
No description
REEEEE
REEEEE•12mo ago
I think the issue is where you're using user you might be using user in the jsx like <div> {user} </div>, and that will throw a warning I believe
Andre
AndreOP•12mo ago
oh, let me see Oh, I just coppied and pasted that code and now it worked, maybe it was the incorrect use of user instead of user() in the JSX somewhere else indeed Also, now I kinda feel that using a store would be better @._rb, I would try to implement that I started going this route of making the store have all properties of User but I realized stores can't be set to null or undefined, only objects
REEEEE
REEEEE•12mo ago
You can do it the original way if you want, you just need to make the user property in the context value object to be a getter
Andre
AndreOP•12mo ago
And I was following the route of checking if the user is logged in with if (user), and if (user = {}) would be a true so that would work You mean with a signal?
REEEEE
REEEEE•12mo ago
<AuthContext.Provider value={{ get user(){ return userStore.user}, loading, handler }}>
{props.children}
</AuthContext.Provider>
<AuthContext.Provider value={{ get user(){ return userStore.user}, loading, handler }}>
{props.children}
</AuthContext.Provider>
Something like this
Andre
AndreOP•12mo ago
ohh interesting Is there a direct advantage of using a store instead of a signal for that?
REEEEE
REEEEE•12mo ago
<AuthContext.Provider value={{ user: () => userStore.user, loading, handler }}>
{props.children}
</AuthContext.Provider>
<AuthContext.Provider value={{ user: () => userStore.user, loading, handler }}>
{props.children}
</AuthContext.Provider>
Or like this if you rather do user().name
Andre
AndreOP•12mo ago
Tbh right now I just feel that calling user.email would be more elegant that user().email so thats why I am going that route
REEEEE
REEEEE•12mo ago
You can do user.email here
Andre
AndreOP•12mo ago
Nice, good idea
REEEEE
REEEEE•12mo ago
it's a JS getter not a function
Andre
AndreOP•12mo ago
Ohh, yeah nice, tyy But is there?
REEEEE
REEEEE•12mo ago
Store vs signal advantage comes when you need to update each property individually and get reactivity for those properties. A store just creates a signal for each property The main reason I suggested a store was so that you can do setUser(reconcile(user)) in the auth state change to only trigger changes on updated properties but I don't think it really matters for User objects so you can use a signal if you'd like
Andre
AndreOP•12mo ago
Ohh, thats great, either way I choose to go, that really explains a lot, tysm
Want results from more Discord servers?
Add your server