How save the user's id in Redux state from Firebase? - reactjs

I try to store the id of the user from Firebase but it does not work. If Somebody has some advice pls tell me or recommend a better solution.
type here
So Guys I have this slice:
`
import { Toast } from "#chakra-ui/react";
import { async } from "#firebase/util";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import {
getAuth,
createUserWithEmailAndPassword,
updateProfile,
signInWithEmailAndPassword,
onAuthStateChanged,
} from "firebase/auth";
import { setDoc, doc, serverTimestamp } from "firebase/firestore";
import { useSelector } from "react-redux";
import { db, auth } from "../firestore/firebase";
export const signUpWithEmail = createAsyncThunk(
"auth/signUpWithEmail",
async ({ firstName, lastName, email, password }) => {
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
updateProfile(auth.currentUser, { displayName: firstName });
const formData = { firstName, lastName, email };
formData.timestamp = serverTimestamp();
await setDoc(doc(db, "users", user.uid), formData);
}
);
export const signInwithEmail = createAsyncThunk(
"auth/signinWithEmail",
async ({ email, password }) => {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
}
);
export const getUserId = createAsyncThunk("auth/getId", async () => {
onAuthStateChanged(auth, (user) => {
if (user) {
const uid = user.uid;
return uid;
}
});
});
const initialState = {
userId: null,
isloggedIn: false,
status: null,
};
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(signUpWithEmail.fulfilled, (state, action) => {
state.status = "succsess";
})
.addCase(signUpWithEmail.pending, (state) => {
state.status = "loading";
})
.addCase(signUpWithEmail.rejected, (state) => {
state.status = "error";
})
.addCase(signInwithEmail.fulfilled, (state) => {
state.status = "succsess";
state.isloggedIn = true;
})
.addCase(signInwithEmail.rejected, (state) => {
state.status = "error";
})
.addCase(signInwithEmail.pending, (state) => {
state.status = "loading";
})
.addCase(getUserId.fulfilled, (state, action) => {
state.userId = action.payload;
if (action.payload) {
state.isloggedIn = true;
}
});
},
});`
export default authSlice.reducer;
And in the index.js i call the function.
`store.dispatch(getUserId());`
I am expecting that I always can accses to the userId with the useSelector.
thx for a lot.

Related

redux createAsyncThunk dispatching twice with Formik

This thunk is being dispatched on a user event (click log in) on a Formik element.
For some reason it dispatches twice. I have commented out strictMode to exclude that possibility.
In my redux slice:
// userSlice.js
const initialState = {
user: Cookies.get('user') ? JSON.parse(Cookies.get('user')) : null,
};
export const loginUser = createAsyncThunk('users/login', async userInputs => {
try {
const { data } = await axios.post('url', userInputs);
Cookies.set('user', JSON.stringify(data));
return data;
} catch (error) {
return error.response.data;
}
});
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
extraReducers(builder) {
builder.addCase(loginUser.fulfilled, (state, action) => {
state.user = action.payload;
});
},
});
In the component dispatching the thunk:
// loginForm.js
const loginSubmitHandler = async () => {
const data = await dispatch(
loginUser({
email,
password,
})
).unwrap();
if (data.message) {
setError(data.message);
}
else {
setError('');
navigate('/');
}
};
<Formik
enableReinitialize
initialValues={{
email,
password,
}}
validationSchema={loginvalidation}
onSubmit={loginSubmitHandler}></Formik>;
Any ideas?

How to use firebase authentication with Redux Toolkit using onAuthStateChanged?

