ARTICLE AD BOX
I'm building an internal auth SDK that requires a configuration method to run before any of its other functionalities will work. I've put logs all over the place, and I've discovered that when I call that configuration method from my main project, it reaches the method in the package just fine, but then the useEffect that's supposed to respond to those states being set within the package goes off with the old state values. I'm at a loss for why this could be. I've ensured my context provider is at a high enough level in my main app, I've fiddled with the vite.config.ts file (which I currently suspect is the culprit), I've tried shuffling things around in the main app, I've tried removing the useCallback and useEffect hooks from the package and just used regular non-state-based operations (which still didn't work)... There are a bunch of other attempts I'm forgetting too. Here's everything I'm working with. In case it matters, the Auth SDK Package exists in a monorepo using pnpm for dependency management.
AUTH SDK PACKAGE:
src/auth-context.tsx (main file):
import React from 'react'; interface AuthService { configure: ( appType: string, baseAuthSiteUrl: string, baseAuthApiUrl: string, defaultRedirectUrl: string ) => void; startLogin: (state?: any, redirectUrl?: string) => void; } export const AuthContext = React.createContext<AuthService | undefined>(undefined); export const AuthServiceProvider = ({ children }: React.PropsWithChildren) => { const [exampleValue, setExampleValue] = React.useState<string>(''); const [otherExampleValue, setOtherExampleValue] = React.useState<string>(''); const configure = React.useCallback((value: string) => { setExampleValue(value); }, []); const startLogin = React.useCallback((state?: any, redirectUrl?: string) => { if (!exampleValue) { console.log('FAILED STARTING LOGIN'); // this is always being hit throw new Error('Auth Wrapper has not been configured. Ensure that the configure method is called with proper parameters before startLogin.'); } // more code that doesn't matter since this is always throwing an error above setOtherExampleValue('test'); }, [exampleValue]); React.useEffect(() => { console.log(exampleValue); // this always prints an empty string if (!exampleValue) { return; // this is always being hit } // more code that doesn't matter since this is always returning early above }, [exampleValue, otherExampleValue]); let value: AuthService = { configure, startLogin, }; return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; } export const useAuth = (): AuthService => { const context = React.useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within an AuthServiceProvider'); } return context; };src/index.ts:
export * from './auth-context';package.json:
{ "name": "anonymized-package-name", "version": "0.0.1", "description": "anonymized description", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist" ], "repository": { "type": "git", "url": "https://github.com/anonymized-org/anonymized-repo.git", "directory": "anonymized-directory" }, "scripts": { "build": "tsc && vite build", "format": "prettier --write src/**/*", "lint": "eslint src/**/*.{js,ts,json}", "increment-version": "node ../scripts/increment-version.script.js", "publish-package": "node ../scripts/publish.script.js" }, "author": "", "license": "ISC", "devDependencies": { "@types/node": "^25.3.1", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", "@types/rollup-plugin-peer-deps-external": "^2.2.6", "@vitejs/plugin-react-swc": "^3.11.0", "react": "^18.3.1", "rollup-plugin-peer-deps-external": "^2.2.4", "typescript": "^5.9.2", "vite": "^5.4.21", "vite-plugin-dts": "^3.9.1" }, "dependencies": { "react-cookie": "^8.0.1" }, "peerDependencies": { "react": "^18.3.1" } }vite.config.ts:
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react-swc' import dts from 'vite-plugin-dts' import external from 'rollup-plugin-peer-deps-external' import path from 'path' // https://vitejs.dev/config/ export default defineConfig({ resolve: { alias: { '!': path.resolve(__dirname, './src'), }, }, build: { sourcemap: true, lib: { entry: 'src/index.ts', name: 'anonymized-package-name', formats: ['es'], }, rollupOptions: { external: [ 'react' ] } }, plugins: [react({}), dts(), external()], }).babelrc:
{ "presets": ["@babel/preset-env", "@babel/preset-react"] }tsconfig.json:
{ "include": ["src"], "exclude": ["node_modules"], "compilerOptions": { "composite": true, "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "nodenext", "skipLibCheck": true, /* Bundler mode */ "resolveJsonModule": true, "isolatedModules": true, "jsx": "react-jsx", "noEmit": true, /* Linting */ "strict": true, "noUnusedLocals": false, "noUnusedParameters": false, "noFallthroughCasesInSwitch": true, "noImplicitAny": false, /* Other */ "allowSyntheticDefaultImports": true, "moduleResolution": "nodenext", "outDir": "./dist", "rootDir": "./src", "declaration": true, "declarationMap": true, "tsBuildInfoFile": "./.build-cache/.tsbuildinfo", "paths": { "!/*": ["./src/*"], }, } }MAIN PROJECT:
src/app.js:
import React from 'react'; import ContainerComponent from './components/container.component.tsx'; import { CookiesProvider } from 'react-cookie'; import { useFlags } from 'launchdarkly-react-client-sdk'; import { AuthServiceProvider } from 'anonymized-package-name'; export const App = () => { return ( <CookiesProvider> <AuthServiceProvider> <ContainerComponent /> </AuthServiceProvider> </CookiesProvider> ); };src/components/container.component.tsx (massive, antiquated class-component file; simplified heavily here, all regular functionality works as intended):
import React, { Component } from 'react'; import { withCookies } from 'react-cookie'; import AuthContainer from './authContainer.component.tsx'; class ContainerComponent extends Component<any, any> { constructor(props) { // existing constructor things } // lots of regular functionality stuff that already existed render() { return ( <AuthContainer> {/* regular existing content */} </AuthContainer> ); } } export default withCookies(ContainerComponent);src/components/authContainer.component.tsx:
import React, { useState, useEffect } from 'react'; import { useAuth } from 'anonymized-package-name'; const FusionAuthContainer = ({ children }) => { const { configure, startLogin } = useAuth(); useEffect(() => { configure('test'); setTimeout(() => { // timeout just for testing startLogin(); }, 5000); }, [configure, startLogin]); // this previously returned an actual container with its children inside, but I've simplified it while testing this return <></>; }; export default FusionAuthContainer;What I'm expecting is that once the useEffect in that last file runs, it runs the configure method from the package, which does happen and I've confirmed that the package is receiving the value passed in. Then I'd think that while it's waiting on that 5-second timeout, the useEffect from inside the package should have time to go off with the new value. Again, that 5-second timeout is just for testing, once I know this works it'll be more stateful and elegant. but what actually happens is the useEffect inside the package only goes off once right after I call configure (curious, since I'd think it would go off an additional time with initial values on first load), and it always has the empty initial value, and so returns early. This context is written exactly like other working contexts I've written in the past, the only difference is it's in another package and being imported. I'm stumped and would appreciate any insight.
