I create a context for user authenticated.It like
interface IAuthContext{
isAuthenticated:boolean
setIsAuthenticated:Dispatch<SetStateAction<boolean>>
checkAuth: () => Promise<void>
logoutClient : () =>void,
}
const defaultIsAuthenticated = false
export const AuthContext = createContext<IAuthContext>({
isAuthenticated:defaultIsAuthenticated,
setIsAuthenticated: () =>{},
checkAuth:() => Promise.resolve(),
logoutClient : () =>{}
})
const AuthContextProvider= ({children} : {children:ReactNode}) =>{
const [isAuthenticated,setIsAuthenticated] = useState(defaultIsAuthenticated)
const checkAuth = async () =>{
//check if token is existing
const token = JwtManager.getToken()
if(token) setIsAuthenticated(true)
else{
const success = await JwtManager.getRefreshToken()
if(success) setIsAuthenticated(true)
}
}
const authContextData = {
isAuthenticated,
setIsAuthenticated,
checkAuth,
logoutClient
}
return(<AuthContext.Provider value={authContextData}>
{children}
</AuthContext.Provider>)
}
export default AuthContextProvider
I use it in _app.tsx , but it not work. Whenever i move it to another component, it work.
This is code in _app.tsx
function MyApp({ Component, pageProps }: AppProps) {
const { checkAuth } = useContext(AuthContext);
useEffect(() => {
const authenticate = async () => {
await checkAuth();
};
authenticate();
}, []);
return (
<ApolloProvider client={client}>
<AuthContextProvider>
<ChakraProvider resetCSS theme={theme}>
<Component {...pageProps} />
</ChakraProvider>
</AuthContextProvider>
</ApolloProvider>
);
}
export default MyApp;
I must use CheckAuth() in _app.tsx for user authenticated, so how can I solve this problem?
Related
My UI is not updating on the creation of a project with invalidateQueries. I have confirmed that the database updates are being made successfully and the onSuccess functions are being called. I'm unsure what I am doing wrong, and would love some help.
useProjects.ts
import { getProjects } from 'queries/get-projects';
import { useQuery } from 'react-query';
function useGetProjectsQuery() {
return useQuery('projects', async () => {
return getProjects().then((result) => result.data);
});
}
export default useGetProjectsQuery;
get-project.ts
import { supabase } from '../utils/supabase-client';
export const getProjects = async () => {
return supabase.from('projects').select(`*`);
};
useCreateProject.ts
import { useUser } from '#/utils/useUser';
import { createProject } from 'queries/create-project';
import { useMutation, useQueryClient } from 'react-query';
export const useCreateProject = () => {
const { user } = useUser();
const queryClient = useQueryClient();
return useMutation(
({ title, type, bgColorClass, pinned }: any) => {
return createProject(title, type, bgColorClass, pinned, user.id).then(
(result) => result.data
);
},
{
onSuccess: () => {
queryClient.invalidateQueries('projects');
}
}
);
};
create-project.ts
import { supabase } from '../utils/supabase-client';
export async function createProject(
title: string,
type: string,
bgColorClass: string,
pinned: boolean,
userId: string
) {
return supabase
.from('projects')
.insert([
{ title, type, bg_color_class: bgColorClass, pinned, user_id: userId }
]);
}
Home Component
const { data: projects, isLoading, isError } = useGetProjectsQuery();
const createProject = useCreateProject();
const createNewProject = async () => {
await createProject.mutateAsync({
title: projectName,
type: selectedType.name,
bgColorClass: _.sample(projectColors),
pinned: false
});
};
_app.tsx
export default function MyApp({ Component, pageProps }: AppProps) {
const [initialContext, setInitialContext] = useState();
const [supabaseClient] = useState(() =>
createBrowserSupabaseClient<Database>()
);
useEffect(() => {
document.body.classList?.remove('loading');
}, []);
const getUserDetails = async () =>
supabaseClient.from('users').select('*').single();
const getSubscription = async () =>
supabaseClient
.from('subscriptions')
.select('*, prices(*, products(*))')
.in('status', ['trialing', 'active'])
.single();
const getInitialData = async () => {
const userDetails = await getUserDetails();
const subscription = await getSubscription();
setInitialContext({
//#ts-ignore
userDetails: userDetails.data,
subscription: subscription.data
});
};
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 0
}
}
});
useEffect(() => {
getInitialData();
}, []);
return (
<QueryClientProvider client={queryClient}>
<SessionContextProvider supabaseClient={supabaseClient}>
<MyUserContextProvider initial={initialContext}>
<SidebarProvider>
<Component {...pageProps} />
<ReactQueryDevtools initialIsOpen={false} />
</SidebarProvider>
</MyUserContextProvider>
</SessionContextProvider>
</QueryClientProvider>
);
}
I have tried moving the onSuccess followup calls to the Home component, and within the hooks, neither one updates the UI. I'm unsure what I am doing wrong and the react query devtools is not helpful.
You are instantiating QueryClient on every render pass so the cache of query keys is being torn down and rebuilt often.
Instantiate this outside of render:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 0
}
}
});
export default function MyApp({ Component, pageProps }: AppProps) {
const [initialContext, setInitialContext] = useState();
// ... etc
This will ensure the client is always stable.
I've created a common component and exported it, i need to call that component in action based on the result from API. If the api success that alert message component will call with a message as "updated successfully". error then show with an error message.
calling service method in action. is there any way we can do like this? is it possible to call a component in action
You have many options.
1. Redux
If you are a fan of Redux, or your project already use Redux, you might want to do it like this.
First declare the slice, provider and hook
const CommonAlertSlice = createSlice({
name: 'CommonAlert',
initialState : {
error: undefined
},
reducers: {
setError(state, action: PayloadAction<string>) {
state.error = action.payload;
},
clearError(state) {
state.error = undefined;
},
}
});
export const CommonAlertProvider: React.FC = ({children}) => {
const error = useSelector(state => state['CommonAlert'].error);
const dispatch = useDispatch();
return <>
<MyAlert
visible={error !== undefined}
body={error} onDismiss={() =>
dispatch(CommonAlertSlice.actions.clearError())} />
{children}
</>
}
export const useCommonAlert = () => {
const dispatch = useDispatch();
return {
setError: (error: string) => dispatch(CommonAlertSlice.actions.setError(error)),
}
}
And then use it like this.
const App: React.FC = () => {
return <CommonAlertProvider>
<YourComponent />
</CommonAlertProvider>
}
const YourComponent: React.FC = () => {
const { setError } = useCommonAlert();
useEffect(() => {
callYourApi()
.then(...)
.catch(err => {
setError(err.message);
});
});
return <> ... </>
}
2. React Context
If you like the built-in React Context, you can make it more simpler like this.
const CommonAlertContext = createContext({
setError: (error: string) => {}
});
export const CommonAlertProvider: React.FC = ({children}) => {
const [error, setError] = useState<string>();
return <CommonAlertContext.Provider value={{
setError
}}>
<MyAlert
visible={error !== undefined}
body={error} onDismiss={() => setError(undefined)} />
{children}
</CommonAlertContext.Provider>
}
export const useCommonAlert = () => useContext(CommonAlertContext);
And then use it the exact same way as in the Redux example.
3. A Hook Providing a Render Method
This option is the simplest.
export const useAlert = () => {
const [error, setError] = useState<string>();
return {
setError,
renderAlert: () => {
return <MyAlert
visible={error !== undefined}
body={error} onDismiss={() => setError(undefined)} />
}
}
}
Use it.
const YourComponent: React.FC = () => {
const { setError, renderAlert } = useAlert();
useEffect(() => {
callYourApi()
.then(...)
.catch(err => {
setError(err.message);
});
});
return <>
{renderAlert()}
...
</>
}
I saw the similar solution in Antd library, it was implemented like that
codesandbox link
App.js
import "./styles.css";
import alert from "./alert";
export default function App() {
const handleClick = () => {
alert();
};
return (
<div className="App">
<button onClick={handleClick}>Show alert</button>
</div>
);
}
alert function
import ReactDOM from "react-dom";
import { rootElement } from ".";
import Modal from "./Modal";
export default function alert() {
const modalEl = document.createElement("div");
rootElement.appendChild(modalEl);
function destroy() {
rootElement.removeChild(modalEl);
}
function render() {
ReactDOM.render(<Modal destroy={destroy} />, modalEl);
}
render();
}
Your modal component
import { useEffect } from "react";
export default function Modal({ destroy }) {
useEffect(() => {
return () => {
destroy();
};
}, [destroy]);
return (
<div>
Your alert <button onClick={destroy}>Close</button>
</div>
);
}
You can't call a Component in action, but you can use state for call a Component in render, using conditional rendering or state of Alert Component such as isShow.
I am trying to use getStaticProps for my Layout component as described here, but do struggle to solve this for my specific case:
_app.tsx
const NoCheck: React.FC = ({ children }) => <>{children}</>
function App({ Component, pageProps }: AppProps) {
const neoPage = Component as NeoPage
const LayoutComponent = neoPage.Layout || Layout
const { withAuthCheck = true } = neoPage
const CheckAuthComponent = withAuthCheck ? CheckAuth : NoCheck
return (
<CommonProviders pageProps={pageProps} overmindConfig={config}>
<CheckAuthComponent>
<LayoutComponent>
<Component {...pageProps} />
</LayoutComponent>
</CheckAuthComponent>
</CommonProviders>
)
}
export default App
LayoutUnauthorized.tsx
export const LayoutUnauthorized: React.FC<Props> = ({
children,
systemNormal,
}) => {
return (
<Flex>
<SystemStatus systemNormal={systemNormal} />
{children}
</Flex>
)
}
Login.tsx (in /page)
export const Login: NeoPage = () => {
return (
...
)
}
Login.Layout = LayoutUnauthorized
Login.withAuthCheck = false
export async function getStaticProps() {
const res = await fetch("https://company.com")
const systemNormal = await res.ok
return {
props: {
systemNormal,
},
revalidate: 1,
}
}
So Layout has a property I would like getStaticProps to pass. How can this be achieved?
I have contexts/RoomContext.tsx:
import { useState, createContext } from 'react';
const RoomContext = createContext([{}, () => {}]);
const RoomProvider = (props) => {
const [roomState, setRoomState] = useState({ meetingSession: null, meetingResponse: {}, attendeeResponse: {} })
return <RoomContext.Provider value={[roomState, setRoomState]}>
{props.children}
</RoomContext.Provider>
}
export { RoomContext, RoomProvider }
Then in my component, RoomPage.tsx, I have:
const RoomPageComponent = (props) => {
const router = useRouter()
const [roomState, setRoomState] = useContext(RoomContext);
useEffect(() => {
const createRoom = async () => {
const roomRes = await axios.post('http://localhost:3001/live')
console.log('roomRes', roomRes)
setRoomState(state => ({ ...state, ...roomRes.data }))
}
if (router.query?.id) {
createRoom()
}
}, [router])
return <RoomPageWeb {...props} />
}
export default function RoomPage(props) {
return (
<RoomProvider>
<RoomPageComponent {...props} />
</RoomProvider>
)
}
But I get a complaint about the setRoomState:
This expression is not callable.
Type '{}' has no call signatures.
The issue here is that you are trying to use RoomContext in a component(RoomPage) which doesn't have RoomContext.Provider, higher up in the hierarchy since it is rendered within the component.
The solution here to wrap RoomPage with RoomProvider
import { RoomProvider, RoomContext } from '../../contexts/RoomContext'
function RoomPage(props) {
const [roomState, setRoomState] = useContext(RoomContext);
useEffect(() => {
const createRoom = async () => {
const roomRes = await axios.post('http://localhost:3001/live')
console.log('roomRes', roomRes)
setRoomState(state => ({...state, ...roomRes.data}))
}
...
return (
<RoomPageWeb {...props} />
)
export default (props) => (
<RoomProvider><RoomPage {...props} /></RoomProvider>
)
If user is logged in, render the component. If not, render login page. I notice, however, that this function is called twice. The first time, useAuthDataContext() is null. The second time, I get the correct object back.
const PrivateRoute = ({ component, ...options }) => {
const { userData } = useAuthDataContext()
console.log(userData)
const finalComponent = userData != null ? component : Login
return (
<Route {...options} component={finalComponent} />
)
};
export default PrivateRoute
I have rewritten this function as follows. Here, PrivateRoute2 is called only once, and useAuthDataContext() returns null.
const PrivateRoute2 = ({ component: Component, ...rest }) => {
const { userData } = useAuthDataContext()
console.log(userData)
return (
<Route
{...rest}
render={props =>
userData != null ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
)
}
Here is my useAuthDataContext() implementation that is causing the rerender:
export const AuthDataContext = createContext(null)
const initialAuthData = {}
const AuthDataProvider = props => {
const [authData, setAuthData] = useState(initialAuthData)
useLayoutEffect( (props) => {
const getUser = async () => {
try {
const userData = await authService.isAuthenticated()
setAuthData( {userData})
} catch (err) {
setAuthData({})
}
}
getUser()
}, [])
const onLogout = () => {
setAuthData(initialAuthData)
}
const onLogin = newAuthData => {
const userData = newAuthData
setAuthData( {userData} )
}
const authDataValue = useMemo(() => ({ ...authData, onLogin, onLogout }), [authData])
return <AuthDataContext.Provider value={authDataValue} {...props} />
}
export const useAuthDataContext = () => useContext(AuthDataContext)
export default AuthDataProvider
I think i found one solution. See this post https://hackernoon.com/whats-the-right-way-to-fetch-data-in-react-hooks-a-deep-dive-2jc13230