I'm trying to implement Firebase Authentication via Redux Toolkit. But I think I'm missing something due to lack of knowledge.
My monitorAuthChange returns undefined.
I have two separate files - first list of firebase functions, second Redux Toolkit slice.
import {
createUserWithEmailAndPassword,
onAuthStateChanged,
} from "firebase/auth";
import { auth } from "./firebaseConfig";
export const createAccount = async (email, password) => {
try {
await createUserWithEmailAndPassword(auth, email, password);
} catch (error) {
console.log(error);
}
};
export const monitorAuthChange = () => {
onAuthStateChanged(auth, (user) => {
if (user) {
return true;
} else {
return false;
}
});
};
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { createAccount, monitorAuthChange } from "../../service/userServices";
export const createUser = createAsyncThunk(
"users/createUser",
async ({ username, password }) => {
await createAccount(username, password);
}
);
const initialState = { loginStatus: false };
const userSlice = createSlice({
name: "users",
initialState,
reducers: {},
extraReducers: {
[createUser.fulfilled]: (state, action) => {
const result = monitorAuthChange();
state.loginStatus = result;
},
[createUser.rejected]: (state, action) => {
state.loginStatus = false;
},
},
});
export const selectAllUsers = (state) => state.users;
export default userSlice.reducer;
Two things make me confused:
Thunk works - it creates account and I see it in Firebase. Do I need to track result of request in a different way?
If add console.log(user) inside monitorAuthChange it logs data depends if user was created or not. But still returns undefined.
Would appreciate any hint or advice or article to read to understand my mistake. Thanks in advance.
It seems you want to track user auth with onAuthStateChanged
You have plenty way to plug this callback to redux.
You cannot call monitorAuthChange inside the reducer as they must be pure.
Using global store
// users.slice.ts
const userSlice = createSlice({
name: "users",
initialState,
reducers: {
setLoginStatus: (state, action) {
state.loginStatus = action.payload;
}
},
extraReducers: {
[createUser.fulfilled]: (state, action) => {
state.loginStatus = true;
},
[createUser.rejected]: (state, action) => {
state.loginStatus = false;
},
},
});
// trackUserAuth.ts
onAuthStateChanged(auth, (user) => {
if (user) {
store.dispatch(setLoginStatus(true))
} else {
store.dispatch(setLoginStatus(true))
}
});
Using hooks
export const useAuth = () => {
const dispatch = useDispatch();
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
dispatch(setLoginStatus(true))
} else {
dispatch(setLoginStatus(true))
}
});
return unsubscribe;
}, []);
}
Using thunks
export const checkAuthStatus = () => (dispatch) {
const unsubscribe = Firebase.auth().onAuthStateChanged(user => {
if (user) {
dispatch(setLoginStatus(true))
} else {
dispatch(setLoginStatus(true))
}
});
return unsubscribe;
}

react toastify with redux from axios API

