Use router from getServerSideProps - javascript

What I want to achieve is to redirect the user to the login page if his token has been expired.
Inside getServerSideProps, I send a get request to the server to receive some data; if I get the data I want to provide this data as props for my page but if it's unauthorized, I want to redirect the user to the login page, but it seems that I can't call useRouter inside getServerSideProps when I try I get this error:
React Hook "useRouter" is called in function "getServerSideProps" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".eslint
This is my code
export async function getServerSideProps({ req, params }) {
const token = await getToken({ req });
const userId = params.userId;
const router = useRouter(); // the line with the error
let result = {};
await axiosPrivateWithToken(accessToken)
.get(`/user/${userId}`)
.then((res) => {
result = res.data;
})
.catch((error) => {
if(error.response.status == 401) {
here where I want to use router.push('/login')
}
});
}
Is it possible? If so - how? Thank you for your time

you can only call hooks from function component or a custom React hook function as it is mentioned in the error, therefore there is no way to use useRouter from inside getServerSidePros.
However you can achieve this by the redirect key object
.then((res) => {
result = res.data;
})
.catch((error) => {
if(error.response.status == 401) result = 'redirection'
});
then based on the value of result you decide what to do :
if(result === 'redirection') {
return {
redirect: {
destination: "/login",
permanent: false,
}};
}else {
// what you are used to do
}

It's not possible to use useRouter inside getServerSideProps.
Instead, you can return axiosPrivateWithToken response and receive it as props on your component and put it inside useEffect and handle your redirection logic there.

We can direct users to the login page if "axiosPrivateWithToken" fails.
export async function authenticatedUsers(userId) {
try {
const response = await axiosPrivateWithToken(accessToken).get(`/user/${userId}`)
const result = response.data;
return result;
} catch (error) {
console.log(error);
return null;
}
return false;
}
export const getServerSideProps: GetServerSideProps = async ({req, params}) => {
const token = await getToken({ req });
const userId = params.userId;
let result = await authenticatedUsers(userId);
if (result == null) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
return {
props: {
result
}
};
};

Related

vue-router beforeEach function does not return item which is updated

