Estoy usando el token auth0 proporcionado por el usuario al iniciar sesión para realizar llamadas de API a través de useAuth0.getTokenSilently.
En este ejemplo, fetchTodoList , addTodoItem y updateTodoItem requieren un token para la autorización. Me gustaría poder extraer estas funciones en un archivo separado (como utils/api-client.js e importarlas sin tener que pasar explícitamente el token.
import React, { useContext } from 'react' import { Link, useParams } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCircle, faList } from '@fortawesome/free-solid-svg-icons' import axios from 'axios' import { queryCache, useMutation, useQuery } from 'react-query' import { TodoItem } from '../models/TodoItem' import { TodoInput } from './TodoInput' import { TodoList as TodoListComponent } from './TodoList' import { TodoListsContext } from '../store/todolists' import { TodoListName } from './TodoListName' import { TodoList } from '../models/TodoList' import { useAuth0 } from '../utils/react-auth0-wrapper' export const EditTodoList = () => { const { getTokenSilently } = useAuth0() const fetchTodoList = async (todoListId: number): Promise<TodoList> => { try { const token = await getTokenSilently!() const { data } = await axios.get( `/api/TodoLists/${todoListId}`, { headers: { Authorization: `Bearer ${token}` } } ) return data } catch (error) { return error } } const addTodoItem = async (todoItem: TodoItem): Promise<TodoItem> => { try { const token = await getTokenSilently!() const { data } = await axios.post( '/api/TodoItems', todoItem, { headers: { Authorization: `Bearer ${token}`, } } ) return data } catch (addTodoListError) { return addTodoListError } } const updateTodoItem = async (todoItem: TodoItem) => { try { const token = await getTokenSilently!() const { data } = await axios.put( '/api/TodoItems', todoItem, { headers: { Authorization: `Bearer ${token}`, } } ) return data } catch (addTodoListError) { return addTodoListError } } const [updateTodoItemMutation] = useMutation(updateTodoItem, { onSuccess: () => { queryCache.refetchQueries(['todoList', todoListId]) } }) const [addTodoItemMutation] = useMutation(addTodoItem, { onSuccess: () => { console.log('success') queryCache.refetchQueries(['todoList', todoListId]) } }) const onAddTodoItem = async (todoItem: TodoItem) => { try { await addTodoItemMutation({ ...todoItem, todoListId: parseInt(todoListId, 10) }) } catch (error) { // Uh oh, something went wrong } } const { todoListId } = useParams() const { status, data: todoList, error } = useQuery(['todoList', todoListId], () => fetchTodoList(todoListId)) const { todoLists, setTodoList } = useContext(TodoListsContext) const todoListIndex = todoLists.findIndex( list => todoListId === list.id.toString() ) const setTodoItems = (todoItems: TodoItem[]) => { // if(todoList) { // const list = { ...todoList, todoItems } // setTodoList(todoListIndex, list) // } } const setTodoListName = (name: string) => { // setTodoList(todoListIndex, { ...todoList, name }) } return ( <> <Link className="block flex align-items-center mt-8" to="/"> <span className="fa-layers fa-fw fa-3x block m-auto group"> <FontAwesomeIcon icon={faCircle} className="text-teal-500 transition-all duration-200 ease-in-out group-hover:text-teal-600" /> <FontAwesomeIcon icon={faList} inverse transform="shrink-8" /> </span> </Link> {status === 'success' && !!todoList && ( <> <TodoListName todoListName={todoList.name} setTodoListName={setTodoListName} /> <TodoInput onAddTodoItem={onAddTodoItem} /> <TodoListComponent todoItems={todoList.todoItems} setTodoItems={setTodoItems} updateTodo={updateTodoItemMutation} /> </> )} </> ) }Aquí hay un enlace al repositorio: https://github.com/gpspake/todo-client
Hay diferentes maneras de resolver esto.
Para no cambiar demasiado su base de código. Yo iría con una tienda con un proveedor y un gancho . Hay muchas bibliotecas de tiendas por ahí.
Aquí hay una versión pequeña que también se puede usar fuera del renderizado de React.
https://github.com/storeon/storeon
Este fue solo un ejemplo de una tienda muy pequeña que pude encontrar que podría cumplir con los requisitos.
El uso de una biblioteca de la tienda fuera de React podría verse así:
import store from './path/to/my/store.js;' // Read data const state = store.get(); // Save data in the store store.dispatch('foo/bar', myToken);No estoy exactamente seguro de por qué no pudo acceder al token dentro de sus funciones individuales. ¿Es porque no serían componentes de la función React sino solo funciones regulares?
Una de las cosas que he hecho es crear un enlace useFetch que puede obtener el token de usuario y adjuntarlo a una solicitud. Luego, en lugar de exportar esas funciones específicamente, puedo simplemente llamar a este nuevo enlace de búsqueda. He aquí un ejemplo de lo que quiero decir.
import React from "react" import { useAuth0 } from "../utils/auth" const useFetch = () => { const [response, setResponse] = React.useState(null) const [error, setError] = React.useState(null) const [isLoading, setIsLoading] = React.useState(false) const { getTokenSilently } = useAuth0() const fetchData = async (url, method, body, authenticated, options = {}) => { setIsLoading(true) try { if (authenticated) { const token = await getTokenSilently() if (!options.headers) { options.headers = {} } options.headers["Authorization"] = `Bearer ${token}` } options.method = method if (method !== "GET") { options.body = JSON.stringify(body) } const res = await fetch(url, options) const json = await res.json() setResponse(json) setIsLoading(false) if (res.status === 200) { return json } throw { msg: json.msg } } catch (error) { console.error(error) setError(error) throw error } } return { response, error, isLoading, fetchData } } export default useFetchEsta es una variante de la respuesta de @james-quick, donde estoy usando una "RequestFactory" para generar solicitudes en el formato axios, y luego solo agrego el encabezado de autenticación de Auth0
Estaba enfrentando el mismo problema y superé esta limitación moviendo toda la lógica de llamadas de mi API a un gancho personalizado que creé:
import { useAuth0 } from '@auth0/auth0-react'; import { useCallback } from 'react'; import makeRequest from './axios'; export const useRequest = () => { const { getAccessTokenSilently } = useAuth0(); // memoized the function, as otherwise if the hook is used inside a useEffect, it will lead to an infinite loop const memoizedFn = useCallback( async (request) => { const accessToken = await getAccessTokenSilently({ audience: AUDIANCE }) return makeRequest({ ...request, headers: { ...request.headers, // Add the Authorization header to the existing headers Authorization: `Bearer ${accessToken}`, }, }); }, [isAuthenticated, getAccessTokenSilently] ); return { requestMaker: memoizedFn, }; }; export default useRequest;Ejemplo de uso:
import { RequestFactory } from 'api/requestFactory'; const MyAwesomeComponent = () => { const { requestMaker } = useRequest(); // Custom Hook ... requestMaker(QueueRequestFactory.create(queueName)) .then((response) => { // Handle response here ... }); }RequestFactory define y genera la carga útil de la solicitud para mis diferentes llamadas a la API, por ejemplo:
export const create = (queueName) => ({ method: 'post', url: '/queue', data: { queueName } });Aquí hay un PR de integración Auth0 completo como referencia.
Estaba teniendo un problema similar sobre cómo usar getAccessTokenSilently fuera de un componente React, lo que terminé fue esto:
Mi envoltorio de cliente HTTP
export class HttpClient { constructor() { HttpClient.instance = axios.create({ baseURL: process.env.API_BASE_URL }); HttpClient.instance.interceptors.request.use( async config => { const token = await this.getToken(); return { ...config, headers: { ...config.headers, Authorization: `Bearer ${token}` }, }; }, error => { Promise.reject(error); }, ); return this; } setTokenGenerator(tokenGenerator) { this.tokenGenerator = tokenGenerator; return this; } getToken() { return this.tokenGenerator(); } } En la raíz de mi aplicación, paso getAccessTokenSilently desde auth0
useEffect(() => { httpClient.setTokenGenerator(getAccessTokenSilently); }, [getAccessTokenSilently]);¡Y eso es!
Ahora tiene una instancia de axios lista para realizar solicitudes autenticadas con