Skip to content

Instantly share code, notes, and snippets.

@samselikoff
Created July 19, 2021 04:32
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save samselikoff/ac8076c6c224786da23c9297567585cf to your computer and use it in GitHub Desktop.
Save samselikoff/ac8076c6c224786da23c9297567585cf to your computer and use it in GitHub Desktop.
Example useAuth hook using Zustand, SWR and Suspense
import firebase from "firebase/app";
import "firebase/auth";
import { gql, GraphQLClient } from "graphql-request";
import { SWRConfig } from "swr";
import create from "zustand";
import { computed } from "zustand-middleware-computed-state";
const firebaseConfig = {
//
};
let firebaseApp = !firebase.apps.length
? firebase.initializeApp(firebaseConfig)
: firebase.app();
let auth = firebaseApp.auth();
const client = new GraphQLClient(
"https://fitness-challenge-backend.herokuapp.com/v1/graphql",
{ headers: {} }
);
let firebaseUser;
let promise = new Promise((r) => (resolve = r));
let resolve;
export const useStore = create(
computed(
(set, get) => ({
currentUser: undefined,
getSessionToken: () => firebaseUser.getIdToken(),
login: (email, password) =>
auth.signInWithEmailAndPassword(email, password),
logout: () => auth.signOut(),
}),
(state) => {
let status =
state.currentUser === undefined
? "unknown"
: state.currentUser === null
? "anonymous"
: "authenticated";
return {
status,
};
}
)
);
auth.onAuthStateChanged(async (f) => {
firebaseUser = f;
if (firebaseUser) {
let res = await fetcher(
currentUserQuery,
JSON.stringify({ id: firebaseUser.uid })
);
useStore.setState({
currentUser: res.users_by_pk,
});
} else {
useStore.setState({ currentUser: null });
}
resolve();
});
let fetcher = async (query, variables = "{}") => {
if (firebaseUser) {
let token = await firebaseUser.getIdToken();
client.setHeader("authorization", token);
}
return client.request(query, JSON.parse(variables));
};
export default function AuthedSWRProvider({ children }) {
let status = useStore((state) => state.status);
if (status === "unknown") {
throw promise;
}
return <SWRConfig value={{ fetcher }}>{children}</SWRConfig>;
}
let currentUserQuery = gql`
query CurrentUser($id: String!) {
users_by_pk(id: $id) {
id
name
}
}
`;
@vaynevayne
Copy link

vaynevayne commented Sep 11, 2023

Hi, before CurrentUser loaded, does the returned promise prevent the whole application from executing down?
Is there a bug? Does getCurrentUser not execute when you refresh the page again?

 if (status === "unknown") {
    throw promise;
  }

Also, to this day, does this code need to be updated? Like, it's got some good ideas, etc.
If you have time, can you provide an online demo? Thanks for your time.

@samselikoff
Copy link
Author

The thrown promise is to integrate with Suspense. So if a component needs currentUser and it hasn't been loaded once yet, that component will suspend, so a parent will render its fallback.

Not sure if it needs to be updated but the ideas should still be relevant!

@vaynevayne
Copy link

https://stackblitz.com/github/vaynevayne/react-auth?file=src%2Frouters.tsx%3AL37

hello, I refer to your idea to build a template project, but there is a problem that the redirect in the rootLoader will not execute. Could you please help me see?

@samselikoff
Copy link
Author

Don't have time to look into this in detail unfortunately! Your best bet is asking on a Discord server or a relevant GitHub Issues forum.

@vaynevayne
Copy link

ok thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment