React Redux Toolkit Doesn't Dispatch Async Thunk - javascript

I am using Redux/Toolkit and I want to use the Async Thunk for authentication processes. But it returns an error when I'm trying to dispatch the function.
What should I do in this case? It's the first time for me to use Async Thunk, so I don't have an idea how to face with this problem.
By the way I am using Typescript. So, I think this problem mostly about Typescript.
userSlice.tsx file:
import {createSlice, createAsyncThunk} from "#reduxjs/toolkit"
import {InterfaceUserSlice} from "../typescript/interfaceUserSlice"
import axios from "../axios"
export const UserLogin = createAsyncThunk("/users/authentication", async (user:{email:string,password:string}) => {
try{
const res = await axios.post("/users/authentication", user)
...
} catch(err){
...
}
})
const initialState:InterfaceUserSlice = {
...
}
const userSlice = createSlice({
name: "user",
initialState,
reducers: {},
extraReducers: (builder) => {}
})
export default userSlice.reducer
Login.tsx page file:
import React, {useState} from "react"
import { useDispatch } from "react-redux"
import { UserLogin } from "../redux/userSlice"
const Login = () => {
const dispatch = useDispatch()
const [email, setEmail] = useState<string>("")
const [password, setPassword] = useState<string>("")
function LoginRequest(){
dispatch(UserLogin({email,password})) //This is the point that I have the error which says: "Argument of type 'AsyncThunkAction<void, { email: string; password: string; }, AsyncThunkConfig>' is not assignable to parameter of type 'AnyAction'."
}
return (
...
)
}
export default Login

If you use TypeScript you always should set return type and arguments for your asyncThunk in genric
export const UserLogin = createAsyncThunk<return type, arguments>("/users/authentication", async (user) => {
try{
const res = await axios.post("/users/authentication", user)
...
} catch(err){
...
}
})
And also you should create custom hook useDispatch and useSelector
import { useSelector, useDispatch, TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "../redux/store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
And main reducer file should have such look:
import { configureStore } from "#reduxjs/toolkit";
import userleSlice from "./slice/userSlice";
export const store = configureStore({
reducer: {
user: userSlice,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Try passing the type of the args to createAsyncThunk:
type ReturnedType = any // The type of the return of the thunk
type ThunkArg = { email:string, password:string }
export const UserLogin = createAsyncThunk<ReturnedType, ThunkArg>("/users/authentication", async (user) => {
try{
const res = await axios.post("/users/authentication", user)
...
} catch(err){
...
}
})

Related

Retrieved data from firebase v9 is undefined

When retrieving data and console.log it, the data shows perfectly, but when trying to dispatch the action with the argument as a data it turns out to be undefined.
I tried to use await before dispatch the action, but it didn't change anything. Why does it happen?
actions.js
import * as types from './actionTypes'
import { db } from '../firebase';
import { collection, getDocs } from "firebase/firestore";
const getFeedbacksStart = () => ({
type: types.GET_FEEDBACKS_START,
});
const getFeedbacksSussess = (feedbacks) => ({
type: types.GET_FEEDBACKS_SUCCESS,
payload: feedbacks
});
const getFeedbacksFail = () => ({
type: types.GET_FEEDBACKS_FAIL,
});
export const getFeedbacks = () => {
return async function (dispatch) {
dispatch(getFeedbacksStart());
try {
const querySnapshot = await getDocs(collection(db, "feedbacks"));
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data())
});
const feedbacks = querySnapshot.forEach((doc) => doc.data());
dispatch(getFeedbacksSussess(feedbacks))
} catch (error) {
dispatch(getFeedbacksFail(error))
}
}
}
actionTypes.js
export const GET_FEEDBACKS_START = 'GET_FEEDBACKS_START';
export const GET_FEEDBACKS_SUCCESS = 'GET_FEEDBACKS_SUCCESS';
export const GET_FEEDBACKS_FAIL = 'GET_FEEDBACKS_FAIL';
reducer.js
import * as types from './actionTypes'
const initialState = {
feedbacks: {},
loading: false,
error: null,
};
const feedbackReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_FEEDBACKS_START:
return {
...state,
loading: true
}
case types.GET_FEEDBACKS_SUCCESS:
return {
...state,
loading: false,
feedbacks: action.payload,
}
case types.GET_FEEDBACKS_FAIL:
return {
...state,
loading: false,
error: action.payload,
}
default:
return state;
}
}
export default feedbackReducer;
root-reducer.js
import { combineReducers } from "redux";
import feedbackReducer from "./reducer";
const rootReducer = combineReducers({
data: feedbackReducer,
});
export default rootReducer;
store.js
import { configureStore } from '#reduxjs/toolkit';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './root-reducer';
const store = configureStore({
reducer: rootReducer,
middleware: [thunk, logger],
});
export default store;
ListRecord.js where I dispatch the action
import React, { useEffect, useState, useContext } from "react";
import { useSelector, useDispatch } from 'react-redux';
import { getFeedbacks } from "../redux/actions";
const ListRecord = () => {
const [data, setData] = useState({});
console.log("data", data);
const state = useSelector(state => state.data);
console.log("state =>", state);
let dispatch = useDispatch();
useEffect(() => {
dispatch(getFeedbacks());
}, [])
return (
<>
</>
);
};
export default ListRecord;
I figured out what I was doing wrong. I tried to retrieve the data in the wrong way. I was trying to use forEach method on a collection. Firstly, it needed to refer to the docs inside a db -> querySnapshot.docs and then you can use .map() method and loop through the whole collection you have inside your database.
The example of how to do it right with firebase v9 is HERE
Here is a working code :)
In actions.js
export const getFeedbacks = () => {
return function (dispatch) {
dispatch(getFeedbacksStart())
const getData = async () => {
try {
const querySnapshot = await getDocs(collection(db, "feedbacks"));
const feedbacks = querySnapshot.docs.map((doc) => ({
...doc.data(),
id: doc.id
}))
dispatch(getFeedbacksSussess(feedbacks));
} catch (error) {
dispatch(getFeedbacksFail(error))
}
}
getData();
}
}

