Have a bit of an issue attempting to get Auth0 info on the logged-in user with our current architecture.
We have redux with #reduxjs/toolkit & react-redux as our state management tool.
We use axios to make HTTP requests via redux-thunk actions.
And now we have a part of our application that allows users to signup/login with Auth0.
So, an example of our problem.
Currently our redux store is setup with some reducers
/* eslint-disable import/no-cycle */
import { configureStore } from '#reduxjs/toolkit';
import thunk from 'redux-thunk';
const createStore = (initialState?: any) => {
return configureStore({
reducer: {
// reducers are here
},
middleware: [thunk],
preloadedState: initialState,
});
};
export default createStore;
Then we attached that to a Provider at the base of our application
import React from 'react';
import { Provider } from 'react-redux';
import createStore from '../store/createStore';
const App = () => {
return (
<Provider store={createStore()}>
//
</Provider>
);
};
export default App;
We have an axios instance function that uses axios to make HTTP requests and handles errors.
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import { getAuthSignature } from '../utils/auth';
export const API_URL = process.env.API_HOST;
const axiosInstance = async <T = any>(requestConfig: AxiosRequestConfig): Promise<AxiosResponse<T>> => {
const { token } = await getAuthSignature();
// I need to access auth0 data here
const { getAccessTokenSilently, isAuthenticated, isLoading, loginWithRedirect, user } = auth0;
if (!token) {
const tokenErr = {
title: 'Error',
message: 'Missing Authentication Token',
success: false,
};
throw tokenErr;
}
try {
let accessToken = token;
// Update authorization token if auth0 user
if(auth0) {
if(isAuthenticcation && user) accessToken = await getAccessTokenSilently({ audience });
else loginWithRedirect();
}
const result = await axios({
...requestConfig,
headers: {
...requestConfig.headers,
authorization: `Bearer ${accessToken}`,
},
});
return result;
} catch (error: any) {
if (error.response) {
if ([401, 403].includes(error.response.status)) {
window.location = '/';
}
const contentType = error?.response?.headers?.['content-type'];
const isHTMLRes = contentType && contentType.indexOf('text/html') !== -1;
const errObj = {
status: error?.response?.status,
statusText: error?.response?.statusText,
errorMessage: isHTMLRes && error?.response?.text && (await error?.response?.text()),
error,
};
throw errObj;
}
throw error;
}
};
export default axiosInstance;
This in an example of a thunk action, we would have something like this that uses the axios instance mentioned above to make the HTTP requests.
import axios, { API_URL } from '../../services/axios';
import { Result } from '../../types/test';
import { AppThunk } from '../../store/store';
import { setResults, setResultsLoading, setTableLoading } from './test.slice';
type DefaultThunk = () => AppThunk<Promise<void>>;
const getResults: DefaultThunk = () => async () => {
dispatch(setTableLoading(true));
try {
const result = await axios<Result[]>(
{
method: 'GET',
url: `${API_URL}/test`,
},
);
dispatch(setResults(result.data));
} catch (err: any) {
console.log({ err });
} finally {
dispatch(setResultsLoading(false));
dispatch(setTableLoading(false));
}
};
export default getResults;
We then dispatch our thunk actions to make HTTP requests and update reducer states in our React components.
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import getResults from '../../reducers/test/test.thunk';
const TestComponent = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getResults());
}, []);
return (
//
);
};
export default TestComponent;
My problem is that I have no idea how to integrate Auth0 gracefully into the current flow, so I do not have to make checks in every react component that uses a thunk action.
Basically I need access to values within the useAuth0 hook from #auth0/auth0-react for example getAccessTokenSilently, isAuthenticated, user & loginWithRedirect. Just to name a few.
We can't use the useAuth0 hook in the axios instance file, as it's not a react component/hook, nor is the thunk file.
So I'm not sure how and where the best place is to get the data so that it is accessible in the axios file, as aforementioned without having to pass it as an argument or something in every redux thunk action.
Perhaps we just need a different approach to the current flow of dispatch > action > axios request?
Is there any way to pass this data in as middleware to redux?
Any help would be greatly appreciated.
I don't believe you'd be able to use a middleware to "sniff" out the auth0 context value because middlewares run outside React. What I'd suggest here is to create a wrapper component that sits between the Auth0Provider and redux Provider components that accesses the auth0 context and dispatches an action to save it into the redux state where it can be selected via useSelector or accessed directly from store.getState().
Fortunately it appears the auth0 context value is already memoized here so it should be able to be directly consumed as a stable reference within the app.
Rough Example:
import { useDispatch } from 'react-redux';
import { useAuth0 } from '#auth0/auth0-react';
import { actions } from '../path/to/auth0Slice';
const Auth0Wrapper = ({ children }) => {
const dispatch = useDispatch();
const auth0 = useAuth0();
useEffect(() => {
dispatch(actions.setAuthContext(auth0));
}, [auth0]);
return children;
};
Create and export the store for consumption within the app.
Store
import { configureStore } from '#reduxjs/toolkit';
import thunk from 'redux-thunk';
import { combineReducers } from 'redux';
...
import auth0Reducer from '../path/to/auth0Slice';
...
const rootReducer = combineReducers({
auth0: auth0Reducer,
... other root state reducers ...
});
const createStore = (initialState?: any) => {
return configureStore({
reducer: rootReducer,
middleware: [thunk],
preloadedState: initialState,
});
};
export default createStore;
App
import Auth0Wrapper from '../path/to/Auth0Wrapper';
import createStore from '../path/to/store';
const store = createStore();
const App = () => {
return (
<Auth0Provider ......>
<Provider store={store}>
<Auth0Wrapper>
// ... JSX ...
</Auth0Wrapper>
</Provider>
</Auth0Provider>
);
};
export store;
export default App;
Create a new Auth0 state slice.
import { createSlice } from '#reduxjs/toolkit';
const auth0Slice = createSlice({
name: 'auth0',
initialState: {},
reducers: {
setAuthContext: (state, action) => {
return action.payload;
},
},
});
export const actions = {
...auth0Slice.actions,
};
export default auth0Slice.reducer;
From here you can import the exported store object and access the current state inside the axios setup.
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import store from '../path/to/App';
import { getAuthSignature } from '../utils/auth';
export const API_URL = process.env.API_HOST;
const axiosInstance = async <T = any>(requestConfig: AxiosRequestConfig): Promise<AxiosResponse<T>> => {
const { token } = await getAuthSignature();
const { auth0 } = store.getState(); // <-- access current state from store
const {
getAccessTokenSilently,
isAuthenticated,
isLoading,
loginWithRedirect,
user
} = auth0;
...
};
The hook methods are great if you're not using redux, but since you are, the recommended approach is to use the spa js library - https://github.com/auth0/auth0-spa-js/.
Here's a code example for a rest call:
document.getElementById('call-api').addEventListener('click', async () => {
const accessToken = await auth0.getTokenSilently();
const result = await fetch('https://myapi.com', {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const data = await result.json();
console.log(data);
});
https://github.com/auth0/auth0-spa-js/blob/master/EXAMPLES.md#calling-an-api
This is easily adaptable to thunks, in your case, inside of your axios instance ie:
const axiosInstance = async <T = any>(requestConfig: AxiosRequestConfig): Promise<AxiosResponse<T>> => {
const accessToken = await auth0.getTokenSilently();
// handle token and request
}
The auth0 with hooks is more like a convenience library, but it's built on top of spa js.
I've tried this and it worked:
const posts = ref{[]}
const urlEndPoint = 'posts'
const getPosts = async () => {
let response = await axios.get('/api/'+urlEndPoint)
posts.value = response.data.data
}
but that one is not dynamic. My goal is to make the urlEndPoint value reactive and set from the components
then i tried this:
const urlEndPoint = ref([])
but I don't know how to send the value of urlEndPoint constant back from the component to the composables.
I tried these in my component:
const urlEndPoint = 'posts'
and
const sendUrlEndPoint = () => {
urlEndPoint = 'posts'
}
but none worked.
is there a way to accomplish this goal? like sending the component name to urlEndPoint value in composable or any other simple way.
Define a composable function named use useFetch :
import {ref} from 'vue'
export default useFetch(){
const data=ref([])
const getData = async (urlEndPoint) => {
let response = await axios.get('/api/'+urlEndPoint)
data.value = response.data.data
}
return {
getData,data
}
in your component import the function and use it like :
const urlEndPoint=ref('posts')
const {getData:getPosts, data:posts}=useFetch()
getPosts(urlEndPoint.value)
Why I see always "loading..."?
I used redux-toolkit and createSlice and fetch data by axios.
I have not any problem by fetching data and my data is in State.
My problem is displaying fetched data.
My Component code is:
import React, {useEffect, useState} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {fetchTrackers} from 'dashboard/dashboardSlice';
export default function TrackerManagerDashboard() {
const [trackersList, setTrackersList] = useState(useSelector(state => state.trackersData));
const [activeTracker, setActiveTracker] = useState(useSelector(state => state.activeTracker));
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchTrackers);
}, []);
if(!trackersList)
return (
<div>loading...</div>
)
return (
<div className="TrackerManagerDashboard">
...
</div>
)
}
and reducer Slice file is:
import { createSlice } from '#reduxjs/toolkit'
import * as env from "../../environments";
import axios from 'axios';
const initialState = {
trackersData: {},
activeTracker: {},
}
const dashboardSlice = createSlice({
name: 'dashboard',
initialState,
reducers: {
setInitialState(state, action) {
state.trackersData = action.payload.data;
state.activeTracker = state.trackersData[Object.keys(state.trackersData)[0]];
},
},
})
export async function fetchTrackers (dispatch, getState) {
try {
const { data } = await axios.get(env.APP_URL + '/fetch/trackers.json');
dispatch(setInitialState({type: 'setInitialState', data }));
} catch(error) {
console.log(error);
}
}
export const { setInitialState} = dashboardSlice.actions
export default dashboardSlice.reducer
Never use useState(useSelector.
That means "create a component-local state with the initial value that the return value of useSelector at the time of first render has". If the Redux state changes later, you will never get to see it, as your useState is already initialized and any change there will not be reflected in your trackersList variable.
Instead, just call useSelector:
const trackersList = useSelector(state => state.trackersData);
const setActiveTracker = useSelector(state => state.activeTracker);
I'm new to hooks and trying to use them more
How can I get data (with Apollo) when a component mount ?
I'm trying to use useQuery inside a useEffect, my code so far looks like this
const MyComponent = () => {
const getME = () => {
const { loading, error, data } = useQuery(ME);
setMe(data.me) // useState hook
console.log('query me: ', me);
};
useEffect(getME);
return (<>
...
</>)
}
but this gives me an error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
edit: this is the query
import { gql } from '#apollo/client';
export const ME = gql`
query me {
profile {
firstName
lastName
}
}
`;
Here is an example on how you should use the useQuery hook and then stock the data in the state
const { loading, data, error } = useQuery(SOME_QUERY)
const [state, setState] = React.useState([])
useEffect(() => {
// do some checking here to ensure data exist
if (data) {
// mutate data if you need to
setState(data)
}
}, [data])`enter code here`
from https://github.com/trojanowski/react-apollo-hooks/issues/158
Right now I am using the HOC withApollo like:
export default connect(mapStateToProps, mapDispatchToProps)(withApollo(withData(Browse)));
then in that component:
render() {
const { client } = this.props;
<Button onPress={() => searchInterestsTab(client)} />
then outside that component:
export const searchInterestsTab = (client) => {
^ but am finding this gets very messy having to pass it into every outside function from my component.
Couldn't I just use:
const apolloClient = new ApolloClient({...})
export default apolloClient;
then:
import apolloClient from './apolloClient';
everywhere?
It should be possible to use it with kind of:
import apolloClient from './apolloClient'
If you look at the usage documantation you see that you can use it. So somewhere most possible in your index.js you should already have
const apolloClient = new ApolloClient({...})
My apollo client is instantiated like this:
import ApolloClient, { addTypename } from 'apollo-client';
const createApolloClient = options => {
return new ApolloClient(Object.assign({}, {
queryTransformer: addTypename,
dataIdFromObject: (result) => {
if (result.id && result.__typename) {
return result.__typename + result.id;
}
return null;
},
}, options))
};
export default createApolloClient;
and in the index.js it is used like this:
...
const client = createApolloClient({
networkInterface: networkInterface,
initialState: window.__APOLLO_STATE__,
ssrForceFetchDelay: 100,
});
....
export {
client,
...
};