Skip to content

Instantly share code, notes, and snippets.

@qs-wang
Forked from ulises-jeremias/auth-hook.js
Created February 19, 2022 11:22
Show Gist options
  • Select an option

  • Save qs-wang/bfdf4fe722d41ce032e5a29ce1cff8ee to your computer and use it in GitHub Desktop.

Select an option

Save qs-wang/bfdf4fe722d41ce032e5a29ce1cff8ee to your computer and use it in GitHub Desktop.

Revisions

  1. @ulises-jeremias ulises-jeremias revised this gist Jun 22, 2020. No changes.
  2. @ulises-jeremias ulises-jeremias revised this gist Jun 22, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions error-hook.js
    Original file line number Diff line number Diff line change
    @@ -13,6 +13,7 @@ export const useErrorHandler = (hookOptions = {}) => {
    */
    const handleError = useCallback((error, options) => (
    // use error and options here
    ['message', '']
    ), []);

    return {
  3. @ulises-jeremias ulises-jeremias renamed this gist Jun 22, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. @ulises-jeremias ulises-jeremias created this gist Jun 22, 2020.
    55 changes: 55 additions & 0 deletions auth-hook.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,55 @@
    import { useCallback, useEffect, useState } from 'react';
    import { useSetRecoilState } from 'recoil';
    import { useKeycloak } from '@react-keycloak/web';

    import { commonNotification } from './common';

    /**
    * Returns the auth info and some auth strategies.
    *
    */
    export const useAuth = () => {
    const [keycloak, initialized] = useKeycloak();
    const setNotification = useSetRecoilState(commonNotification);

    const [user, setUser] = useState({});

    // fetch user profile
    useEffect(() => {
    if (!initialized) {
    return;
    }

    const fetchUserInfo = async () => {
    try {
    const userProfile = await keycloak.loadUserProfile();

    setUser({ ...userProfile, fullName: `${userProfile.firstName} ${userProfile.lastName}` });
    } catch (err) {
    setNotification({ isVisible: true, message: err.message });
    }
    };

    if (keycloak.authenticated) {
    fetchUserInfo();
    }
    }, [keycloak, initialized]);

    return {
    isAuthenticated: !!keycloak.authenticated,
    initialized,
    meta: {
    keycloak,
    },
    token: keycloak.token,
    user,
    roles: keycloak.realmAccess,
    login: useCallback(() => { keycloak.login(); }, [keycloak]),
    logout: useCallback(() => { keycloak.logout(); }, [keycloak]),
    register: useCallback(() => { keycloak.register(); }, [keycloak]),
    };
    };

    export default {
    useAuth,
    };
    42 changes: 42 additions & 0 deletions axios-hook.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,42 @@
    import axios from 'axios';
    import { useState, useEffect } from 'react';
    import { useAuth } from 'auth-hook';

    /**
    * Returns an authorizated axios instance
    *
    * @param {Object} config is the default config to be sent to the axios creator
    *
    * @return {Object} an object containing the axios instance and the initialized prop
    *
    */
    export const useAxios = (config = {}) => {
    const { token, initialized: authInitialized } = useAuth();

    const [initialized, setInitialized] = useState(false);
    const [axiosInstance, setAxiosInstance] = useState({});

    useEffect(() => {
    const instance = axios.create({
    ...config,
    headers: {
    ...(config.headers || {}),
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    Authorization: authInitialized ? `Bearer ${token}` : undefined,
    },
    });

    setAxiosInstance({ instance });
    setInitialized(true);

    return () => {
    setAxiosInstance({});
    setInitialized(false);
    };
    }, [token, authInitialized]);

    return { axios: axiosInstance.instance, initialized };
    };

    export default { useAxios };
    10 changes: 10 additions & 0 deletions common.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,10 @@
    import { atom } from 'recoil';

    // notification
    export const commonNotification = atom({
    key: 'commonNotification',
    default: {
    isVisible: false,
    message: '',
    },
    });
    23 changes: 23 additions & 0 deletions error-hook
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,23 @@
    import { useCallback } from 'react';

    // Hook defined thinking in the future. Actually it does not behave as a hook.
    export const useErrorHandler = (hookOptions = {}) => {
    /**
    * Error handler
    *
    * En la función se define el flujo de la aplicación en caso de un error.
    *
    * @param {String | Object | Error} error
    * @returns {String[2]}
    *
    */
    const handleError = useCallback((error, options) => (
    // use error and options here
    ), []);

    return {
    handleError,
    };
    };

    export default { useErrorHandler };
    107 changes: 107 additions & 0 deletions request-hook.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,107 @@
    import { useState, useCallback } from 'react';
    import { useHistory } from 'react-router-dom';
    import { useSetRecoilState } from 'recoil';

    import { commonNotification } from './common';
    import { useAxios } from './axios-hook';
    import { useErrorHandler } from './error-hook';

    const API_BASE_URL = '/api/';
    const REQUEST_TIMEOUT = 5;

    /**
    *
    * @param {Object} options request options
    * @param {String} options.url The request url
    * @param {String} options.method The request http method
    * @param {Object} requestParams.initialValue The response data initial value
    *
    * @see useAxios
    * @see axiosDefaultConfig
    * @see mapResponseToData
    *
    * @return {Object} return an object containing the data, isLoading and the request strategy.
    *
    */
    export const useRequest = (options = {}, axiosConfig = {}) => {
    const [data, setData] = useState(options.initialValue);
    const [isLoading, setLoading] = useState(false);

    const setNotification = useSetRecoilState(commonNotification);
    const { handleError } = useErrorHandler();
    const { axios, initialized: axiosInitialized } = useAxios({
    ...axiosDefaultConfig,
    ...axiosConfig,
    });

    const history = useHistory();

    /**
    * Specific request for options
    *
    * @param {Object} requestParams Request
    * @param {Object} requestParams.params The request query params
    * @param {Object} requestParams.data The request body data
    *
    */
    const request = (requestParams) => {
    if (!axiosInitialized) {
    return;
    }

    const fetchData = async () => {
    setLoading(true);

    try {
    const response = await axios({ ...options, ...requestParams });
    const responseData = mapResponseToData(response);

    setData(responseData);
    setLoading(false);
    } catch (err) {
    const [message, redirect] = handleError(err);

    setLoading(false);
    setNotification({ message, isVisible: true });

    if (redirect) {
    history.push(redirect);
    }
    }
    };

    fetchData();
    };

    const fetch = useCallback(request, [axiosInitialized]);

    return { isLoading, data, fetch };
    };

    /**
    * Request default config for axios creator
    *
    * @see API_BASE_URL
    * @see REQUEST_TIMEOUT
    */
    const axiosDefaultConfig = {
    baseURL: API_BASE_URL,
    withCredentials: true,
    timeout: REQUEST_TIMEOUT,
    };

    /**
    * Maps axios response to data
    *
    * @param {Object} response
    *
    * @return {Object} the response data
    * @throws {Object} throws an object containing the axios response
    */
    export const mapResponseToData = (response) => {
    const { data } = response;

    return data;
    };

    export default { useRequest };