i am trying to send the error messages that sent from my server ( express ) to axios and the error message displays in toastify component but the error message doesn't show up here is the login axios function with the toastify how can i display toastify message inside my page from redux ?
here is my code :
// redux controller
const login = async (username, password) => {
await axios.post("/login",{username,password,},
{ withCredentials: true });};
// reducer page
export function generateError(prop) {
return function (dispatch) {
dispatch({
type: "USER_FAIL"
});
toast.error(prop);
};
}
export function generateSuccess(prop) {
return function (dispatch) {
dispatch({
type: "USER_SUCCESS"
});
toast.success(prop);
};
}
export const login = createAsyncThunk(
"/login",
async ({ username, password }) => {
try {
const data = await authService.login(username, password);
if (data) {
if (data.errors) {
const { username, password } = data.errors;
if (username) generateError(username)
else if (password) generateError(password);
} else {
generateSuccess(data.success);
}
}
return { user: data };
} catch (error) {
console.log(error);
}
}
);
// login page
const handleSubmit = (e) => {
e.preventDefault();
dispatch(login({ username, password }));
}
i am using react-tostify and #redux-toolkit but the message doesn't display inside my page
i fixed it and here is my code :
// auth.js ( redux page )
export const login = createAsyncThunk(
"/login",
async ({ username, password }) => {
try {
const {data} = await axios.post(
"/login",
{
username,
password,
},
{ withCredentials: true }
);
return { user: data };
} catch (error) {
console.log(error);
}
});
const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };
const authSlice = createSlice({
name: "auth",
initialState,
extraReducers: {
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
}})
const { reducer } = authSlice; export default reducer;
Login Page :
const { isLoggedIn } = useSelector((state) => state.auth);
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
dispatch(login({ username, password })).then(data => {
console.log(data)
if (data.payload.user) {
if (data.payload.user.errors) {
const { username, password } = data.payload.user.errors;
if (username) generateError(username)
else if (password) generateError(password);
} else {
generateSuccess(data.success);
navigate("/dashboard");
}
}
})
}
i realized when i back the data it has an object name payload i used it to get the error messages from express and then i put the message in toastify function gettingError and here it is
const generateError = error => {
toast.error(error, {
position: "bottom-right",
})
}
Hai I'm also looking for the same problem while searching I found a solution at with this : react-toastify-with-redux
my Code : authAction.js
import 'react-toastify/dist/ReactToastify.min.css';
import { toast} from 'react-toastify';
export const registerUser = (userData) => dispatch =>{
axios.post('user/register',userData)
.then(res=>toast.success('Your Account Created Successfully 👍'))
.then(res=> window.location = '/authentication/sign-in')
.catch(err=>dispatch(
{
type: GET_ERRORS,
payload: err.response.data
}
),toast.error("Error 😣"))
// .catch((err)=> {return })
};
On your signUp page just add
<ToastContainer />
That's all ...
This answer is probably late. But I came across this problem and decided to do it my way. I know there is toast. promise to handle promises and I don't want to call dispatch.then every time. So I can up with passing dispatch to my action wrapper. Here is my code.
// utils.ts
type ArgumentTypes<F extends CallableFunction> = F extends (
...args: infer A
) => any
? A[0]
: never;
export const withToast = <T = AnyAction | typeof createAsyncThunk>(
action: T,
{ pending, error, success }: ToastPromiseParams<T>
) => {
return (
dispatch: ReturnType<typeof useAppDispatch>,
actionParams?: ArgumentTypes<T & CallableFunction> | void
) => {
const promise = dispatch(
(action as CallableFunction)(actionParams as any)
).unwrap();
toast.promise(promise, {
pending,
error,
success,
});
};
};
// actions.ts
export const login = createAsyncThunk(
"user/login",
async (payload: {
email: string;
password: string;
}): Promise<Partial<LoginAPIResponse>> => {
const { data } = await axios.post(`${API}/${LOGIN_EP}/`, payload);
return data;
}
);
export const loginWithToast = withToast(login, {
pending: "Logging in...",
error: {
render: (error: any) => {
return error?.password || error?.email
? "Invalid email or password"
: "Something went wrong";
},
},
success: "Logged in successfully",
});
// usage in component
const dispatch = useAppDispatch();
loginWithToast(dispatch, {
email: values.email.value,
password: values.password.value,
});
First createAsyncThunk:
import { coreAxios } from "utilities/axios"; // Own customized axios
import { createAsyncThunk } from "#reduxjs/toolkit";
const BASE_URL = process.env.REACT_APP_MAIN_URL
export const GetProducts = createAsyncThunk(
"inventory/GetProducts",
async () => {
const {data} = await coreAxios.get(`${BASE_URL}/api/product/list/`);
return data
}
);
Second createSlice:
import { createSlice } from "#reduxjs/toolkit";
import { GetProducts } from "services/inventory/product.service";
import { toast } from 'react-toastify';
export const productSlice = createSlice({
name: "products",
initialState: {
productsList: [],
productsLoading: false,
productsError: null,
},
extraReducers:
(builder) => {
builder.addCase(GetProducts.pending, (state) => {
toast.loading('Promise is pending...')
state.productsLoading = true
});
builder.addCase(GetProducts.fulfilled, (state, action) => {
toast.dismiss();
toast.success('Promise resolved 👌');
state.productsList = action.payload
state.productsLoading = false
state.productsError = null
});
builder.addCase(GetProducts.rejected, (state, action) => {
toast.dismiss();
toast.error('Promise rejected 🤯 😣')
state.productsLoading = false
state.productsError = action.error?.message
});
},
});
export default productSlice.reducer;
Third page:
import { ToastContainer } from 'react-toastify';
import { useSelector, useDispatch } from "react-redux";
import { GetProducts } from 'services/inventory/product.service';
const Product = () => {
const { productsList, productsLoading, productsError } = useSelector((state) => state.products);
const dispatch = useDispatch();
useEffect(() => {
dispatch(GetProducts());
}, []);
return (
<div className="grid crud-demo">
<h1>Hello Alim</h1>
<ToastContainer />
</div>
);
}

Passing multiple params in Redux Toolkit's createAsyncThunk

I have the following code where I want to send POST request in with a data object and a history function. How can I pass the history param in the action creator? Thanks
login action creator
import { createAsyncThunk } from '#reduxjs/toolkit';
import { client } from '../client';
import {User} from './userLoginSlice';
export const userLogin = createAsyncThunk('user/userLogin' , async (data: User, history: string, thunkAPI) => {
try {
const res = await client.post<User>('/User/Credentials/Login', data);
if(res.status === 200) history.push('/dashboard');
return res.data;
} catch (error) {
return thunkAPI.rejectWithValue('Sorry! Something went wrong ):') ;
}
});
login slice
import { createSlice } from '#reduxjs/toolkit';
import { userLogin } from './userLoginThunk';
export interface User{
email: string;
passowrd: string;
rememberMe: boolean;
}
const initialState = {
user: {} as User,
loading: false,
error: ''
};
export const userLoginSlice = createSlice({
name: 'user',
initialState: initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(userLogin.pending, (state) => {
state.loading = true;
state.error = '';
});
builder.addCase(userLogin.fulfilled, (state, { payload }) => {
state.loading = false;
state.user = payload;
});
builder.addCase(userLogin.rejected, (state, { payload }) => {
state.loading = false;
state.error = String(payload);
});
}
});
export const userLoginReducer = userLoginSlice.reducer;
Using the action creator in a component
import { Form, Input, Checkbox } from 'antd';
import { useHistory } from 'react-router-dom';
import { ConfirmBtn } from '../small-components/ActionBtns';
import { useAppDispatch } from '../../custom-hooks/reduxCustomHooks';
import {userLogin } from 'src/redux/user-login/userLoginThunk';
import {User} from '../../redux/user-login/userLoginSlice';
import '../../sass/light-theme/user-login.scss';
export const UserLogin = () => {
const dispatch = useAppDispatch();
const history = useHistory();
const onFinish = (values: User) => {
dispatch(userLogin(values, history));
};
return (
<Form></Form>
);
};
It's throwing the error which says that the userLogin expects 1 arguments but got 2. What am I doing wrong?
A thunk only accepts one parameter, so you would have to provide an object containing both your values and the history object.
In your case, you could also use the the unwrap() function (https://stackoverflow.com/a/67876542/3170628) so you don't have to provide the history object to your thunk:
const onFinish = async (values: User) => {
try {
await dispatch(userLogin(values)).unwrap();
history.push('/dashboard');
} catch (error) {
...
}
};

React-redux- Fetching data from API

I am working on a project and I need to fetch data from backend or from an API. I tried fetch the data but nothing appears. I think I am doing something wrong in the container. I am a beginner in react-redux, I don't know what I am doing wrong.
I've already read all the posts but nothing seems to works.
my reducer:
const initialState={
articles: [],
};
const rootReducer = (state = initialState, action) => {
const { type, payload }=action;
switch(type) {
case SRETRIEVE_ARTICLE:{
return {
...state,
articles:payload,
};
}
default: return state;
}
}
export default rootReducer;
This is what I have right now in container:
import Articles from 'components/Articles';
import { fetchArticles } from '../../pages/index';
const mapStateToProps = (state) => ({
articles:state.articles
})
const ConnectedArticles = connect(
mapStateToProps,
{fetchArticles}
)(Articles)
export default ConnectedArticles;
pages.js
axios.get('API').then((response) => {
const { data } = response;
dispatch({ type: RETRIEVE_ARTICLES, payload: data });
});
};
const Index = () => {
const articles= useSelector((state) => state.articles);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchArticles);
}, []);
return <>{articles && articles.map((article) => <Article key={article.id} name={article.name} />)}</>;
};
Index.getInitialProps = async () => ({
authRequired: true,
label: 'Dashboard',
});
export default Index;
Also I defined the action type: export const SET_UNOPENED_REWARD = 'SET_UNOPENED_REWARD';
and action const unopenedRewards = (payload) => ({ type: SET_UNOPENED_REWARD, payload });
One very nice way to do data fetching with redux is to use redux toolkit's createAsyncThunk and createSlice functions.
// src/features/articles/articlesSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const fetchArticles = createAsyncThunk("articles/get", async () => {
// Here you can use axios with your own api
const response = await fetch("https://rickandmortyapi.com/api/character");
const json = await response.json();
return json.results;
});
export const slice = createSlice({
name: "articles",
initialState: {
loading: false,
data: []
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchArticles.pending, (state) => {
state.loading = true;
});
builder.addCase(fetchArticles.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = false;
});
builder.addCase(fetchArticles.rejected, (state) => {
state.loading = false;
});
}
});
export default slice.reducer;
// src/features/articles/Articles.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchArticles } from "./articlesSlice";
export const Articles = () => {
const articles = useSelector((state) => state.articles.data);
const loading = useSelector((state) => state.articles.loading);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchArticles());
}, []);
return (
<>
{loading && "...loading"}
{articles.map((article) => <Article key={article.id} {...article} />)}
</>
);
};
you should use async and await
let response = await axios.get('https://run.mocky.io/v3/5c045896-3d18-4c71-a4e5-5ed32fbbe2de')
if(response.status==200){
dispatch({ type: RETRIEVE_ARTICLES, payload: data });
}

Resources