【问题标题】:Problem with using useRef current in useEffect在 useEffect 中使用 useRef current 的问题
【发布时间】:2021-07-03 01:40:43
【问题描述】:

我有以下问题:目前我正在尝试编写一个 customHook,我可以使用它进行所有后端操作。这样做的目的是让我的代码看起来更干净、更容易理解并且更少写。

要理解这个问题,我必须先解释几件事。首先,我正在使用 JWT 令牌并执行一些操作,您需要使用身份验证标头中的令牌进行身份验证。如果我在尝试发布内容时遇到错误,我的函数应该刷新 JWT 令牌并再次调用该函数,如果第一次发生身份验证错误。因为我正在使用功能组件,而 customHook(我认为)只能以功能组件的方式使用钩子,所以在将状态设置为当前错误值后,我必须再次调用前一个函数。因此,我将以前的函数及其参数保存在 useRefs 中,但是当触发 useEffect 时,ref.current 值没有它们应该具有的值。

在下面的代码中只包含了这个问题需要的操作:

使用后端:

import axios from "axios";
import { useEffect, useRef, useState } from "react";

const BASE_URL = 'https://localhost:44372/api/';
const R = 'Requests/'; const M = 'Movies/'; const U = 'Users/';


const defaultOperations = (controller) => {
    return {
       post: (object) => buildParams((`${controller}/Post${controller.substring(0, controller.length-1)}`), 'post', true, object),
       put: (object, id) => buildParams((`${controller}/Put${controller.substring(0, controller.length-1)}/${id}`), 'put', true, object),
       delete: (id) => buildParams((`${controller}/Delete${controller.substring(0, controller.length-1)}/${id}`), 'delete', true),
       getAll: () => buildParams((`${controller}/Get${controller}`), 'get', false),
       getByID: (id) => buildParams((`${controller}/Get${controller.substring(0, controller.length-1)}/${id}`), 'get', false),
    }
}

const buildParams = (url, type, header, param) => {
    return {url: url, type: type, header: header, param: param};
}


export const CONTROLLERS = {
    REQUESTS: {
        post: (object) => defaultOperations('Requests').post(object),
        put: (object, id) => defaultOperations('Requests').put(object, id),
        delete: (id) => defaultOperations('Requests').delete(id),
        getAll: () => defaultOperations('Requests').getAll(),
        getByID: (id) => defaultOperations('Requests').getByID(id),
        getRequestsByUser: () => buildParams(`${R}GetRequestsByUser`, 'post', true, {accessToken: localStorage.accessToken}),
    
    },

    USERS: {
        refreshToken: () => buildParams(`${U}RefreshToken`, 'post', false, {accessToken: localStorage.accessToken, refreshToken: localStorage.refreshToken}),
        login: (mail, password) => buildParams(`${U}Login`, 'post', false, {mail: mail, password: password}),
        register: (object) => buildParams(`${U}Register`, 'post', false, object),
        getUserByAccessToken: () => buildParams(`${U}GetUserByAccessToken`, 'post', true, {accessToken: localStorage.accessToken}),
        authentificate: () => buildParams('authentificate', 'special', true, undefined),
        getAll: () => defaultOperations('Users').getAll(),
        getByID: (id) => defaultOperations('Users').getByID(id),
        post: (object) => defaultOperations('Users').post(object),
        put: (object, id) => defaultOperations('Users').put(object, id),
        delete: (id) => defaultOperations('Users').delete(id),

    },
}


