Created
March 4, 2022 13:10
-
-
Save samselikoff/9ed4be4052c1b3235b630e39029d26d4 to your computer and use it in GitHub Desktop.
Zustand auth store vs Valtio auth store
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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