I'm trying to implement Google Authentication using GAPI and I want to store the userId of the user fetched by GAPI into the redux store. I don't know how to pass class component variables into a redux store or action dispatcher. Can someone please help me out to get out of this?
import { SIGN_IN, SIGN_OUT } from './types'
export const signIn = (dispatch, userId) => {
console.log('actions',userId)
dispatch(
{
type: SIGN_IN,
payload:userId
}
)
}
export const signOut = (dispatch) => {
dispatch(
{
type: SIGN_OUT
}
)
}
const mapStateToProps = (state,ownProps) => {
return {
isSignedIn: state.auth.isSignedIn
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
SignIn: () => signIn(dispatch),
SignOut: () => signOut(dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(GoogleAuth);
SignIn: () => signIn(dispatch,ownProps.toString()),
I think it's should be
SignIn: (value) => signIn(dispatch, value),
You can pass value when you actually dispatch an action. once you have the user id you do
this.props.SignIn(userId)
this will fire action dispatch where you have to pass value like
const mapDispatchToProps = (dispatch, ownProps) => {
return {
SignIn: (id) => signIn(dispatch,id),
SignOut: () => signOut(dispatch)
}
}
export const signIn = (userId) => {
return {
type: SIGN_IN,
payload: userId
};
};
export const signOut = () => {
return {
type: SIGN_OUT
};
};
For your GoogleAuth Component:
componentDidMount() {
window.gapi.load('client:auth2', () => {
window.gapi.client.init({
clientId: 'YOUR_CLIENT_ID',
scope: 'email'
}).then(() => {
this.auth = window.gapi.auth2.getAuthInstance(); //Assign the auth instance
this.onAuthChange(this.auth.isSignedIn.get()); //Update the auth state in store
this.auth.isSignedIn.listen(this.onAuthChange); //Wait for the auth state to change in future
});
});
}
onAuthChange = (isSignedIn) => {
if (isSignedIn) {
this.props.signIn(this.auth.currentUser.get().getId());
} else {
this.props.signOut();
}
};
const mapsStateToProps = (state) => {
return { isSignedIn: state.auth.isSignedIn };
}
export default connect(mapsStateToProps, { signIn, signOut })(GoogleAuth);
Related
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;
}
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>
);
}
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 });
}
In this my action the user's log out of my application is exported in react
export const logoutUser = () => {
return dispatch => {
dispatch(
{
type: LOGOUT_USER
}
)
.then(() => {
logoutUserSuccess(dispatch, )
})
.catch((err) => {
logoutUserError(err, dispatch)
})
}
}
const logoutUserSuccess = (dispatch) => {
dispatch(
{
type: LOGOUT_USER_SUCCESS
}
)
AsyncStorage.removeItem('#token_jwt')
console.log('saiu')
Actions.loginScreen()
}
const logoutUserError = (err, dispatch) => {
dispatch(
{
type: LOGOUT_USER_ERROR
}
)
Alert.alert('Erro ao sair da conta')
console.log(err)
}
is my Reducer
case LOGOUT_USER:
return {
...state
}
case LOGOUT_USER_SUCCESS:
return {
INITIAL_STATE
}
case LOGOUT_USER_ERROR:
return {
...state
}
is my screen to logout
onLogout() {
this.props.logoutUser()
}
const mapStateToProps = state => (
{
email: state.Authentication.email
}
)
export default connect(mapStateToProps, { logoutUser })(Home)
The return is the following error
I put the email on the mapStateToProps, because I don't know how to leave it blank, what matters to me is the logout
You can try creating a mapDispatchToProps function and dispatch the action logoutUser from inside the function and pass it as a second argument to connect.
In doing so, you can invoke the LogoutUser from mapDispatchToProps in your onLogout function.
import {logoutUser} from './youractions.js'
onLogout() {
this.props.LogoutUser();
}
const mapDispatchToProps = (dispatch) => ({
LogoutUser: () => dispatch(logoutUser()),
});
export default connect(null, mapDispatchToProps)(Home);
After changing one of my components, (Profile.js) from a class to a function to simplify and have cleaner code, the onClick triggering of a redux action (like) no longer does anything.
Some have pointed out the action needs to be map differently, but I'm not sure why as I'm still new to redux and it's confusing as to why it works fine as a class but not as a function.
What adds to the confusion is that I'm also using react thunk to make things async.
User.js
import { fetchUser, like } from '../../actions/userActions';
class User extends React.Component {
componentDidMount() {
const { username } = this.props.match.params;
this.props.fetchUser(username);
}
render() {
const { like, user } = this.props;
return (
<Profile user={user} like={like} />
)
}
}
const mapStateToProps = state => ({
user: state.store.user
});
export default connect(mapStateToProps, {fetchUser, like})(User);
Profile.js Before
import { like, user } from '../../actions/userActions';
class Profile extends React.Component {
const { like, user } = this.props
return (
<a onClick={() => like(user.username)}>Like</a>
)
}
export default connect (mapStateToProps, {like}){Profile)
Profile.js After
const Profile = (props) => {
const { like, user } = props
return (
<a onClick={() => like(user.username)}>Like</a>
)
}
actions.js
const url = 'http://localhost:3001'
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const like = (username) => dispatch => {
fetch(`${url}/like/${username}`, {credentials: 'include', method: 'post'})
.then(handleErrors)
.then(res => res.json())
.then(res =>
dispatch({
type: LIKE,
payload: res
})
)
.catch(error => console.error('Error:', error))
}
export const fetchUser = (username) => dispatch => {
fetch(`${url}/${username}`, {credentials: 'include'})
.then(handleErrors)
.then(res => res.json())
.then(res =>
dispatch({
type: FETCH_USER,
payload: res
})
)
.catch(error => console.error('Error:', error))
}
reducers.js
export default function(state = initialState, action) {
switch (action.type) {
case FETCH_USER:
return {
...state,
user: action.payload.user
};
case LIKE:
return {
...state,
user: {
...state.user,
meta: {
...state.user.meta,
like: action.payload.like
}
}
};
store.js
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
console.log of like in Profile.js
const Profile = (props) => {
const { user, like } = props
console.log(like)
ƒ like(username) {
return function (dispatch) {
fetch(url + '/like/' + username, { credentials: 'include', method: 'post' }).then(handleErrors).then(function (res) {
return res.json();
…
If I were to create a normal function such as
const test = () => { console.log('test') }
and change the onClick={} in Profile.js to use that, it works fine.
You should create handler in the User component, call your action creator there and pass it as a callback to child Profile component.
So, your code will looks like:
import { like } from '../../actions/userActions';
class User extends React.Component {
...
onClickHandler = username => {
return () => {
this.props.like(username);
}
}
render() {
const { user } = this.props;
return <Profile user={user} onClickHandler={this.onClickHandler} />
}
}
const mapStateToProps = state => ({
user: state.store.user
});
export default connect(mapStateToProps, {fetchUser, like})(User);
Then, call onClickHandler in your Profile component:
const Profile = props => {
const { onClickHandler, user } = props;
return (
<button onClick={onClickHandler(user.username)}>Like</button>
)
}
Hope it will helps.