I'm stuck with a problem. So here is the scenario. I put an axios request which takes the access token from cookies on store. Then I committed a mutation to make true isLoggedIn variable. Then I access this variable from Navbar to change menu items. It works. But when I try to access isLoggedIn variable with getters in beforeEach function, it turns still false. But it is true on Navbar.
user/actions.js which I request to backend to for authentication.
import axios from 'axios'
const checkUser = ({ commit }) => {
axios({
method: 'get',
url: 'http://localhost:3000/api/auth/VpW02cG0W2vGeGXs8DdLIq3dQ62qMd0',
withCredentials: true,
headers: {
Accept: "application/json",
},
})
.then((res) => {
commit('defineUser', res.data)
return true
})
.catch((err) => {
console.log(err)
return false
})
}
export default {
checkUser,
}
user/mutations.js which I set user and isLoggedIn variables
const defineUser = (state, res) => {
state.user = res.user
state.isLoggedIn = true
}
export default {
defineUser,
}
Then I call that action func in beforeEach in router
router.beforeEach(async (to, from, next) => {
const accessToken = VueCookies.get('access_token')
if (accessToken) { // first I check if there is an access token. I do that because I check every request if it is logged in. So I can manage Navbar.
await store.dispatch('user/checkUser')
if (store.getters['user/isLoggedIn']) { // HERE IS THE PROBLEM IT TURNS FALSE HERE. if there is an access token, then I check it with if mutation made isLoggedIn true and all doors are open for that user
next()
} else { // if it is false then show a toast and remove access token and reload the page
router.app.$bvToast.toast('You need to log in to see this page!', { // another question, when I deleted async it cannot read toast with only getter. If I put something else it works perfectly.
title: 'Unathorized',
variant: 'danger',
solid: true
})
VueCookies.remove('access_token')
router.go(router.currentRoute)
}
} else if (to.meta.requiresAuth) { // so if there is no access token and this page requires auth so show an toast
router.app.$bvToast.toast('You need to log in to see this page!', {
title: 'Unathorized',
variant: 'danger',
solid: true
})
} else { // if no requires auth and no access token then just get in the page
next()
}
})
If you need any other information please say, so I can share with you. Any help will be appreciated.
You are awaiting checkUser but it doesn't return a promise. Change it to:
const checkUser = ({ commit }) => {
return axios({ // notice the `return` statement
...
}
Alternatively, you could use async/await:
const checkUser = async ({ commit }) => { // async
await axios({ // await
...
}

How to fix "undefined" issue in async call to dispatch in action creator of redux?

I'm making a react app, implementing authentication to get token for the provides credentials from the server using the API call. Do my approach is perfect to do this?
I'm using Django API for backend and reactJS for frontend also using thunk as a middleware with redux.
authAction.js
import {AUTH_USER} from "./types";
export const authUser = (username, password) =>
{
return (dispatch) => {
const data = {
username: username,
password: password
}
return fetch("http://127.0.0.1:8000/api/v1/login/auth/", {
method: 'POST',
body: JSON.stringify(data),
headers: {'Content-Type': 'application/json'}
})
.then(results => results.json())
.then(results => {
dispatch({
type: AUTH_USER,
payload: results
})
}
)
}
}
authReducer.js
import {AUTH_USER} from "../actions/types";
const initialState = {
errors: [],
token : '',
}
export default function (state=initialState,action) {
switch (action.type) {
case AUTH_USER:
return {
...state,
token:action.payload.token,
errors:action.payload.errors,
}
default:
return state
}
}
login function
login(e){
e.preventDefault();
if(this.state.username && this.state.password){
console.log("Login");
this.props.authUser(this.state.username,this.state.password)
.then(res=>console.log(res)) // undefined
}
}
I want to get the results to be printed on the console which is fetched from API or in the more explained way I want to return the fetched results from the call of action in the caller. i.e. token: any token
It doesn't work that way. When you perform the action, after the async call, you do a dispatch with the result and the result is added to the store through the reducer. You need to get the values from the store by connecting to the store.
const mapStateToProps = ({
token, errors
}) => ({
token,
errors
});
connect(mapStateToProps, {authUser})(Login)
You can access this in your component now using this.props.token & this.props.errors

Where to put API session auth token in SDK request methods?

I am using the ConnectyCube React Native SDK and have obtained an app auth token using their API. This token is required when making further requests - for example when logging in as a user. Their documentation says:
Upgrade session token (user login)
If you have an application session token, you can upgrade it to a user session by calling login method:
var userCredentials = {login: 'cubeuser', password: 'awesomepwd'};
ConnectyCube.login(userCredentials, function(error, user) {
});
The problem is it that when I use this method, I get an error in response saying 'Token is required'.
If I were interfacing with a REST API, I would put the token in the header of the request, but obviously in this instance I can't. So the question is, where do I put the token? I have it, the documentation just doesn't tell you how to use it! Any help appreciated.
Ok I came up with a fix. First of all I just tried passing the auth token in to the userCredntials object in the same way as in the documentation for social auth, that is absent from the description in my above code snippet taken from their docs.
Then I Promisified the API calls from within useEffect inside an async function to make sure everything was happening in the right order, and it works:
export default function App() {
const createAppSession = () => {
return new Promise((resolve, reject) => {
ConnectyCube.createSession((error, session) => {
!error
? resolve(session.token)
: reject(error, '=====1=====');
});
})
}
const loginUser = (credentials) => {
return new Promise((resolve, reject) => {
ConnectyCube.login(credentials, ((error, user) => {
!error
? resolve(user)
: reject(error, '=====2=====');
}));
})
}
useEffect(() => {
const ccFunc = async () => {
ConnectyCube.init(...config)
const appSessionToken = await createAppSession();
const userCredentials = { login: 'xxxxx', password: 'xxxxxxx', keys: { token: appSessionToken } };
const user = await loginUser(userCredentials);
console.log(user);
}
ccFunc()
}, []);
Hope it works....
please implement it by yourself...just take an understanding from code below.
code says: send the username and password to api...if all ok then authenticate else throw error ...if all ok..then store the returned token is asyncStorage...you can create the storage by any name you like...and use the token eveywhere in your app.
SignInUser = async () => {
this.setState({
username: this.state.username,
password:this.state.password,
})
if(this.state.username && this.state.password !== null){
try{
this.setState({
loading:true
})
const response = await fetch('YOUR API', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: this.state.username,
password: this.state.password
})
});
var promiseResponse = await response.json()
console.log(promiseResponse.token);
try {
await AsyncStorage.setItem('STORE_YOUR_LOGIN_TOKEN_HERE', JSON.stringify(promiseResponse.token));
console.log('Token Stored In Async Storage');
let tokenFromAsync = await AsyncStorage.getItem('STORE_YOUR_LOGIN_TOKEN_HERE');
console.log('Getting Token From Async...')
tokenFromAsync = JSON.parse(tokenFromAsync)
if(tokenFromAsync !== null){
console.log(tokenFromAsync);
this.setState({
loading:false
})
this.props.navigation.navigate('Tabnav');
}
} catch (error) {
// saving error
console.log(`ERROR OCCURED ${error}`)
}
//this.props.navigation.navigate('Tabnav')
} catch(error){
console.log(`COULDN'T SIGN IN ${error}`)
}
} else {
this.setState({
msg:'Invalid Credentials',
label:'red'
});
}
}
This is how i got the login to work in their sample react native app 1. i created a credentials object like this in my custom login function in src>components>AuthScreen>AuthForm.js
var credentials = {id:'',login: this.state.login,password: this.state.password}
2.I used their _signIn(credentials) function and set the 'id' attribute of my credentials object after their UserService.signin(credentials) resolved with a user object. (the resolved user object contained the logged-in user's id i.e user.id). Then it worked. This is how the code looked for the signin after the little tweak.
loginUser() { //my custom signin function
var credentials = {id:'',login: this.state.login,password: this.state.password} //my credentials object
this._signIn(credentials)
}
_signIn(userCredentials) { //their signin function
this.props.userIsLogging(true);
UserService.signin(userCredentials)
.then((user) => {
userCredentials.id = user.id //setting id of my credentials object after promise resolved
ChatService.connect(userCredentials) //using my credentials object with id value set
.then((contacts) => {
console.warn(contacts)
this.props.userLogin(user);
this.props.userIsLogging(false);
Actions.videochat(); //login worked
})
.catch(e => {
this.props.userIsLogging(false);
alert(`Error.\n\n${JSON.stringify(e)}`);
})
})
.catch(e => {
this.props.userIsLogging(false);
alert(`Error.\n\n${JSON.stringify(e)}`);
})
}

How to wait for async data before send response using promises and arrow functions?

I'm new to ES6, arrow functions and promises, and I can't figure out how to use them, even worse together.
I started a project with a REST generator (https://github.com/diegohaz/rest) and it works fine, but I need to modify part of the authentication.
I need to return data from a third-party server during authentication. I created the function that returns the data correctly with axios, however I can't return this information along with the other information (from this project), response is sent before.
Below is the generated code, almost untouchable, I added just extraData: user.getExtraData(user)
// function in auth controller file
export const login = ({ user }, res, next) => {
sign(user.id)
.then((token) => ({
token, user: user.view(true), extraData: user.getExtraData(user)
}))
.then(success(res, 201))
.catch(next)
}
// function in user model file
view (full) {
let view = {}
let fields = ['id', 'name', 'picture']
if (full) {
fields = [...fields, 'email', 'createdAt']
}
fields.forEach((field) => {
view[field] = this[field]
})
return view
}
Here is my function added into the user model
getExtraData (userView) {
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.post( userView.host, querystring.stringify( {
data1:userView.data1,
data2:userView.data2
}))
.then((response) => {
return response.data
})
.catch((error) => {
console.log('Error', error)
return null
})
}
How would the best way to make response wait until extraData is return from getExtraData function with the given code ? Thanks
You can use async/await. In that case, you need to await for getExtraData. For such reason, the anonymous function inside login and getExtraData both need to be declared as asynchronous functions:
// function in auth controller file
export const login = ({ user }, res, next) => {
sign(user.id)
.then(async (token) => ({
token,
user: user.view(true),
// Wait for getExtraData to finish using await
extraData: await user.getExtraData(user)
}))
.then(success(res, 201))
.catch(next)
}
async getExtraData (userView) {
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
try {
const response = await axios.post( userView.host, querystring.stringify( {
data1:userView.data1,
data2:userView.data2
}))
return response.data
}
catch (err){
return null
}
}

Shared method of store fields

Motivation
I store user credentials in redux store. They are filled when user logs in. I would like to have reusable method to fetch data with user's username and password.
State / auth
const initState = {
isLoading: false,
user: undefined,
auth_err: false
};
My attempt
const fetchData = props => async (url, method, body) => {
try {
const response = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Base64.btoa(props.user.username + ":" + props.user.password)
},
body: body
});
console.log(response);
return response;
} catch (err) {
console.log(err);
}
};
const mapStateToProps = state => {
return {
user: state.auth.user
}
};
export const SENDREQUEST = connect(mapStateToProps)(fetchData);
Call
const response = await SENDREQUEST("http://localhost:8080/users", "GET");
But once I call it I get:
TypeError: Cannot call a class as a function
Is there any way at all to create such one?
Any help would be appreciated ♥
I am assuming that you know about redux and its middleware.
First of all the error comes from passing fetchData to the return value of connect : connect returns a function which is a HOC : takes a component, returns a component which is a class here that cannot be called as a function as you do.
A solution for your problem is to use mapDispatchToProps and a middleware, roughly as follow :
class LoginFormPresenter {
render () {
// render your login
return <form onSubmit={this.props.logUser}></form>
}
}
// This makes the LoginFormPresenter always receive a props `logUser`
const LoginFormConnector = connect((state => { user: state.user }), {
logUser: (e) => (
// create a credentials object by reading the form
const credentials = ...;
// return a valid redux action
return {
type: "LOGIN",
credentials
};
)
});
const LoginForm = LoginFormConnector(LoginFormPresenter);
// Create an ad hoc middleware
//
const middleware = ({ dispatch, getState }) => next => action => {
if (action.type === "LOGIN") {
// log your user
fetch()
.then(user => next({ type: "LOGIN", user }));
return next({ type: "PROCESSING_LOGIN" }); // indicate to redux that you are processing the request
}
// let all others actions pass through
return next(action);
};
So the mechanism works like this:
The LoginFormConnector will inject a props logUser into any component it is applied to. This props is a function wich dispatches an action with the credentials of your user. It will also inject a user props taken from the redux state for you to show your user.
Inside a redux middleware you catch the action and use your generic fetchData to process the request. When the request is resolved you dispatch the action to the reducer and update it. No data fetching occurs in the component itself, everything is handled at the middleware level.

Categories

Resources