Skip to content

Instantly share code, notes, and snippets.

@samselikoff
Created March 4, 2022 13:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samselikoff/9ed4be4052c1b3235b630e39029d26d4 to your computer and use it in GitHub Desktop.
Save samselikoff/9ed4be4052c1b3235b630e39029d26d4 to your computer and use it in GitHub Desktop.
Zustand auth store vs Valtio auth store
import firebaseProvider from "@/lib/auth-providers/firebase";
import testProvider from "@/lib/auth-providers/test";
import { proxy, useSnapshot } from "valtio";
import { gql } from "graphql-request";
import request from "@/lib/request";
let isServer = typeof window === "undefined";
let isTest = !isServer && window.Cypress;
let mocksEnabled = process.env.NEXT_PUBLIC_ENABLE_MOCKS;
let providers = {
firebase: firebaseProvider,
test: testProvider,
default: {
initialize() {
let invalid = () => {
throw new Error("You need to set an active auth provider.");
};
return {
onSessionChange: invalid,
getSessionToken: invalid,
login: invalid,
logout: invalid,
onSessionChanged: invalid,
};
},
},
};
let activeProviders = {
firebase: !isServer && !isTest && !mocksEnabled,
test: mocksEnabled || isTest,
};
let currentProvider =
Object.keys(activeProviders).find((key) => activeProviders[key]) || "default";
let provider = providers[currentProvider].initialize();
let resolveInitialSession;
let rejectInitialSession;
let initialSessionPromise = new Promise((resolve, reject) => {
resolveInitialSession = resolve;
rejectInitialSession = reject;
});
let store = proxy({
/** @type { Promise | { token: string, userId: string }} */
session: initialSessionPromise,
/** @type { Promise | { id: string, name: string }} */
currentUser: initialSessionPromise,
login: async (email, password) => {
let session = await provider.login(email, password);
let currentUser = await fetchCurrentUser(session);
store.session = session;
store.currentUser = currentUser;
},
signup: async (email, password, name) => {
let session = await provider.signup({ email, password });
let currentUser = await createUser({ session, name });
store.session = session;
store.currentUser = currentUser;
},
logout: async () => {
await provider.logout();
store.session = null;
store.currentUser = null;
},
/** @type {{ Promise | 'authenticated | 'anonymous' }} */
get status() {
if (this.session instanceof Promise) return this.session;
return this.session ? "authenticated" : "anonymous";
},
});
async function fetchCurrentUser(session) {
return await request({
query: gql`
query CurrentUser($id: String!) {
users_by_pk(id: $id) {
id
name
avatarUrl
}
}
`,
variables: { id: session.userId },
session,
}).then((data) => data?.users_by_pk);
}
async function createUser({ session, name }) {
return await request({
query: gql`
mutation CreateUser($object: users_insert_input!) {
insert_users_one(object: $object) {
id
name
}
}
`,
variables: { object: { id: session.userId, name } },
session,
}).then((data) => data?.insert_users_one);
}
// On client, fetch initial session/user + set up auth listener
if (!isServer) {
provider.onSessionChange(async (session) => {
if (session) {
try {
let currentUser = await fetchCurrentUser(session);
// TODO: Handle session but no currentUser.
store.session = session;
store.currentUser = currentUser;
resolveInitialSession();
} catch (error) {
rejectInitialSession(error);
}
} else {
store.session = null;
store.currentUser = null;
resolveInitialSession();
}
});
}
export default function useAuth() {
return useSnapshot(store);
}
/*
This one does less than the other one but still interesting to compare.
I had a separate useAuth() hook which reexported this store bc I also used this store outside of React
*/
import create from "zustand";
import { computed } from "zustand-middleware-computed-state";
import firebaseProvider from "./providers/firebase";
import testProvider from "./providers/test";
let isServer = typeof window === "undefined";
let isTest = typeof window !== "undefined" && window.Cypress;
let mocksEnabled = process.env.NEXT_PUBLIC_ENABLE_MOCKS;
let providers = {
firebase: firebaseProvider,
test: testProvider,
default: {
initialize() {
let invalid = () => {
throw new Error("Auth store should not be used during SSR");
};
return {
fetchSession: invalid,
getSessionToken: invalid,
login: invalid,
logout: invalid,
onSessionChanged: invalid,
};
},
},
};
let activeProviders = {
firebase: !isServer && !isTest && !mocksEnabled,
test: mocksEnabled || isTest,
};
let currentProvider =
Object.keys(activeProviders).find((key) => activeProviders[key]) || "default";
let provider = providers[currentProvider].initialize();
let store = create(
computed(
(set, get) => ({
session: undefined,
fetchSession: () => provider.fetchSession(),
login: async (email, password) => {
let session = await provider.login(email, password);
set({ session });
},
logout: async () => {
await provider.logout();
set({ session: null });
},
}),
(state) => {
let status =
state.session === undefined
? "unknown"
: state.session === null
? "anonymous"
: "authenticated";
return {
status,
};
}
)
);
export default store;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment