User Auth with React Context API - javascript

I'm using React, Axios and Mongoose. Trying to store a user state but am having trouble with the stored state.user object.
When I manually enter values for state.user, the app works properly, however when I actually login from the site, the user object is stored in localStorage but is not being read properly by the app. I noticed I had to remove new ObjectId from the object and also convert the createdAt and lastUpdated dates into strings in order for my static values to work. How can I get around this? Thanks!
Screenshot of localStorage object
context.js
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
isFetching: false,
error: false,
};
export const AuthContext = createContext(INITIAL_STATE);
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
JSON.stringify(localStorage.setItem("user", state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
isFetching: state.isFetching,
error: state.error,
dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
reducer.js
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
isFetching: true,
error: false,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
isFetching: false,
error: false,
};
case "LOGIN_FAILURE":
return {
user: null,
isFetching: false,
error: true,
};
case "FOLLOW":
return {
...state,
user: {
...state.user,
following: [...state.user.following, action.payload],
},
};
case "UNFOLLOW":
return {
...state,
user: {
...state.user,
following: state.user.following.filter(
(following) => following !== action.payload
),
},
};
default:
return state;
}
};
export default AuthReducer;
actions.js
export const LoginStart = (userCredentials) => ({
type: "LOGIN_START",
});
export const LoginSuccess = (user) => ({
type: "LOGIN_SUCCESS",
payload: user,
});
export const LoginFailure = (error) => ({
type: "LOGIN_FAILURE",
payload: error,
});
export const Follow = (userId) => ({
type: "FOLLOW",
payload: userId,
});
export const Unfollow = (userId) => ({
type: "UNFOLLOW",
payload: userId,
});
utils/api.js
import axios from "axios";
export const loginCall = async (userCredentials, dispatch) => {
dispatch({ type: "LOGIN_START" });
try {
const response = await axios.post("/api/auth/login", userCredentials);
dispatch({ type: "LOGIN_SUCCESS", payload: response.data });
} catch (error) {
dispatch({ type: "LOGIN_FAILURE", payload: error });
}
};

Related

why the profile inside the state return null instead of empty object after update?

I have a problem with my redux reducer.It doesn't return the expected state after dispatching the getCurrentProfile action, it returns the initial state which is "null" instead of "{}", which is fetched with an ajax request, so when the network return the result the state profile change to the result returned but when it is an error returned it stay null instead of empty object, so that is my code:
enter image description here
profileAcction.js :
import axios from 'axios';
import { GET_PROFILE, PROFILE_LOADING, CLEAR_CURRENT_PROFILE} from './types';
//Loading profile
const setProfileLoading = () => {
return {
type: PROFILE_LOADING
}
};
// Clear current profile
export const clearCurrentProfile = () => {
return {
type: CLEAR_CURRENT_PROFILE,
}
}
// Get current profile
export const getCurrentProfile = () => dispatch => {
dispatch(setProfileLoading());
axios.get('/api/profile')
.then(res => dispatch({
type: GET_PROFILE,
payload: res.data
})).catch(error =>
dispatch({
type: GET_PROFILE,
payload: {}
}))
};
profileReducer.js:
import {GET_PROFILE, PROFILE_LOADING, CLEAR_CURRENT_PROFILE} from '../actions/types';
const initialState = {
profile: null,
profiles: null,
loading: false
};
const profileReducer = (state=initialState, action) => {
switch(action.type) {
case PROFILE_LOADING:
return {
...state,
loading: true
}
case GET_PROFILE:
return {
...state,
profile: action.payload,
loading: false
}
case CLEAR_CURRENT_PROFILE:
return {
...state,
profile: null,
loading: false
}
default:
return state;
}
};
export default profileReducer;

TypeError: Cannot destructure property 'user' of 'Object(...)(...)' as it is undefined

I'm relatively new to react. I'm trying to use the jwt login methods from a template I downloaded. It's throwing this error and I'm clueless any help would be appreciated.
AuthHooks.js
// ForJWT Auth
import {getUserFromJwtAuth} from './helper/AuthHelper';
import {
useJWTAuth,
useJWTAuthActions,
} from '../services/auth/jwt-auth/JWTAuthProvider';
export const useAuthUser = () => {
const {user, isAuthenticated, isLoading} = useJWTAuth();
return {
isLoading,
isAuthenticated,
user: getUserFromJwtAuth(user),
};
};
export const useAuthMethod = () => {
const {signInUser, signUpUser, logout} = useJWTAuthActions();
return {
signInUser,
logout,
signUpUser,
};
};
JWTAuthProvider.js
import React, {createContext, useContext, useEffect, useState} from 'react';
import PropTypes from 'prop-types';
import {useDispatch} from 'react-redux';
import {
FETCH_ERROR,
FETCH_START,
FETCH_SUCCESS,
} from '../../../../shared/constants/ActionTypes';
import jwtAxios, {setAuthToken} from './jwt-api';
const JWTAuthContext = createContext();
const JWTAuthActionsContext = createContext();
export const useJWTAuth = () => useContext(JWTAuthContext);
export const useJWTAuthActions = () => useContext(JWTAuthActionsContext);
const JWTAuthAuthProvider = ({children}) => {
const [firebaseData, setJWTAuthData] = useState({
user: null,
isAuthenticated: false,
isLoading: true,
});
const dispatch = useDispatch();
useEffect(() => {
const getAuthUser = () => {
const token = localStorage.getItem('token');
if (!token) {
setJWTAuthData({
user: undefined,
isLoading: false,
isAuthenticated: false,
});
return;
}
setAuthToken(token);
jwtAxios
.get('/auth')
.then(({data}) =>
setJWTAuthData({
user: data,
isLoading: false,
isAuthenticated: true,
}),
)
.catch(() =>
setJWTAuthData({
user: undefined,
isLoading: false,
isAuthenticated: false,
}),
);
};
getAuthUser();
}, []);
const signInUser = async ({email, password}) => {
dispatch({type: FETCH_START});
try {
const {data} = await jwtAxios.post('auth', {email, password});
localStorage.setItem('token', data.token);
setAuthToken(data.token);
const res = await jwtAxios.get('/auth');
setJWTAuthData({user: res.data, isAuthenticated: true, isLoading: false});
dispatch({type: FETCH_SUCCESS});
} catch (error) {
setJWTAuthData({
...firebaseData,
isAuthenticated: false,
isLoading: false,
});
dispatch({type: FETCH_ERROR, payload: error.message});
}
};
const signUpUser = async ({name, email, password}) => {
dispatch({type: FETCH_START});
try {
const {data} = await jwtAxios.post('users', {name, email, password});
localStorage.setItem('token', data.token);
setAuthToken(data.token);
const res = await jwtAxios.get('/auth');
setJWTAuthData({user: res.data, isAuthenticated: true, isLoading: false});
dispatch({type: FETCH_SUCCESS});
} catch (error) {
setJWTAuthData({
...firebaseData,
isAuthenticated: false,
isLoading: false,
});
dispatch({type: FETCH_ERROR, payload: error.message});
}
};
const logout = async () => {
localStorage.removeItem('token');
setAuthToken();
setJWTAuthData({
user: null,
isLoading: false,
isAuthenticated: false,
});
};
return (
<JWTAuthContext.Provider
value={{
...firebaseData,
}}>
<JWTAuthActionsContext.Provider
value={{
signUpUser,
signInUser,
logout,
}}>
{children}
</JWTAuthActionsContext.Provider>
</JWTAuthContext.Provider>
);
};
export default JWTAuthAuthProvider;
JWTAuthAuthProvider.propTypes = {
children: PropTypes.node.isRequired,
};
Currently it's throwing error TypeError: Cannot destructure property 'user' of 'Object(...)(...)' as it is undefined. on the line
const {user, isAuthenticated, isLoading} = useJWTAuth();
You need at least to initialize the JWTAuthContext context with an empty object.
const JWTAuthContext = createContext({});

getting typeError while using dispatch from react context-api

I have created a context store using react context API and I a function to dispatch action on the state but every time I call the function I am getttype errorpeError: dispatch is not a function```
the implementation is of login for the user in react using context API to store the state
my context provider / auth context provider
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
isFetching: false,
error: false,
};
export const AuthContext = createContext(INITIAL_STATE);
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
isFetching: state.isFetching,
error: state.error,
dispatch: dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
and every time I useContext to get the values of user,isFetching,error,dispatch
I am getting undefined for dispatch but the other values are coming correct isFetching is coming false as it should be initially but the value of dispatch is coming undefined.
where I am using the context
const { isFetching, dispatch } = useContext(AuthContext);
const handleClick = (e) => {
e.preventDefault();
console.log({ isFetching, dispatch });
dispatch({ type: "LOGIN_START" });
e.preventDefault();
loginCall(
{ email: email.current.value, password: password.current.value },
dispatch
);
};
login call function
export const loginCall = async (userCredential, dispatch) => {
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/auth/login", userCredential);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
} catch (err) {
dispatch({ type: "LOGIN_FAILURE", payload: err });
}
};
You need to import AuthContextProvider in index.js
and add this snipet
<AuthContextProvider>
<App/>
</AuthContextProvider>

Migrating from Redux to Redux toolkit

I'm slowly migrating over from Redux to Redux toolkit. I'm still pretty new but I have this login action function. How can I translate old function below do I need createAsyncThunk to achieve this?
export const login = (email, password) => (dispatch) => {
dispatch(requestLogin());
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((user) => {
dispatch(responseLogin(user));
})
.catch((error) => {
dispatch(loginError());
});
};
and my auth slice looks something like this:
const authSlice = createSlice({
name: "authSlice",
initialState: {
isLoggingIn: false,
isLoggingOut: false,
isVerifying: false,
loginError: false,
logoutError: false,
isAuthenticated: false,
user: {},
},
reducers: {
signInWithEmail: (state, action) => {
const { email, password } = action.payload;
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((response) => {
const {
uid,
email,
emailVerified,
phoneNumber,
password,
displayName,
photoURL,
} = response.user;
})
.catch((error) => {
console.log(error);
});
},
},
extraReducers: {},
});
Lets create a productSlice.js
import { createSlice,createSelector,PayloadAction,createAsyncThunk,} from "#reduxjs/toolkit";
export const fetchProducts = createAsyncThunk(
"products/fetchProducts", async (_, thunkAPI) => {
try {
const response = await fetch(`url`); //where you want to fetch data
return await response.json();
} catch (error) {
return thunkAPI.rejectWithValue({ error: error.message });
}
});
const productsSlice = createSlice({
name: "products",
initialState: {
products: [],
loading: "idle",
error: "",
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchProducts.pending, (state) => {
state. products = [];
state.loading = "loading";
});
builder.addCase(
fetchProducts.fulfilled, (state, { payload }) => {
state. products = payload;
state.loading = "loaded";
});
builder.addCase(
fetchProducts.rejected,(state, action) => {
state.loading = "error";
state.error = action.error.message;
});
}
});
export const selectProducts = createSelector(
(state) => ({
products: state.products,
loading: state.products.loading,
}), (state) => state
);
export default productsSlice;
In your store.js add productsSlice: productsSlice.reducer in out store reducer.
Then for using in component add those code ... I'm also prefer to use hook
import { useSelector, useDispatch } from "react-redux";
import { fetchProducts,selectProducts,} from "path/productSlice.js";
Then Last part calling those method inside your competent like this
const dispatch = useDispatch();
const { products } = useSelector(selectProducts);
React.useEffect(() => {
dispatch(fetchProducts());
}, [dispatch]);
And Finally, you can access data as products in your component.
The reducer you showed is very wrong. Reducers must never do anything async!
You don't need createAsyncThunk, but if you want to use it, it'd be like this:
export const login = createAsyncThunk(
'login',
({email, password}) => firebase.auth().signInWithEmailAndPassword(email, password)
);
const authSlice = createSlice({
name: "authSlice",
initialState: {
isLoggingIn: false,
isLoggingOut: false,
isVerifying: false,
loginError: false,
logoutError: false,
isAuthenticated: false,
user: {},
},
reducers: {
/* any other state updates here */
},
extraReducers: (builder) => {
builder.addCase(login.pending, (state, action) => {
// mark something as loading here
}
builder.addCase(login.fulfilled, (state, action) => {
// mark request as complete and save results
}
}
});
Note that createAsyncThunk only allows one argument to be passed to the thunk action creator, so it now must be an object with both fields instead of separate arguments.

getCurentPosition() in Reactjs not updating state

I'm trying the get the user current location in my app, but even if I can see it when I console.log it it doesn't work.
I'm using an async function in order to retrieve it but I must be doing something wrong and I cannot figure out what the issue is.
ContextState
import React, { useReducer } from "react";
import RestContext from "./restContext";
import RestReducer from "./restReducer";
import Yelp from "../../Util/Yelp";
import { getCurrentPosition } from "../../Util/GeoLocation";
import {
GET_RESTAURANTS,
GET_INFO_RESTAURANT,
CLEAR_SEARCH,
SET_LOADING,
GET_LOCATION,
} from "../../types";
const RestState = (props) => {
const initalState = {
restaurants: [],
restaurant: {},
loading: false,
location: {},
};
const [state, dispatch] = useReducer(RestReducer, initalState);
// Get Restaurants
const getRestaurants = async (text) => {
setLoading();
let restaurants = await Yelp.searchRestaurants(text);
if (restaurants) {
dispatch({ type: GET_RESTAURANTS, payload: restaurants });
} else {
dispatch({ type: GET_RESTAURANTS, payload: [] });
}
};
// Get info Restaurants
const getRestaurantInfo = async (id) => {
setLoading();
let restaurant = await Yelp.searchRestaurantsInfo(id);
if (restaurant) {
dispatch({ type: GET_INFO_RESTAURANT, payload: restaurant });
} else {
dispatch({ type: GET_INFO_RESTAURANT, payload: {} });
}
};
// Clear search
const clearSearch = () => dispatch({ type: CLEAR_SEARCH });
// Set loading
const setLoading = () => dispatch({ type: SET_LOADING });
// Get location
const fetchCoordinates = async () => {
try {
const coords = await getCurrentPosition();
dispatch({ type: GET_LOCATION, payload: coords });
} catch (error) {
// Handle error
console.error(error);
}
}
return (
<RestContext.Provider
value={{
restaurants: state.restaurants,
restaurant: state.restaurant,
loading: state.loading,
getRestaurants,
clearSearch,
getRestaurantInfo,
fetchCoordinates,
}}
>
{props.children}
</RestContext.Provider>
);
};
export default RestState;
It's reducer
import {
GET_RESTAURANTS,
GET_INFO_RESTAURANT,
CLEAR_SEARCH,
SET_LOADING,
GET_LOCATION,
} from "../../types";
export default (state, action) => {
switch (action.type) {
case GET_RESTAURANTS:
return { ...state, restaurants: action.payload, loading: false };
case GET_INFO_RESTAURANT:
return { ...state, restaurant: action.payload, loading: false };
case CLEAR_SEARCH:
return { ...state, restaurants: [], loading: false };
case SET_LOADING:
return {
...state,
loading: true,
};
case GET_LOCATION:
return { ...state, location: action.payload };
default:
return state;
}
};
And the Home page when it's should be used
import React, { Fragment, useEffect, useContext } from "react";
import Search from "../../Components/restaurants/Search";
import Alert from "../../Components/layout/Alert";
import Navbar from "../../Components/layout/Navbar";
import DisplayRestaurants from "../../Components/layout/DisplayRestaurants";
import Footer from "../../Components/layout/Footer";
import { Waypoint } from "react-waypoint";
import RestContext from "../context/restaurant/restContext";
const Home = () => {
const restContext = useContext(RestContext);
useEffect(() => {
restContext.fetchCoordinates();
// eslint-disable-next-line
}, []);
const handleWaypointEnter = () => {
document.querySelector("nav").classList.remove("fixed");
};
const handleWaypointLeave = () => {
document.querySelector("nav").classList.add("fixed");
};
return (
<section className="main-home">
<Fragment>
<Navbar />
<Search />
<Alert />
<Waypoint onEnter={handleWaypointEnter} onLeave={handleWaypointLeave} />
<DisplayRestaurants />
<Footer />
</Fragment>
</section>
);
};
export default Home;
getCurrentPosition
export function getCurrentPosition(options = {}) {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, options);
});
}
coord obj
GeolocationCoordinates {latitude: 52.3555177, longitude: -1.1743196999999999, altitude: null, accuracy: 372529, altitudeAccuracy: null, …}
accuracy: 372529
altitude: null
altitudeAccuracy: null
heading: null
latitude: 52.3555177
longitude: -1.1743196999999999
speed: null
__proto__: GeolocationCoordinates
Thanks for your help
can you try this instead?
it returns a promise so in theory should be able to use .then
getCurrentPosition().then((res) => {
console.log(res) // check what `res` is
dispatch({ type: GET_LOCATION, payload: res.cords });
})

Categories

Resources