On my website switching between pages is completely fine and works (it doesnt refresh or load due to redux) but the moment the page is refreshed or i manually enter a link to access, it logs me out. Also it just started happening now, yesterday when i was working on some other things in code, it never logged me out when I manually with links/urls navigated thru website or refreshing but now for some reason it doesnt work and I'm 99% sure I havent touched any auth part of the code...
This is my code:
authApiSlice:
import { apiSlice } from "../../app/api/apiSlice";
import { logOut, setCredentials } from "./authSlice";
export const authApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
login: builder.mutation({
query: (credentials) => ({
url: "/auth",
method: "POST",
body: { ...credentials },
}),
}),
sendLogout: builder.mutation({
query: () => ({
url: "/auth/logout",
method: "POST",
}),
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
console.log(data);
dispatch(logOut());
setTimeout(() => {
dispatch(apiSlice.util.resetApiState());
}, 1000);
} catch (err) {
console.log(err);
}
},
}),
refresh: builder.mutation({
query: () => ({
url: "/auth/refresh",
method: "GET",
}),
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
console.log(data);
const { accessToken } = data;
dispatch(setCredentials({ accessToken }));
} catch (err) {
console.log(err);
}
},
}),
}),
});
export const { useLoginMutation, useSendLogoutMutation, useRefreshMutation } =
authApiSlice;
authSlice:
import { createSlice } from "#reduxjs/toolkit";
const authSlice = createSlice({
name: "auth",
initialState: { token: null },
reducers: {
setCredentials: (state, action) => {
const { accessToken } = action.payload;
state.token = accessToken;
},
logOut: (state, action) => {
state.token = null;
},
},
});
export const { setCredentials, logOut } = authSlice.actions;
export default authSlice.reducer;
export const selectCurrentToken = (state) => state.auth.token;
import { Outlet, Link } from "react-router-dom";
import { useEffect, useRef, useState } from "react";
import { useRefreshMutation } from "./authApiSlice";
import usePersist from "../../hooks/usePersist";
import { useSelector } from "react-redux";
import { selectCurrentToken } from "./authSlice";
const PersistLogin = () => {
const [persist] = usePersist();
const token = useSelector(selectCurrentToken);
const effectRan = useRef(false);
const [trueSuccess, setTrueSuccess] = useState(false);
const [refresh, { isUninitialized, isLoading, isSuccess, isError, error }] =
useRefreshMutation();
useEffect(() => {
if (effectRan.current === true || process.env.NODE_ENV !== "development") {
// React 18 Strict Mode
const verifyRefreshToken = async () => {
console.log("verifying refresh token");
try {
//const response =
await refresh();
//const { accessToken } = response.data
setTrueSuccess(true);
} catch (err) {
console.error(err);
}
};
if (!token && persist) verifyRefreshToken();
}
return () => (effectRan.current = true);
// eslint-disable-next-line
}, []);
let content;
if (!persist) {
// persist: no
console.log("no persist");
content = <Outlet />;
} else if (isLoading) {
//persist: yes, token: no
console.log("loading");
} else if (isError) {
//persist: yes, token: no
console.log("error");
content = (
<p className="errmsg">
{`${error?.data?.message} - `}
<Link to="/login">Please login again</Link>.
</p>
);
} else if (isSuccess && trueSuccess) {
//persist: yes, token: yes
console.log("success");
content = <Outlet />;
} else if (token && isUninitialized) {
//persist: yes, token: yes
console.log("token and uninit");
console.log(isUninitialized);
content = <Outlet />;
}
return content;
};
export default PersistLogin;
RequireAuth
import { useLocation, Navigate, Outlet } from "react-router-dom";
import useAuth from "../../hooks/useAuth";
const RequireAuth = ({ allowedRoles }) => {
const location = useLocation();
const { roles } = useAuth();
const content = roles.some((role) => allowedRoles.includes(role)) ? (
<Outlet />
) : (
<Navigate to="/prijava" state={{ from: location }} replace />
);
return content;
};
export default RequireAuth;
It just stopped worked for some reason, it shouldnt log me out when I refresh.
Related
When the user hits the login button, it redirects to the Unsplash login page. After a successful login, the page redirects back to "localhost" with the "code=" parameter in the URL (http://localhost:3000/?code=VbnuDo5fKJE16cjR#=). After that, I need to get the username of the current user and change the background color of his liked images.
Why does the background color only change when the page is reloaded and not after a successful login?
There are too many requests happening at the same time and I don't know how to handle them properly.
Home.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import ImageList from "../components/ImageList";
import SearchBar from "../components/SearchBar";
import Loader from "../helpers/Loader";
import Login from "../components/Login";
function Home() {
const [page, setPage] = useState(1);
const [query, setQuery] = useState("landscape");
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(false);
const clientId = process.env.REACT_APP_UNSPLASH_KEY;
const url = `https://api.unsplash.com/search/photos?page=${page}&query=${query}&client_id=${clientId}&per_page=30`;
const fetchImages = () => {
setLoading(true);
axios
.get(url)
.then((response) => {
setImages([...images, ...response.data.results]);
})
.catch((error) => console.log(error))
.finally(() => {
setLoading(false);
});
setPage(page + 1);
};
useEffect(() => {
fetchImages();
setQuery("");
}, []);
return (
<div>
<Login />
{loading && <Loader />}
<ImageList images={images} />
</div>
);
}
export default Home;
Login.js
import React, { useEffect } from "react"
import { useAppContext } from "../context/appContext";
function Login() {
const { handleClick, getToken, token, getUserProfile } = useAppContext();
useEffect(() => {
if (window.location.search.includes("code=")) {
getToken();
}
if (token) {
getUserProfile();
}
}, [token]);
return (
<div>
<button onClick={() => handleClick()}>Log in</button>
</div>
);
}
export default Login;
appContext.js
import React, { useReducer, useContext } from "react";
import reducer from "./reducer";
import axios from "axios";
import {SET_TOKEN,SET_LIKED_PHOTOS_ID } from "./actions";
const token = localStorage.getItem("token");
const username = localStorage.getItem("username");
const initialState = {
token: token,
username: username,
likedPhotosId: [],
};
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const handleClick = () => {
window.location.href = `${api_auth_uri}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope.join(
"+"
)}`;
};
const getToken = async () => {
const urlCode = window.location.search.split("code=")[1];
try {
const { data } = await axios.post(
`${api_token_uri}?client_id=${client_id}&client_secret=${client_secret}&redirect_uri=${redirect_uri}&code=${urlCode}&grant_type=${grant_type}`
);
const { access_token } = data;
localStorage.setItem("token", access_token);
dispatch({
type: SET_TOKEN,
payload: { access_token },
});
} catch (error) {
console.log(error);
}
};
const getUserProfile = async () => {
try {
const { data } = await axios.get(`https://api.unsplash.com/me`, {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + state.token,
},
});
const { username } = data;
localStorage.setItem("username", username);
} catch (error) {
console.log(error);
}
};
const getLikedPhotos = async () => {
try {
const { data } = await axios.get(
`https://api.unsplash.com/users/${state.username}/likes`,
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + state.token,
},
}
);
const likedPhotosId = data.map((photo) => photo.id);
dispatch({
type: SET_LIKED_PHOTOS_ID,
payload: { likedPhotosId },
});
} catch (error) {
console.log(error);
}
};
return (
<AppContext.Provider
value={{
...state,
handleClick,
getToken,
getUserProfile,
getLikedPhotos,
}}
>
{children}
</AppContext.Provider>
);
};
const useAppContext = () => useContext(AppContext);
export { AppProvider, initialState, useAppContext };
ImageList.js
import React, {useEffect } from "react";
import "../styles/ImageList.scss";
import { useAppContext } from "../context/appContext";
function ImageList({ images }) {
const { username, likedPhotosId, getLikedPhotos } = useAppContext();
useEffect(() => {
if (username) {
getLikedPhotos();
}
}, [username]);
return (
<div className="result">
{images?.map((image) => (
<div
style={{
backgroundColor: likedPhotosId?.includes(image.id) ? "red" : "",
}}
>
<div key={image.id}>
<img src={image.urls.small} alt={image.alt_description} />
</div>
</div>
))}
</div>
);
}
export default ImageList;
reducer.js
import { SET_TOKEN, SET_LIKED_PHOTOS_ID } from "./actions";
const reducer = (state, action) => {
if (action.type === SET_TOKEN) {
return {
...state,
token: action.payload.access_token,
};
}
if (action.type === SET_LIKED_PHOTOS_ID) {
return {
...state,
likedPhotosId: action.payload.likedPhotosId,
};
}
throw new Error(`no such action : ${action.type}`);
};
export default reducer;
The problem is in your function. You save the username in localStorage but not in your reducer state:
const getUserProfile = async () => {
try {
const { data } = await axios.get(`https://api.unsplash.com/me`, {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + state.token,
},
});
const { username } = data;
localStorage.setItem("username", username);
} catch (error) {
console.log(error);
}
};
The issue here is that react doesn't trigger a rerender of components when you set something in localStorage and in your ImageList component use have a useEffect expecting username to change before calling the getLikedPhotos:
const { username, likedPhotosId, getLikedPhotos } = useAppContext();
useEffect(() => {
if (username) {
getLikedPhotos();
}
}, [username]);
So to fix you need to add an action for setting the username state in your reducer:
import { SET_TOKEN, SET_LIKED_PHOTOS_ID, SET_USERNAME } from "./actions";
const reducer = (state, action) => {
if (action.type === SET_TOKEN) {
return {
...state,
token: action.payload.access_token,
};
}
if (action.type === SET_USERNAME) {
return {
...state,
username: action.payload.username,
};
}
if (action.type === SET_LIKED_PHOTOS_ID) {
return {
...state,
likedPhotosId: action.payload.likedPhotosId,
};
}
throw new Error(`no such action : ${action.type}`);
};
export default reducer;
And then dispatch that action from the getUserProfile:
const getUserProfile = async () => {
try {
const { data } = await axios.get(`https://api.unsplash.com/me`, {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + state.token,
},
});
const { username } = data;
localStorage.setItem("username", username);
dispatch({
type: SET_USERNAME,
payload: { username },
});
} catch (error) {
console.log(error);
}
};
I have done jwt authorization and registration. But when checking whether the user is authorized, he issues 401 Unauthorized sent a request to receive a refresh token via axios
Here is the authorization verification action:
export const checkAuth = () => {
try {
return async dispatch => {
const response = await axios.get(`http://localhost:5000/api/refresh`, {withCredentials: true})
//console.log(response)
dispatch({
type: SET_USER,
payload: response.data
})
}
} catch (e) {
console.log(e.response?.data?.message);
}
}
here is the reducer:
import {LOGOUT, SET_USER} from "../types";
const initialState = {
currentUser: {},
isAuth: false
}
export const authReducer = (state = initialState, action) => {
switch (action.type) {
case SET_USER:
localStorage.setItem('token', action.payload.accessToken);
return {
...state,
currentUser: action.payload,
isAuth: true
}
case LOGOUT:
localStorage.removeItem('token')
return {
...state,
currentUser: {},
isAuth: false
}
default:
return state
}
}
export const logout = () => ({type: LOGOUT})
Here is the app component:
import './App.css';
import {Route, Routes} from "react-router-dom";
import LoginForm from "./component/LoginForm";
import {useDispatch, useSelector} from "react-redux";
import RegisterForm from "./component/RegisterForm";
import {useEffect} from "react";
import {checkAuth} from "./redux/action";
function App() {
const users = useSelector(state => {
const {authReducer} = state
return authReducer
})
const dispatch = useDispatch()
//console.log(users)
useEffect(() => {
checkAuth() // here I did dispatch()
}, [])
if (!users.isAuth) {
return (<div>
<Routes>
<Route path="/registration" element={<RegisterForm/>}/>
<Route path="/login" element={<LoginForm/>}/>
</Routes>
</div>)
}
console.log(users.currentUser)
return (
<div className="App">
{users.isAuth ? users.currentUser.rows.map(data => <div key={data.id}>{data.email}</div>) : 'log in'}
</div>
);
}
export default App;
Here are the axios settings, but I tried using fetch() and the result is the same (401)
import axios from "axios";
export const API_URL = `http://localhost:5000/api`
const $api = axios.create({
withCredentials: true,
baseURL: API_URL
})
$api.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`
return config;
})
$api.interceptors.response.use((config) => {
return config;
},async (error) => {
const originalRequest = error.config;
if (error.response.status == 401 && error.config && !error.config._isRetry) {
originalRequest._isRetry = true;
try {
const response = await axios.get(`${API_URL}/refresh`, {withCredentials: true})
localStorage.setItem('token', response.data.accessToken);
return $api.request(originalRequest);
} catch (e) {
console.log('NOT AUTHORIZED')
}
}
throw error;
})
export default $api;
I installed cars on the server and call it like this
app.use(cors()) // Use this after the variable declaration / I tried such settings, it didn't help {credentials: true, origin: 'http://localhost:3000'}
For some reason, a 401 error pops up, although everything works in postman!
Why don't I know anymore!???
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>
);
}
So I have a mobile app in react native for which I am trying to create the auth flow properly.I had this first as a web app and then now trying to do the same as a mobile app. I am using redux for state management.
Here is the flow: Once the user logs in or registers, I send the data to the backend using axios. The backend generates a user token. I would like to create a system where I can fetch the token when a user logs in and store that with AsyncStorage so that I can directly log the user in when he or she tries to open the app again.
Here is the code, more than happy to answer any questions, this was done through a react tutorial really:
LoginScreen.js
const Login = ( { navigation }) => {
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [message, setMessage] = useState(null)
// Errors
const [EmailMessage, setEmailMessage] = useState(null);
const [PasswordMessage, setPasswordMessage] = useState(null);
const dispatch = useDispatch();
const userLogin = useSelector((state) => state.userLogin);
const { loading, error, userInfo } = userLogin
useEffect(() => {
if (userInfo) {
navigation.navigate('MainTabs', { screen: 'Home'});
}
}, [navigation, userInfo]);
const submitHandler = (e) => {
e.preventDefault();
if (!email) {
alert('Please fill in the email');
return
};
if (!password) {
alert('Please fill in the password');
return
}
dispatch(login(email, password));
};
The return part is the frontend code so left that out for the sake of brevity
Now the redux part:
userActions.js
import axios from "axios";
import { USER_LOGIN_FAIL, USER_LOGIN_REQUEST, USER_LOGIN_SUCCESS, USER_LOGOUT, USER_REGISTER_FAIL, USER_REGISTER_SUCCESS, USER_REGISTER_REQUEST } from "../constants/userConstants"
import AsyncStorage from '#react-native-async-storage/async-storage';
export const login = (email, password) => async(dispatch) => {
try {
dispatch({ type: USER_LOGIN_REQUEST });
const config = {
headers: {
"Content-type": "application/json"
}
}
const {data} = await axios.post("api/url", {email, password}, config)
dispatch({type: USER_LOGIN_SUCCESS, payload:data});
await AsyncStorage.setItem("userInfo", JSON.stringify(data))
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
})
console.log("This login attempt is unsuccessful");
}
}
export const logout = () => async (dispatch) => {
await AsyncStorage.removeItem("userInfo")
dispatch({ type: USER_LOGOUT });
};
export const register = (full_name, email, password, social) => async(dispatch) => {
try {
dispatch({type: USER_REGISTER_REQUEST});
const config = {
headers: {
"Content-type": "application/json"
}
};
const {data} = await axios.post("api/url", {full_name, email, password, social}, config);
dispatch({ type: USER_REGISTER_SUCCESS, payload: data});
dispatch({ type: USER_LOGIN_SUCCESS, payload: data});
await AsyncStorage.setItem("userInfo", JSON.stringify(data))
} catch (error) {
dispatch({type: USER_REGISTER_FAIL, payload:
error.response && error.response.data.message
? error.response.data.message
: error.message
})
}}
userReducers.js
import { USER_LOGIN_REQUEST, USER_REGISTER_FAIL, USER_REGISTER_REQUEST, USER_REGISTER_SUCCESS } from "../constants/userConstants";
import { USER_LOGIN_SUCCESS, USER_LOGIN_FAIL, USER_LOGOUT } from "../constants/userConstants";
export const userLoginReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return {loading: true}
case USER_LOGIN_SUCCESS:
return {loading: false, userInfo: action.payload}
case USER_LOGIN_FAIL:
return {loading: false, error:action.payload}
case USER_LOGOUT:
return {}
default:
return state;
}
}
export const userRegisterReducer = (state = {}, action) => {
switch (action.type) {
case USER_REGISTER_REQUEST:
return {loading:true}
case USER_REGISTER_SUCCESS:
return {loading:false, userInfo: action.payload}
case USER_REGISTER_FAIL:
return {loading:false, error: action.payload}
default:
return state;
}
}
store.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
import { userLoginReducer, userRegisterReducer } from './src/redux/reducers/userReducers';
import AsyncStorage from '#react-native-async-storage/async-storage';
const reducer = combineReducers({
//contains reducers
userLogin: userLoginReducer,
userRegister: userRegisterReducer
});
const middleware = [thunk];
const getData = async () => {
try {
const value = await AsyncStorage.getItem("userInfo")
return value != null ? JSON.parse(value) : null;
} catch(e) {
console.log("this attempt is not successful");
}
}
const userInfoFromStorage = getData() || null;
alert(JSON.stringify(userInfoFromStorage))
const initialState = {
userLogin: {userInfo: userInfoFromStorage}
};
const store = createStore(
reducer,
initialState,
applyMiddleware(...middleware)
);
export default store
I would appreciate any help here as I am not able to resolve how to solve this is this is a promise issue on fetch or something more general than that. I have a token generated in the backend once I register a user. I would really appreciate if anyone knows of the best way on how to fetch and save that token and log the user in if the user had already logged in
First, create a file with the name authProvider.ts. This will hold objects, with key as isAuthenticated and async getAuthToken() with callback function as property.
const authProvider: any = {
isAuthenticated: false,
authToken: null,
async getAuthToken(callback: VoidFunction) {
await GetSessionData('Access-token').then(
res => {
if (res) {
authProvider.isAuthenticated = true;
authProvider.authToken = res;
callback();
} else {
authProvider.isAuthenticated = false;
authProvider.authToken = res;
callback();
}
}
);
}
};
export { authProvider };
export const GetSessionData = async (key: string) => {
return await AsyncStorage.getItem(
key,
(err, value) => {
if (err) {
console.log(err);
} else {
return value;
}
}
);
}
In app.tsx
In App component we will call the above authProvider to get the token from Async storage using await. Once done it will execute the below callback function logic.
const App = () => {
const [isLoading, setLoading] = useState<boolean>(true);
useEffect(() => {
authProvider.getAuthToken(() => {
if (authProvider.isAuthenticated) {
store.dispatch(isAuthenticatedUser(true));
setLoading(false);
} else {
store.dispatch(isAuthenticatedUser(false));
setLoading(false);
// *** redirect to login page logic ***.
}
});
}, []);
return (
<>
<Provider store={store}>
{
!isLoading ?
<Dashboard /> :
<AppLoader />
}
</Provider>
</>
);
}
Here is my problem, in my react app whenever a order is created I want to get a Subscription for that order called orderNotification,
setup in order resolver:
Subscription: {
orderNotification: {
subscribe: (_, __, { pubsub }) => pubsub.asyncIterator(ORDER_NOTIFICATION)
}
}
mutation:
Mutation: {
async createOrder(_, { MakeOrderInput: { state, message, products, total } }, context) {
try {
const userAuth = isAuth(context);
const pubsub = context.pubsub;
const newOrder = new Order({
state,
username: userAuth.username,
user: userAuth.id,
createdAt: new Date().toISOString(),
total,
message,
products,
userAddress: userAuth.address,
});
const index = products.findIndex(x => x.cost === 0);
if (index != -1) {
const u = await User.findById({ _id: userAuth.id });
await User.findByIdAndUpdate({ _id: userAuth.id }, { points: u.points - 20 }, (err, data) => {
if (err) {
console.log(err)
} else {
console.log('fatto')
}
});
}
const order = await newOrder.save();
pubsub.publish(ORDER_NOTIFICATION, {
orderNotification: order
});
return order;
} catch (err) {
// throw new Error(err);
console.log(err)
}
},
all works fine in graphql Playground but when I have to get and show the results in my component the returned data is null:
import React from 'react'
import gql from 'graphql-tag';
import { useSubscription } from '#apollo/client';
import { Box } from 'grommet'
function SubscriptionOrder() {
const { data, loading, error } = useSubscription(SUBSCRIPTION_USER_ORDER, {
onSubscriptionData: (d) => console.log(d),
onSubscriptionComplete: (da) => console.log(da)
});
// return null
// console.log(data)
return (
<>
<Box style={{ marginTop: '96px' }}>
{data && data.orderNotification ? (
<h1>hi: {data.orderNotification.username}</h1>
) : (
<h1>NO DATA</h1>
)
}
</Box>
</>
)
};
const SUBSCRIPTION_USER_ORDER = gql`
subscription orderNotification{
orderNotification {
username
}
}
`;
export default SubscriptionOrder;
so considering that in playground works the error may be in my ApolloClient links configuration:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { ApolloClient } from 'apollo-client';
import { ApolloProvider } from '#apollo/react-hooks';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { ApolloLink, Observable, split } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { TokenRefreshLink } from "apollo-link-token-refresh";
import jwtDecode from "jwt-decode";
import { getAccessToken, setAccessToken } from './accessToken';
import dotenv from 'dotenv/config.js'
const cache = new InMemoryCache({});
const httpLink = new HttpLink({
uri: process.env.NODE_ENV === 'development' ? `${process.env.REACT_APP_SERVER_DEV}/graphql` : `${process.env.REACT_APP_SERVER_PRODUCTION}/graphql`,
credentials: "include",
});
const wsLink = new WebSocketLink({
uri: process.env.NODE_ENV === 'development' ? `ws://${process.env.REACT_APP_SERVER_DEV_WS}/graphql` : `ws://${process.env.REACT_APP_SERVER_PRODUCTION_WS}/graphql`,
options: {
reconnect: true,
lazy: true,
inactivityTimeout: 1000,
},
connectionCallback: err => {
if (err) {
console.log('Error Connecting to Subscriptions Server', err);
}
}
});
const splitLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscriptions";
},
wsLink,
httpLink
);
const requestLink = new ApolloLink(
(operation, forward) =>
new Observable(observer => {
let handle;
Promise.resolve(operation)
.then(operation => {
const accessToken = getAccessToken();
if (accessToken) {
operation.setContext({
headers: {
authorization: `Bearer ${accessToken}`
},
fetchOptions: {
credentials: 'include'
}
});
}
})
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
const client = new ApolloClient({
link: ApolloLink.from([
new TokenRefreshLink({
accessTokenField: "accessToken",
isTokenValidOrUndefined: () => {
const token = getAccessToken();
if (!token) {
return true;
}
try {
const { exp } = jwtDecode(token);
if (Date.now() >= exp * 1000) {
return false;
} else {
return true;
}
} catch {
return false;
}
},
fetchAccessToken: () => {
return fetch(process.env.NODE_ENV === 'development' ? `${process.env.REACT_APP_SERVER_DEV}/refresh_token` : `${process.env.REACT_APP_SERVER_PRODUCTION}/refresh_token`, {
method: "POST",
credentials: "include"
});
},
handleFetch: accessToken => {
setAccessToken(accessToken);
},
handleError: err => {
console.warn("Your refresh token is invalid. Try to relogin");
console.error(err);
}
}),
onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors);
console.log(networkError);
}),
requestLink,
splitLink,
]),
cache,
connectToDevTools: true,
credentials: 'include',
});
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode >,
document.getElementById('root')
);
here is my server:
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => ({ req, res, pubsub }),
introspection: true,
cors: corsOptions,
});
server.applyMiddleware({ app, cors: false });
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(PORT, () => {
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`)
console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`)
})
resolve the payload in the subscription with
Subscription: {
orderNotification: {
subscribe: (_, __, { pubsub }) => pubsub.asyncIterator(ORDER_NOTIFICATION),
resolve: (payload) => {
return payload;
},
}
}
another way to remove null or unwanted entries is withFilter to remove intimation in case of unknown or unwanted events;
writing this answer as an opyion, but the answer above is better
const { withFilter } = require('graphql-subscriptions');
Subscription: {
orderNotification: {
subscribe: withFilter(
() => pubsub.asyncIterator('ORDER_NOTIFICATION'),
(payload, variables) => {
// add any condition here
return (payload && payload !== null);
},
),
},
}