reactjs - 在react组件/redux工具包之外的函数中使用useDispatch

标签 reactjs redux rtk

我需要帮助来解决此错误:

"useDispatch is called in function that is neither a React function component nor a custom React Hook function".

说明:

store.jsuserSlice.js 保存我的 Redux 相关事物 (rtk) 的定义。

Auth.js 旨在保存身份验证/注销功能并保持 redux“用户”存储更新。到目前为止,我只有 google 身份验证,当我调用redirectToGoogleSSO 时,该身份验证已通过身份验证。

身份验证部分工作完美,我正在正确检索用户信息,但我很难使其更新用户存储。 dispatch(fetchAuthUser()) 是我收到错误的地方。

Sidebar.js 是一个导航侧边栏,其中包含用于登录/注销和访问 profile.js 的菜单(尚未实现)。 如果我将 Auth 中的所有代码放入侧边栏组件中,身份验证工作和 redux 存储都会被填充,但我想将内容保留在 Auth.js 中,这样我就可以在其他组件中使用它,而不仅仅是在侧边栏中.


//store.js:

import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';

export default configureStore({
    reducer: {
        user: userReducer
    }
});

//userSlice.js

import { createSlice } from '@reduxjs/toolkit';
import axios from "axios";

export const userSlice = createSlice({
  name: 'user',
  initialState: { 
    email: 'teste@123',
    name: 'teste name',
    picture: 'teste pic',
    isAuthenticated: false
  },
  reducers: {
    setUser (state, actions) {
      return {...state,     
          email: actions.payload.email,      
          name: actions.payload.name,
          picture: actions.payload.picture,
          isAuthenticated: true
         }
    },
    removeUser (state) {
      return {...state, email: '', name: '', picture: '', isAuthenticated: false}
    }
  }
});

export function fetchAuthUser() {  

  return async dispatch => {

    const response = await axios.get("/api/auth/user", {withCredentials: true}).catch((err) => {
      console.log("Not properly authenticated");
      dispatch(removeUser());
    });

    if (response && response.data) {
      console.log("User: ", response.data);
      dispatch(setUser(response.data));
    }
  }
};

export const { setUser, removeUser } = userSlice.actions;

export const selectUser = state => state.user;

export default userSlice.reducer;

//Auth.js

import React, {  useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { fetchAuthUser } from '../../redux/userSlice';

export const AuthSuccess = () => {
    useEffect(() => {
        setTimeout(() => {
            window.close();
        },1000);
    });

    return <div>Thanks for loggin in!</div>
}

export const AuthFailure = () => {
    useEffect(() => {
        setTimeout(() => {
            window.close();
        },1000);
    });

    return <div>Failed to log in. Try again later.</div>
}

export const redirectToGoogleSSO = async() => { 
    const dispatch = useDispatch(); 
    let timer = null;
    const googleAuthURL = "http://localhost:5000/api/auth/google";
    const newWindow = window.open(
        googleAuthURL,
        "_blank",
        "toolbar=yes,scrollbars=yes,resizable=yes,top=200,left=500,width=400,height=600"
    );

    if (newWindow) {
        timer = setInterval(() => {
            if(newWindow.closed) {
                console.log("You're authenticated"); 
                dispatch(fetchAuthUser()); //<----- ERROR HERE ---->
                if (timer) clearInterval(timer);
            }
        }, 500);
    }
}

//侧边栏.js

import React from 'react';
import { Link } from 'react-router-dom';
import { redirectToGoogleSSO } from '../auth/Auth';
import { useSelector } from 'react-redux';

export const Sidebar = () => { 

    const handleSignIn = async() => { 
        redirectToGoogleSSO();
    };

    const {name,picture, isAuthenticated} = useSelector(state => state.user);  

    return (  
        <div id="sidenav" className="sidenav">
            <div className="nav-menu">
                <ul> 
                    {
                        isAuthenticated  
                        ? <li>
                            <img className="avatar" alt="" src={picture} height="40" width="40"></img>                        
                            <Link to="/" className="user">{name}</Link> 
                            <ul>
                                <li><Link to="/"><i className="pw-icon-export"/> logout</Link></li>
                            </ul>
                        </li>

                        : <li>
                            <Link to="/" className="login" onClick={handleSignIn}>                         
                                <i className="pw-icon-gplus"/>&nbsp;&nbsp;
                                Sign In / Sign Up 
                            </Link> 
                        </li> 
                    } 
                </ul>
            </div>
        </div> 
      )
}

最佳答案

您只能从 React 组件或自定义 Hook 使用 useDispatch Hook ,在您的情况下,您应该使用 store.dispatch(),尝试执行以下操作:

import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';

// following the docs, they assign configureStore to a const
const store = configureStore({
    reducer: {
        user: userReducer
    }
});
export default store;

编辑:我还注意到您正在尝试分派(dispatch)一个不是操作的函数,redux 不会那样工作,您应该只分派(dispatch)您在 reducer 中定义的操作,否则你的状态会不一致。

因此,首先,将 fetchAuthUser 移动到另一个文件,例如 apiCalls.ts 或其他任何文件,这只是为了避免从 store.js 循环导入。

此后,在 fetchAuthUser 上调用 store.dispatch:

// File with the fetch function
// Don't forget to change the path
import store from 'path/to/store.js'
export function fetchAuthUser() {

    const response = await axios.get("/api/auth/user", {withCredentials: true}).catch((err) => {
      console.log("Not properly authenticated");
      store.dispatch(removeUser());
    });

    if (response && response.data) {
      console.log("User: ", response.data);
      store.dispatch(setUser(response.data));
    }

};

在 Auth.js 中,您不必调用调度,因为您已经在函数中调用了它。

export const redirectToGoogleSSO = async() => { 
    let timer = null;
    const googleAuthURL = "http://localhost:5000/api/auth/google";
    const newWindow = window.open(
        googleAuthURL,
        "_blank",
        "toolbar=yes,scrollbars=yes,resizable=yes,top=200,left=500,width=400,height=600"
    );

    if (newWindow) {
        timer = setInterval(() => {
            if(newWindow.closed) {
                console.log("You're authenticated");

                // Just call the fetchAuthUser, you are already dispatching the state inside this function
                await fetchAuthUser();
                if (timer) clearInterval(timer);
            }
        }, 500);
    }
}

因此请记住,无论您需要在 React 组件或自定义钩子(Hook)之外使用分派(dispatch),您必须使用 store.dispatch,否则它将无法工作,并且不要忘记仅调度操作以保持状态一致。我建议您阅读core concepts关于 redux,还有 see this video更好地了解它的幕后工作原理。希望我能帮上一点忙!

关于reactjs - 在react组件/redux工具包之外的函数中使用useDispatch,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71835965/

相关文章:

javascript - 堆栈导航器给我未定义的错误

reactjs - React router v4 不支持 Redux

java - 有没有办法让 Android 设备充当 NTRIP 客户端?

reactjs - 如何使用 Material UI 在应用栏右侧设置图标

javascript - 如何将一对数组的数组显示为键值对?

reactjs - 通过react-router-4导航后未获取路由参数

javascript - 无法让 redux 工具包与异步一起使用(createAsyncThunk)

javascript - react 测试渲染器 : Invariant Violation: getNodeFromInstance: Invalid argument

javascript - 我检查 Prop 的方式有什么不同吗?