export const  useBackend = (error, setError, initValue) => {
    const [values, setValues] = useState([]);
    const lastFunction = useRef();
    const lastParameter = useRef();


     function selectFunction(objc) {
        switch(objc.type) {
            case 'get':  buildGetAndFetch(objc.url, objc.param, objc.header); break;
            case 'post':  buildPostAndFetch(objc.url, objc.param, objc.header);break;
            case 'put':  buildPutAndFetch(objc.url, objc.param, objc.header);break;
            case 'delete':  buildDeleteAndFetch(objc.url, objc.param, objc.header);break;
            case 'special': authentificate();break;
            default: console.log("Error in Switch");
        }
    }
    if(initValue!==undefined) setValues(initValue);


  


     function buildPostAndFetch(url, param, header) {
         let _param = param;
         if(param?.accessToken !== undefined) {
            _param = {accessToken: localStorage.accessToken};
            if(param?.refreshToken) {
                _param = {...param, refreshToken: localStorage.refreshToken}
            }
         }'GetUserByAccessToken'));
        const finalurl = `${BASE_URL}${url}`;
        if(header) {
            axios.post(finalurl, _param, {headers: {
                'Authorization': `Bearer ${(localStorage.accessToken)}`
            }})
            .then(res => {
                response(res);
            })
            .catch(err => {        
               authError(buildPostAndFetch, url, param, header);           
            })
        }
        else {
            axios.post(finalurl, param)
            .then(res => {
                response(res);
            })
            .catch(err => {
                setError(true);
            })
        }
    }

    

    
    useEffect(() => {
    }, [values])

     function buildDeleteAndFetch(url, param, header) {
        const finalurl = `${BASE_URL}${url}`;
        if(header) {
            axios.delete(finalurl, param, {headers: {'Authorization': `Bearer ${(localStorage.accessToken)}`}})
            .then(res => {
                setValues(values.filter(item => item.requestID !== param));
                setError(false);
            })
            .catch(err => {
                 authError(buildDeleteAndFetch, url, param, header);
            })
        }
        else {
            axios.delete(finalurl, param)
            .then(res => {
                setValues(values.filter(item => item.requestID !== param));
                setError(false);
            })
            .catch(err => {
                setError(true);
            })
        }
    }

    function response(res) {
        setValues(res.data)
        setError(false);
    }

      function refreshToken(err) {
        const finalurl= `${BASE_URL}${U}RefreshToken`;
       
        axios.post(finalurl, {accessToken: localStorage.accessToken, refreshToken: localStorage.refreshToken})
        .then(res => {
            localStorage.accessToken = res.data.accessToken;
            setError(err);
        })
        .catch(err => {
            localStorage.accessToken = undefined;
            localStorage.refreshToken = undefined;
            setError(err);
        });
    }


     function authError(funktion223, url, param, header) {
        if(error!==true) {
            lastFunction.current = funktion223;
            lastParameter.current = [url, param, header];
            refreshToken(true);
        }
    }

    useEffect(() => {
        if(error===true) {
            if(localStorage.accessToken !== undefined && localStorage.refreshToken !== undefined) {
                    const lastfunc = lastFunction.current;
                    const lastparams = lastParameter.current;
                    if(lastfunc !== undefined && lastparams.length > 0 ) {
                        console.log(lastfunc);
                        console.log(lastparams[0]);
                        lastfunc(lastparams[0], lastparams[1], lastparams[2]);
                    }
            }
        }
    }, [error])
    
    return [values, 
            (objc) => selectFunction(objc)];
}

组件说明: 我是 userSettings 组件,当 componentsDidMount 调用钩子以检查用户是否登录时,如果没有,尝试从后端获取其他数据没有任何意义。如果他已登录或至少他的令牌已过期,它将被刷新。在 UserRequests 组件中,当没有错误时,将获取用户的请求。

(我不知道你是否需要这些信息,也许你认为自定义钩子是正确的,而我只是在使用它们的组件中犯了错误)

用户设置:

import React, { useEffect, useState } from 'react';
import {Row, Col} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import '../../App.css';
import ProfileSettings from './profileSettings';
import SettingsChooser from './settingsChooser';
// import SettingRoutings from '../settingRoutings';
import {BrowserRouter as Router, useLocation, useParams} from 'react-router-dom';
// import Routings from '../Routings.js';
import UserRequests from './userRequests';
import useAuth from '../../API/useAuthentification';
import { CONTROLLERS, useBackend } from '../../hooks/useBackend';

function UserSettings({user}) {

    const {title: path} = useParams();
    const [authError, setAuthError] = useState(false);
    const [userValues, authentificate] = useBackend(authError, setAuthError, user);
    let component;
    

    useEffect(() => {
        console.log('render');
        authentificate(CONTROLLERS.USERS.getUserByAccessToken());
    }, [])

    useEffect(() => {
        console.log(userValues);
    }, [userValues])

    if(path ==='logout') {
        // localStorage.accessToken = undefined;
        // localStorage.refreshToken = undefined
    }
    else if(path === 'deleteaccount') {
        //TODO
        //Implement  
    }
        
   component = <UserRequests user={userValues} setAuthError={setAuthError} authError={authError}/>



    return (
            <div classname="">
            <Row className="">
            <Col className="formsettings2"   md={ {span: 3, offset: 1}}>
                <SettingsChooser/>
            </Col>
           <Col  className="ml-5 formsettings2"md={ {span: 6}}>
                     {authError ? <p>No Access, please Login first</p> : component}                 
            </Col>
            </Row>
            </div>
    );
}

export default UserSettings;

请求:

import React, { useEffect, useState } from 'react';
import {Table} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import createAPIEndpoint from '../../API/callBackendAPI';
import {toast } from 'react-toastify';
import Loading from '../Alerts/loading';
import notify from '../Alerts/toasts';
import { ToastProvider } from 'react-toast-notifications';
import useAuth from '../../API/useAuthentification';
import { useHistory } from 'react-router';
import { CONTROLLERS, useBackend } from '../../hooks/useBackend';

toast.configure({
    
});

function UserRequests({user, authError, setAuthError}) {

    const headers = ['ID', 'Title', 'Release Date', 'Producer', 'Director', 'Status', 'UTC Time', '#', '#'];
    const history = useHistory();
    const [requests, requestActions] = useBackend(authError, setAuthError);
    const [loaded, setLoaded] = useState(false);

    useEffect(() => {
        if(requests.length > 0) setLoaded(true);
    }, [requests])
    useEffect(() => { 
       if(authError === false) requestActions(CONTROLLERS.REQUESTS.getRequestsByUser());
    }, []);


    
    const handleDelete = (e) => {
        requestActions(CONTROLLERS.REQUESTS.delete(e));
    }

    const handleUpdate = (e) => {
        console.log(e);
        sessionStorage.movie = JSON.stringify(e);
        history.push('/updateRequest');
    }



    if(loaded===false) return <Loading/>

    return(
      <ToastProvider>
      <Table bordered  hover responsive="md">
          <thead>
              <tr>
                {headers.map((item, index) => {
                   return( <th className="text-center" key={index}>{item}</th> );
                })}
              </tr>
          </thead>
          <tbody>
                {requests.map((item, index) =>{
                    return(
                    <tr>
                        <td>{index + 1}</td>
                        <td>{item.movie.movieTitle}</td>
                        <td>{item.movie.movieReleaseDate}</td>
                        <td>{item.movie.movieProducer}</td>
                        <td>{item.movie.movieDirector}</td>
                        <td>{(item.requestStatus === 1 ? 'Success' : item.requestStatus ===2 ? 'Pending' : 'Denied')}</td>
                        <td className="col-md-3">{item.requestDate}</td>
                        <td><span  onClick={() => handleDelete(item.requestID)}><i  className="fas fa-times"></i></span></td>
                        <td><span  onClick={() => handleUpdate(item.movie)}><i  className="fa fa-pencil-alt"></i></span></td>
                    </tr>);
                })}
          </tbody>
      </Table>
      </ToastProvider>
    );
}

export default UserRequests

【问题讨论】:

    标签: reactjs react-hooks use-effect use-ref


    【解决方案1】:

    useEffect 运行 onmount

    当您使用 customHook useBackenderror === true 并且您在 localStorage 中有令牌时,它将尝试使用 lastFunctionlastParameter 引用,但您尚未启动它们。

     useEffect(() => {
        if(error===true) { //could use if(error) if error is boolean
            if(localStorage.accessToken !== undefined && localStorage.refreshToken !== undefined) {
                    const lastfunc = lastFunction.current;
                    const lastparams = lastParameter.current;
                    if(lastfunc !== undefined && lastparams.length > 0 ) {
                        console.log(lastfunc);
                        console.log(lastparams[0]);
                        lastfunc(lastparams[0], lastparams[1], lastparams[2]);
                    }
            }
        }
    }, [error])
    

    如果您不想让useEffect 运行onmount,您可以使用isMounted 标志,此处已回答:

    Stop useEffect from running on mount

    【讨论】:

    • 非常感谢您的回答,但我真的不明白为什么它们还没有初始化。我知道 useEffects 在挂载时运行,但是这个显式操作只在我的错误为真并且在设置 lastfunction 和 lastparams 后才设置为 true 时运行。
    • 我试过这个解决方案,但还是不行。在 authError 中,ref 属性已正确更改,但是正如您提到的,当调用 useEffect 时,refs 没有初始化,但正如我所提到的,我不知道为什么。在我看来,isMounted 标志仅提供 useEffect 中的代码不会在初始渲染时调用,但我真的不知道它应该如何避免 useEffect 在 refs 初始化并正确设置后使用这些值。可能我只是理解不正确,思维有误,但如果您能更详细地向我解释,我将不胜感激
    • 在您发送请求之前,请验证您的令牌是否有效,如果它已过期,您可以通过刷新令牌来处理它,然后再发送您的请求。你可以做类似这样的事情:stackoverflow.com/questions/44982412/… 那么你就不需要存储你的最后一个请求,在发送请求之前处理令牌。
    猜你喜欢
    • 2020-08-09
    • 2020-07-10
    • 2021-11-26
    • 2021-08-28
    • 2022-10-21
    • 2021-09-10
    • 2023-03-20
    • 1970-01-01
    相关资源
    最近更新 更多