Redux typescript exporting state of reducer in array

So I'm having a problem wherein my reducer is undefined and I don't freaking understand what it means. It's working when it is not array but when array it is not working.
My code should be like this.
In my groupSlice.ts it is something like this
export interface GroupState {
grouplist: (string)[];
}
const groupInitialState: GroupState = {
grouplist:['Binary','Jhonny Sins']
}
export const createGroup = createSlice({
name: 'group',
initialState:groupInitialState,
reducers:{
addGroup: (state,action) => {
state.grouplist.push(action.payload)
},
subGroup: ( state,action ) => {
state.grouplist.filter((group) => group != action.payload)
}
}
})
...
And then I store it here at my store.ts
import { configureStore, ThunkAction, Action } from '#reduxjs/toolkit';
import { createGroup } from '../features/counter/groupSlice';
export const store = configureStore({
reducer: {
groupings: createGroup.reducer,
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
And then the hook.ts to pass on my state in reducer.
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
but when I pass it here in my Group.tsx it says my grouplist is undefined even I already input a an array at string[]
import React,{ useState,useEffect } from 'react'
import styles from './Group.module.css'
import {
addGroup,
subGroup
} from './groupSlice';
import { useAppSelector, useAppDispatch } from '../../app/hooks';
import { RootState } from '../../app/store';
// HERE IS MY ERROR THAT DOESN"T WORK>
export const selectCount = (state: RootState) => {
console.log(state.groupings?.grouplist)
const groupList = state.groupings?.grouplist
return groupList;
}
const Grouping = () => {
const groups = useAppSelector(selectCount);
const dispatch = useAppDispatch();
const [groupName, setGroupName] = useState('');
return (
...
)
}
export default Grouping
So I'm trying to say that my grouplist is undefined but I don't know why since I already input a list values there. Can anyone spotted the mistake here? Thanks.
In the subGroup reducer:
state.grouplist.filter(...)
This only returns the filtered array, it doesn't change it in place (unlike push), so you need to reassign it:
state.grouplist = state.grouplist.filter(...)

React and Typescript : Error creating a context with jwt info

I'm trying to create a context using Typescript after login with the info I got from the backend. It works fine in Javascript without Typescript, but I can't make it work with Typescript.
Login.tsx:
const { setAuth } = useAuth();
setAuth.({ user, pwd, role, accessToken });
AuthProvider.tsx:
import { createContext, useState } from "react";
import { IUserData, AuthContextType } from "../interfaces/interfaces";
const AuthContext = createContext<AuthContextType>({});
interface props {
children: JSX.Element | JSX.Element[]
}
export const AuthProvider = ({ children }: props) => {
const [auth, setAuth] = useState<IUserData>({});
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
useAuth.tsx:
import { useContext, useDebugValue } from "react";
//import AuthContext from "../context/AuthProvider";
import { IUserData } from "../interfaces/interfaces";
import { createContext, useState } from "react";
const AuthContext = createContext<IUserData>({});
// global auth state to use throughout the application globally
const useAuth = () => {
const auth = useContext(AuthContext);
useDebugValue(auth, auth => auth?.user ? "Logged In" : "Logged Out")
return useContext(AuthContext);
}
export default useAuth;
interfaces.tsx:
import { Dispatch, SetStateAction } from "react";
export interface IUserData {
user?: string
pwd?: string
role?: number
accessToken?: string
auth?: {user?: string
pwd?: string
role?: number
accessToken?: string}
setAuth?: Dispatch<SetStateAction<IUserData>>
}
export type AuthContextType = {
auth?: IUserData;
setAuth?: (auth: IUserData) => void;
//updateTodo: (id: number) => void;
};
On another page after login I try to display the information:
const { auth } = useAuth();
console.log(auth);
console.log(auth?.role);
But I get "undefined"...
I think It's my interfaces file and AuthProvider.tsx that aren't correctly code. Do you know how to change it correctly?

Function is not getting called anymore, when trying to dispatch a type

I am currently trying to access my data using the Spotify API. This works very well. Thats the function I am using to get my Data. I assume the other stuff is not important. I can post that, if you need that.
export const getSpotifyUser = (access_token:string) =>{
setAuthorizationHeader(access_token)
axios.get('https://api.spotify.com/v1/me').then((res) => {
console.log(res.data)
})
}
I have set up a redux store and trying to put the credentials into the store, by dispatching the right type (SET_USER).
export const getSpotifyUser = (access_token:string) => (dispatch: any) => {
console.log("function is not called") // Function is not even called why ?
setAuthorizationHeader(access_token)
axios.get('https://api.spotify.com/v1/me').then((res) => {
console.log(res.data)
dispatch ({
type: SET_USER,
payload: res.data
})
}
but as soon as I use dispatch, the function is no longer called.
I really do not see my mistake. Is that a typescript error ?. ( I am using react typescript)
store.js
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './rootReducer'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
)
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
export default store
rootReducer.ts
import { combineReducers } from 'redux'
import userReducer from './User/userReducer'
const rootReducer = combineReducers({
user: userReducer,
})
export default rootReducer
userReducer.ts
import { AnyAction } from 'redux'
import { SET_USER } from './userTypes'
interface Credentials {
username: string
email: string
profilepicture: string
id: number
}
interface InitialState {
authenticated: boolean
loadding: boolean
credentials?: Credentials
}
const initialState: InitialState = {
authenticated: false,
loadding: false,
credentials: {} as Credentials,
}
const reducer = (state = initialState, action: AnyAction) => {
switch (action.type) {
case SET_USER: {
return {
...state,
loading: false,
credentials: action.payload,
}
}
default:
return state
}
}
export default reducer
Login.tsx ( I am making the login here. It is working fine, if am not using dispatch
import { IonButton } from '#ionic/react'
import React, { useEffect } from 'react'
import {
getAuthorizeHref,
getHashParams,
removeHashParamsFromUrl,
getSpotifyUser,
} from '../../Helpers/login'
const Login: React.FC = () => {
// const user = useSelector((state: RootState) => state.user.credentials)
useEffect(() => {
const hashParams = getHashParams()
const access_token = hashParams.access_token
// const expires_in = hashParams.expires_in
removeHashParamsFromUrl()
getSpotifyUser(access_token)
}, [])
return (
<IonButton onClick={() => window.open(getAuthorizeHref(), '_self')}>
)}
export default Login
since you're using typescript with react, I believe you have added the getSpotifyUser function to your interface, now if you want to access that i think you should call it like this
props.getSpotifyUser(access_token)
and finally add it to your connect as a dispatch function that's wrapping your component
your login component should be like this one
import { IonButton } from '#ionic/react'
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import {
getAuthorizeHref,
getHashParams,
removeHashParamsFromUrl,
getSpotifyUser,
} from '../../Helpers/login'
interface ILogin {
getAuthorizeHref: () => any;
getHashParams: () => any;
removeHashParamsFromUrl: () => any;
getSpotifyUser: (access_token) => any;
}
const Login: React.FC = (props: ILogin) => {
// const user = useSelector((state: RootState) => state.user.credentials)
useEffect(() => {
const hashParams = props.getHashParams()
const access_token = hashParams.access_token
// const expires_in = hashParams.expires_in
props.removeHashParamsFromUrl()
props.getSpotifyUser(access_token)
}, [])
return (
<IonButton onClick={() => window.open(props.getAuthorizeHref(), '_self')}>
)}
export default connect(null, {getAuthorizeHref, getHashParams, removeHashParamsFromUrl, getSpotifyUser})(Login)
Basicly Shamim has given the right answer.Any function that uses that dispatch is a redux action, and you have to follow the docs specifically to call that function. You have to use connect to dispatch actions. As an alternative you can use the dispatchHook. If am wrong please please correct me !!!!
Thats the right code I just had to correct Login.tsx
import { IonApp, IonButton } from '#ionic/react'
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import {
getAuthorizeHref,
getHashParams,
removeHashParamsFromUrl,
getSpotifyUser,
} from '../../Helpers/login'
const style = {
Logo: {
display: 'flex',
justifyContent: 'space-evenly',
color: 'white',
position: 'relative',
top: '70%',
} as const,
}
const Login: React.FC = (props: any) => {
// const user = useSelector((state: RootState) => state.user.credentials)
useEffect(() => {
const hashParams = getHashParams()
const access_token = hashParams.access_token
// const expires_in = hashParams.expires_in
removeHashParamsFromUrl()
console.log('halloeuseeffect')
props.getSpotifyUser(access_token)
console.log('halloeuseeffect')
}, [])
return (
<IonApp>
<IonButton onClick={() => window.open(getAuthorizeHref(), '_self')}>
knsnan
</IonApp>
)
}
export default connect(null, {
getSpotifyUser,
})(Login)

Redux state does not update even when reducer called with correct data

For some reason, even though the reducer runs and console.log shows that the correct data was passed to it, the redux store was not updated.
Relevant files:
App.jsx
import {Provider} from 'react-redux';
import store from './store';
const Stack = createStackNavigator();
export default class App extends Component {
render() {
return (
<Provider store={store()}>
Store.js
import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
const store = (initialState = {}) =>{
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
)
}
export default store;
Register.tsx
...
<Pressable
style={styles.button}
onPress={() => this.props.submitRegistration(this.state)}
>
...
const mapDispatchToProps = (dispatch: any) => {
return {
submitRegistration: (data: any) => {
dispatch(UserActions.submitRegister(data))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Register);
UserActions
import { signUp } from '../../services/backend';
import { setUser } from '../../actions';
export function submitRegister(data: any) {
return async (dispatch: any) => {
const response = await signUp(data);
const responseData = await response.json();
if(responseData.token) {
console.log('here', responseData);
dispatch(setUser(responseData.user));
}
};
}
Action creator
export const setUser = (user: any) => ({
type: 'SET_USER',
user
});
User Reducer
import { SET_USER } from "../actions/actionTypes"
const initialState = {
user: {}
}
const User = (state = initialState, action: any) => {
switch(action.type) {
case SET_USER:
console.log('here action', action.user);
return { user: action.user}
default:
return state
}
}
export default User;
I would really appreciate any help possible. Seems like I misconfigured in someway because even when I set initial state :
const initialState = {
user: {firstName: "John"}
}
it's not reflected in the redux store.
In your action creator:
export const setUser = (user: any) => (
return {
type: 'SET_USER',
user
});

Categories