Invalid hook call. Hooks can only be called inside of the body of a function - django

I was trying to develop a login page with the Django rest framework as the backend. The backend is working perfectly whereas I can't even set up react js. I am getting an error in the Index.js file of react. It tells "Invalid hook call. Hooks can only be called inside of the body of a function component"
This is what the error I get
App.js
import React from 'react';
import './App.css';
import Paperbase from './Layout/Paperbase'
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Login from './Layout/Login/Login'
import Register from './Layout/Register/Register'
export function App() {
return (
<BrowserRouter>
<Switch>
<Route path="/dashboard" render={() => <Paperbase /> } />
<Route path="/account/login" render={() =><Login />} />
<Route path="/account/register" render={() => <Register />} />
</Switch>
</BrowserRouter>
)
}
export default App
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import store from './store';
import { Provider } from 'react-redux';
import { render } from 'react-dom';
ReactDOM.render(
(<Provider store={store}>
<App/>
</Provider>),
document.getElementById('root') || document.createElement('div') // for testing purposes
);
serviceWorker.unregister();
Login.js
import React from 'react';
import Avatar from '#material-ui/core/Avatar';
import Button from '#material-ui/core/Button';
import CssBaseline from '#material-ui/core/CssBaseline';
import TextField from '#material-ui/core/TextField';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import Grid from '#material-ui/core/Grid';
import LockOutlinedIcon from '#material-ui/icons/LockOutlined';
import Typography from '#material-ui/core/Typography';
import Container from '#material-ui/core/Container';
import { withStyles } from '#material-ui/core/styles';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { login } from '../../actions/auth';
const styles = theme => ({
'#global': {
body: {
backgroundColor: theme.palette.common.white,
},
},
paper: {
marginTop: theme.spacing(25),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.primary.light,
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
backgroundColor: theme.palette.primary.light,
},
});
class SignIn extends React.Component {
state = {
email: '',
password: '',
};
static propTypes = {
login: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool,
};
onSubmit = (e) => {
e.preventDefault();
this.props.login(this.state.email, this.state.password);
};
onChange = (e) => this.setState({ [e.target.name]: e.target.value });
render() {
const { classes } = this.props;
const { email, password } = this.state;
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} onSubmit={this.onSubmit}>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
onChange={this.onChange}
value={email}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
onChange={this.onChange}
value={password}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign In
</Button>
<Grid container>
</Grid>
</form>
</div>
</Container>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isAuthenticated,
});
export default (withStyles(styles)(SignIn));
authreducer.js
import {
USER_LOADED,
USER_LOADING,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT_SUCCESS,
REGISTER_SUCCESS,
REGISTER_FAIL,
} from '../actions/types';
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: null,
isLoading: false,
user: null,
};
export default function (state = initialState, action) {
switch (action.type) {
case USER_LOADING:
return {
...state,
isLoading: true,
};
case USER_LOADED:
return {
...state,
isAuthenticated: true,
isLoading: false,
user: action.payload,
};
case LOGIN_SUCCESS:
case REGISTER_SUCCESS:
localStorage.setItem('token', action.payload.token);
return {
...state,
...action.payload,
isAuthenticated: true,
isLoading: false,
};
case AUTH_ERROR:
case LOGIN_FAIL:
case LOGOUT_SUCCESS:
case REGISTER_FAIL:
localStorage.removeItem('token');
return {
...state,
token: null,
user: null,
isAuthenticated: false,
isLoading: false,
};
default:
return state;
}
}
authactions.js
import axios from 'axios';
import {
USER_LOADED,
USER_LOADING,
LOGIN_SUCCESS,
LOGOUT_SUCCESS,
REGISTER_SUCCESS,
} from './types';
// CHECK TOKEN & LOAD USER
export const loadUser = () => (dispatch, getState) => {
// User Loading
dispatch({ type: USER_LOADING });
axios
.get('http://localhost:8000/api/auth/user', tokenConfig(getState))
.then((res) => {
dispatch({
type: USER_LOADED,
payload: res.data,
});
})
.catch((err) => {
console.log(err)
});
};
// LOGIN USER
export const login = (email, password) => (dispatch) => {
// Headers
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const body = JSON.stringify({ email, password });
axios
.post('http://localhost:8000/api/auth/login', body, config)
.then((res) => {
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
})
.catch((err) => {
console.log(err.response.data)
});
};
// REGISTER USER
export const register = ({ username, password, email }) => (dispatch) => {
// Headers
const config = {
headers: {
'Content-Type': 'application/json',
},
};
// Request Body
const body = JSON.stringify({ username, email, password });
axios
.post('http://localhost:8000/api/auth/register', body, config)
.then((res) => {
dispatch({
type: REGISTER_SUCCESS,
payload: res.data,
});
})
.catch((err) => {
console.log(err.response.data)
});
};
// LOGOUT USER
export const logout = () => (dispatch, getState) => {
axios
.post('http://localhost:8000/api/auth/logout/', null, tokenConfig(getState))
.then((res) => {
dispatch({ type: 'CLEAR_LEADS' });
dispatch({
type: LOGOUT_SUCCESS,
});
})
.catch((err) => {
console.log(err.response.data)
});
};
// Setup config with token - helper function
export const tokenConfig = (getState) => {
// Get token from state
const token = getState().auth.token;
// Headers
const config = {
headers: {
'Content-Type': 'application/json',
},
};
// If token, add to headers config
if (token) {
config.headers['Authorization'] = `Token ${token}`;
}
return config;
};

Based on my experience, this error often occurs due to the fact that one of the packages in package.json (often it's React) is in dependencies (not in devDependencies). And this version conflicts with yours.

Related

Okta authentication, how to use the response.params?

We are trying to use Expo authentication with Okta as stated here:
https://docs.expo.dev/guides/authentication/#okta
Expo has very good documentation for lot's of stuff, but for the Okta authentication unfortunately we could not sort out how to use the library in a correct way.
Currently, with lot's of suffering (mostly because of the ambiguity in Okta's configuration pages), we came to a certain point where the following code correctly responds the code parameter. This is the exact same part from Expo documentation:
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
But unfortunately we could not find any method how we can use the parameter code to get the scope information, email, name, etc...
Can anybody guide us how we can use the object code to retrieve these data? (The Okta documentation is not clear for this either, so we are stuck.)
Edit 1:
The response has the following structure:
response: {
"type": "success",
"error": null,
"url": "http://localhost:19006/?code=fUMjE4kBX2QZXXXXXX_XXXXXXXMQ084kEPrTqDa9FTs&state=3XXXXXXXXz",
"params": {
"code": "fUMjE4kBX2QZXXXXXX_XXXXXXXMQ084kEPrTqDa9FTs",
"state": "3XXXXXXXXz"
},
"authentication": null,
"errorCode": null
}
Edit 2:
Calling exchangeCodeAsync also yields errors.
Code:
const tokenRequestParams = {
code: code,
clientId: config.okta.clientId,
redirectUri: oktaRedirectUri,
extraParams: {
code_verifier: authRequest.codeVerifier
},
}
const tokenResult = await exchangeCodeAsync(tokenRequestParams, discovery);
Error:
TokenRequest.ts:205 Uncaught (in promise) Error: Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The authorization server MAY return an HTTP 401 (Unauthorized) status code to indicate which HTTP authentication schemes are supported. If the client attempted to authenticate via the "Authorization" request header field, the authorization server MUST respond with an HTTP 401 (Unauthorized) status code and include the "WWW-Authenticate" response header field matching the authentication scheme used by the client.
More info: Client authentication failed. Either the client or the client credentials are invalid.
at AccessTokenRequest.<anonymous> (TokenRequest.ts:205:1)
at Generator.next (<anonymous>)
at asyncGeneratorStep (asyncToGenerator.js:3:1)
at _next (asyncToGenerator.js:22:1)
PS: I asked the same question to the Expo forums also here. If we can solve there, I plan to reflect to here also for wider audience. (The method can be related with Okta rather than Expo itself.)
there are two ways to use Okta in React Native
1. By Restful APIs, using fetch/Axios
2. By Using Native SDK
By Restful APIs, using fetch/Axios
here is the full code of okta using restful
import React, { useState } from "react";
import {
ScrollView,
StyleSheet,
Text,
View,
TouchableOpacity,
Platform,
} from "react-native";
import {
useAutoDiscovery,
useAuthRequest,
makeRedirectUri,
exchangeCodeAsync,
} from "expo-auth-session";
import { maybeCompleteAuthSession } from "expo-web-browser";
import axios from "axios";
const oktaConfig = {
okta_issuer_url: "",
okta_client_id: "",
okta_callback_url: "com.okta.<OKTA_DOMAIN>:/callback",
};
export default App = (props) => {
const useProxy = true;
if (Platform.OS === "web") {
maybeCompleteAuthSession();
}
const discovery = useAutoDiscovery(oktaConfig.okta_issuer_url);
// When promptAsync is invoked we will get back an Auth Code
// This code can be exchanged for an Access/ID token as well as
// User Info by making calls to the respective endpoints
const [authRequest, response, promptAsync] = useAuthRequest(
{
clientId: oktaConfig.okta_client_id,
scopes: ["openid", "profile"],
redirectUri: makeRedirectUri({
native: oktaConfig.okta_callback_url,
useProxy,
}),
},
discovery
);
async function oktaCognitoLogin() {
const loginResult = await promptAsync({ useProxy });
ExchangeForToken(loginResult, authRequest, discovery);
}
return (
<View style={styles.container}>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.equalSizeButtons}
onPress={() => oktaCognitoLogin()}
>
<Text style={styles.buttonText}>Okta Login</Text>
</TouchableOpacity>
</View>
<ScrollView>
{response && <Text>{JSON.stringify(response, null, 2)}</Text>}
</ScrollView>
</View>
);
};
this is how, we can get exchange token and then get user info by using restful api
//After getting the Auth Code we need to exchange it for credentials
async function ExchangeForToken(response, authRequest, discovery) {
// React hooks must be used within functions
const useProxy = true;
const expoRedirectURI = makeRedirectUri({
native: oktaConfig.okta_callback_url,
useProxy,
})
const tokenRequestParams = {
code: response.params.code,
clientId: oktaConfig.okta_client_id,
redirectUri: expoRedirectURI,
extraParams: {
code_verifier: authRequest.codeVerifier
},
}
const tokenResult = await exchangeCodeAsync(
tokenRequestParams,
discovery
)
const creds = ExchangeForUser(tokenResult)
const finalAuthResult = {
token_res : tokenResult,
user_creds : creds
}
console.log("Final Result: ", finalAuthResult)
}
this is how we can get user info by using restful api
async function ExchangeForUser(tokenResult) {
const accessToken = tokenResult.accessToken;
const idToken = tokenResult.idToken;
//make an HTTP direct call to the Okta User Info endpoint of our domain
const usersRequest = `${oktaConfig.okta_issuer_url}/v1/userinfo`
const userPromise = await axios.get(usersRequest, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
console.log(userPromise, "user Info");
}
const styles = StyleSheet.create({
container: {
margin: 10,
marginTop: 20,
},
buttonContainer: {
flexDirection: "row",
alignItems: "center",
margin: 5,
},
equalSizeButtons: {
width: "50%",
backgroundColor: "#023788",
borderColor: "#6df1d8",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
padding: 9,
borderWidth: 1,
shadowColor: "#6df1d8",
shadowOpacity: 8,
shadowRadius: 3,
shadowOffset: {
height: 0,
width: 0,
},
},
buttonText: {
color: "#ffffff",
fontSize: 16,
},
});
Reference Code
By Using Native SDK
for native SDK, you can use okta-react-native package like this
Login Screen
import React from 'react';
import {
Alert,
Button,
StyleSheet,
TextInput,
View,
ActivityIndicator
} from 'react-native';
import {
signIn,
introspectIdToken
} from '#okta/okta-react-native';
export default class CustomLogin extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
username: '',
password: '',
};
}
async componentDidMount() {
}
signInCustom = () => {
this.setState({ isLoading: true });
signIn({ username: this.state.username, password: this.state.password })
.then(() => {
introspectIdToken()
.then(idToken => {
this.props.navigation.navigate('ProfilePage', { idToken: idToken, isBrowserScenario: false });
}).finally(() => {
this.setState({
isLoading: false,
username: '',
password: '',
});
});
})
.catch(error => {
// For some reason the app crashes when only one button exist (only with loaded bundle, debug is OK) 🤦‍♂️
Alert.alert(
"Error",
error.message,
[
{
text: "Cancel",
onPress: () => console.log("Cancel Pressed"),
style: "cancel"
},
{ text: "OK", onPress: () => console.log("OK Pressed") }
]
);
this.setState({
isLoading: false
});
});
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder='Username'
onChangeText={input => this.setState({ username: input })}
testID="username_input"
/>
<TextInput
style={styles.input}
placeholder='Password'
onChangeText={input => this.setState({ password: input })}
testID="password_input"
/>
<Button
onPress={this.signInCustom}
title="Sign in"
testID='sign_in_button'
/>
<View style={styles.flexible}></View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
input: {
height: 40,
width: '80%',
margin: 12,
borderWidth: 1,
padding: 10,
},
flexible: {
flex: 1,
}
});
Profile Screen
import React from 'react';
import {
Text,
Button,
StyleSheet,
TextInput,
View,
} from 'react-native';
import {
signOut,
revokeAccessToken,
revokeIdToken,
clearTokens,
} from '#okta/okta-react-native';
export default class ProfilePage extends React.Component {
constructor(props) {
super(props);
this.state = {
idToken: props.route.params.idToken,
isBrowserScenario: props.route.params.isBrowserScenario
};
}
logout = () => {
if (this.state.isBrowserScenario == true) {
signOut().then(() => {
this.props.navigation.popToTop();
}).catch(error => {
console.log(error);
});
}
Promise.all([revokeAccessToken(), revokeIdToken(), clearTokens()])
.then(() => {
this.props.navigation.popToTop();
}).catch(error => {
console.log(error);
});
}
render() {
return (
<View style={styles.container}>
<Text testID="welcome_text">Welcome back, {this.state.idToken.preferred_username}!</Text>
<Button
onPress={this.logout}
title="Logout"
testID="logout_button"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Reference Code

How to mock AWS Amplify in Jest?

I am creating an application with Vue and using Vue Test Utils and Jest as a unit testing framework. However, I encountered an issue in testing the scenario when it should show invalid credentials on failed login. I was wondering how to mock the Auth of AWS Amplify. I am not quite sure if I am doing the testing right because I am new to unit testing in frontend.
Login.vue:
import { Auth } from 'aws-amplify'
import { required } from 'vuelidate/lib/validators'
export default {
name: 'loginComponent',
data() {
return {
form: {
email: null,
password: null,
},
authErrMsg: '',
isShowAuthErr: false,
isLoading: false,
}
},
validations: {
form: {
email: { required },
password: { required }
}
},
methods: {
validateState(name) {
const { $dirty, $error } = this.$v.form[name];
return $dirty ? !$error : null;
},
onSubmit() {
this.$v.$touch();
if (this.$v.$invalid) {
return
}
this.isLoading = true
this.isShowAuthErr = false
const { email, password } = this.form
Auth.signIn(email, password).then(() => {
this.isLoading = false
this.$store.dispatch('ACTION_SET_LOGGEDIN_STATUS', true)
this.$router.push({ name: 'home' })
}).catch(() => {
this.isLoading = false
this.authErrMsg = 'Invalid login credentials'
this.$store.dispatch('ACTION_SET_LOGGEDIN_STATUS', false)
this.isShowAuthErr = true
})
}
}
}
Login.spec.js:
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import Login from '#/components/Login'
import BootstrapVue from 'bootstrap-vue'
import Vuelidate from 'vuelidate'
import Vuex from 'vuex'
import Auth from '#aws-amplify/auth'
import flushPromises from 'flush-promises'
const localVue = createLocalVue()
localVue.use(BootstrapVue)
localVue.use(Vuelidate)
localVue.use(Vuex)
localVue.use(Auth)
let wrapper
beforeEach(() => {
wrapper = mount(Login, { localVue,
form: {
email: null,
password: null,
}
})
})
describe('Login', () => {
it('should error message on failed login', async () => {
wrapper.find('input[name="email"]').setValue('email#gmail.com')
wrapper.find('input[name="password"]').setValue('123ABC')
wrapper.find("form").trigger("submit.prevent")
await flushPromises()
Auth.signIn = jest.fn().mockImplementation(() => {
throw new Error('Incorrect username or password.')
});
expect(Auth.signIn()).rejects.toThrow()
})
})
Error I got:
TypeError: Cannot read property 'clientMetadata' of undefined

How to use react semantic ui Dropdown to show number of pages and selected page on right side?

I am new to react. I am following https://www.truecodex.com/course/react-js this example but in this example, it shows all the customers. I want to add a dropdown with no. of pages and on-page selection changes the values in the table.
I think, I should take one more customer{} and add 1-4customer 2-next4customer and then on dropdown selection on change change the table values
Here is my Customer view class where I am fetching all customers.
import React from 'react';
import { Table,Icon, Button } from 'semantic-ui-react';
export default class CustomerView extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
deleteTitle: "customer",
isLoaded: false,
formClose: false,
singleCustomer: [],
users: []
}
}
//fetch data
componentDidMount() {
const customerApi = 'https://localhost:44387/api/Customers';
const myHeader = new Headers();
myHeader.append('Content-type', 'application/json');
myHeader.append('Accept', 'application/json');
myHeader.append('Origin', 'https://localhost:44387');
const options = {
method: 'GET',
myHeader
};
fetch(customerApi, options)
.then(res => res.json())
.then(
(result) => {
this.setState({
users: result,
isLoaded: true
});
},
(error) => {
this.setState({
isLoaded: false,
error
});
}
)
}
//Delete Customer
onDeleteCustomer = customerId => {
const { users } = this.state;
this.setState({
users: users.filter(customer => customer.customerId !== customerId)
});
const customerApi = 'https://localhost:44387/api/Customers/' + customerId;
const myHeader = new Headers({
'Accept': 'application/json',
'Content-type': 'application/json; charset=utf-8'
});
fetch(customerApi, {
method: 'DELETE',
headers: myHeader
})
.then(res => res.json())
.then(
(result) => {
this.setState({
})
}, (error) => {
this.setState({ error });
}
)
}
render() {
const { users } = this.state;
return (
<div>
<Button color='blue' onClick={() => this.props.onCreate()}>New Customer</Button>
<br/>
<br/>
<Table celled textAlign='center'>
<Table.Header>
<Table.Row>
<Table.HeaderCell>ID</Table.HeaderCell>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Address</Table.HeaderCell>
<Table.HeaderCell>Action</Table.HeaderCell>
<Table.HeaderCell>Action</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body >
{
users.map(user => (
<Table.Row key={user.customerId}>
<Table.Cell>{user.customerId}</Table.Cell>
<Table.Cell>{user.name}</Table.Cell>
<Table.Cell>{user.address}</Table.Cell>
<Table.Cell>
<Button color='yellow' icon labelPosition='right'
onClick={() => this.props.onEditCustomer(user.customerId)}>
<Icon name='edit outline'/>
Edit</Button>
</Table.Cell>
<Table.Cell>
<Button color='red' icon labelPosition='right'
onClick={() => this.props.onDeleteClick(user.customerId)}>
<Icon name='trash alternate'/>
Delete</Button>
</Table.Cell>
</Table.Row>
))
}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan='5'>
No of Pages
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
</div>
)
}
}

Keep user session logged in when page refreshed in vue js

I'm create user login page in vue js and consuming data from django with axios. I have utilized jwt to create token session in client-side
The problem is the session is not saved when the page is refreshed. I have frustated because it. This is my source code :
In '../src/store/modules/auth.js'
import Vue from 'vue'
import Axios from 'axios'
import 'es6-promise/auto'
// In order that Axios work nice with Django CSRF
Axios.defaults.xsrfCookieName = 'csrftoken'
Axios.defaults.xsrfHeaderName = 'X-CSRFToken'
const state = {
authUser: {},
users: [],
isAuthenticated: false,
jwt: localStorage.getItem('token'),
endpoints: {
obtainJWT: 'http://127.0.0.1:8000/api/auth/obtain_token/',
refreshJWT: 'http://127.0.0.1:8000/api/auth/refresh_token/',
baseUrl: 'http://127.0.0.1:8000/api/auth/',
register: 'http://127.0.0.1:8000/signup/'
}
}
const mutations = {
setAuthUser: (state, {
authUser,
isAuthenticated
}) => {
Vue.set(state, 'authUser', authUser)
Vue.set(state, 'isAuthenticated', isAuthenticated)
},
updateToken: (state, newToken) => {
localStorage.setItem('token', newToken);
state.jwt = newToken;
},
removeToken: (state) => {
localStorage.removeItem('token');
state.jwt = null;
},
}
const actions = {
refreshToken(){
const payload = {
token: this.state.jwt
}
Axios.post(state.endpoints.refreshJWT, payload)
.then((response)=>{
this.commit('updateToken', response.data.token)
})
.catch((error)=>{
console.log(error)
})
}
}
export default {
state,
mutations,
actions,
}
In '../src/store/index.js'
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import auth from './modules/auth'
Vue.use(Vuex)
// Make Axios play nice with Django CSRF
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
export default new Vuex.Store({
modules: {
auth
},
})
In '../src/components/login.vue'
<template>
<div class="login">
<form>
<label for="username">Username</label>
<input
type="text"
name="username"
v-model="username"
/><br>
<label for="password">Password</label>
<input
type="password"
name="password"
v-model="password"
/><br>
<input
type="button"
#click="login()"
value="Login"
/>
</form>
</template>
<script>
import axios from 'axios'
/* eslint-disable */
export default {
name: 'Login',
data(){
return {
username: '',
password: ''
}
},
methods: {
login(){
const payload = {
username: this.username,
password: this.password
}
axios.post(this.$store.state.auth.endpoints.obtainJWT, payload)
.then((response) => {
this.$store.commit('updateToken', response.data.token)
this.$store.commit('setAuthUser',
{
authUser: response.data,
isAuthenticated: true
}
)
this.$router.push({path: 'dashboard-user/id/list-vendor'})
})
.catch((error) => {
//NOTE: erase this when production
console.log(error);
console.debug(error);
console.dir(error);
alert("The username or password is incorrect");
})
}
}
}
</script>
In 'main.js'
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
import 'tachyons'
import routes from './routes'
import './styles.css'
import store from '#/store'
Vue.config.productionTip = false
Vue.use(VueRouter)
import '#/assets/fonts/all.css';
const router = new VueRouter({
mode: 'history',
routes
})
router.beforeEach((to, from, next) => {
// to and from are both route objects. must call `next`.
if(to.fullPath === '/dashboard-user/id/list-vendor') {
if(!store.state.jwt) {
next('/login')
}
}
if(to.fullPath === '/login') {
if(store.state.jwt) {
next('/dashboard-user/id/list-vendor')
}
}
next();
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Since use register auth as a module, you should use store.state.auth.jwt instead of store.state.jwt
router.beforeEach((to, from, next) => {
// to and from are both route objects. must call `next`.
if(to.fullPath === '/dashboard-user/id/list-vendor') {
if(!store.state.auth.jwt) {
next('/login')
}
}
if(to.fullPath === '/login') {
if(store.state.auth.jwt) {
next('/dashboard-user/id/list-vendor')
}
}
next();
})

Vue.js w vuex : mocked action not executed

I am trying to test the following App.vue component when a click event is fired on the logout vue-router link...
App.vue
<template>
<div id="app">
<header id="header">
<nav>
<ul class="navigation">
<li id="home"><router-link :to="{ name: 'home' }">Home</router-link></li>
<li id="login" v-if="!isAuthenticated"><router-link :to="{ name: 'login' }">Login</router-link></li>
<li id="shoppinglists" v-if="isAuthenticated"><router-link :to="{ name: 'shoppinglists' }" >Shopping Lists</router-link></li>
<li id="logout" v-if="isAuthenticated">Logout</li>
</ul>
</nav>
</header><!-- /#header -->
<section id="page">
<router-view></router-view>
</section><!-- /#page -->
</div>
</template>
<script>
import store from '#/vuex/store'
import router from '#/router/index'
import { mapGetters } from 'vuex'
export default {
name: 'app',
computed: {
...mapGetters({ isAuthenticated: 'isAuthenticated' })
},
methods: {
logout () {
this. $store.dispatch('logout')
.then(() => {
window.localStorage.removeItem('vue-authenticate.vueauth_token')
this/$router.push({ name: 'home' })
})
}
},
store,
router
}
</script>
To test the logout click, I preset the isAuthenticated state to true, so the logout router link show up and I trigger the click event on it.
LOG: 'navigation: ', <ul class="navigation"><li id="home">
Home</li> <!---->
<li id="shoppinglists">Shopping Lists
</li> <li id="logout">Logout</li></ul>
I expect the action logout to have been called .. but it's not ... why ?
App.spec.js
import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import App from '#/App'
import router from '#/router/index'
import { mount } from 'avoriaz'
import sinon from 'sinon'
Vue.use(Vuex)
Vue.use(VueRouter)
describe('App.vue', () => {
let actions
let getters
let store
beforeEach(() => {
getters = {
isAuthenticated: (state) => {
return state.isAuthenticated
}
}
actions = {
logout: sinon.stub().returns(Promise.resolve(true))
}
store = new Vuex.Store({
getters,
actions,
state: {
isAuthenticated: true,
currentUserId: ''
}
})
router
})
it('calls logout method', () => {
const wrapper = mount(App, { router, store })
console.log('navigation: ', wrapper.find('ul.navigation')[0].element)
const logoutLink = wrapper.find('#logout a')[0]
logoutLink.trigger('click')
expect(actions.logout.calledOnce).to.equal(true)
})
})
vuex/actions.js
import { IS_AUTHENTICATED, CURRENT_USER_ID } from './mutation_types'
import getters from './getters'
export default {
logout: ({commit}) => {
commit(IS_AUTHENTICATED, { isAuthenticated: false })
commit(CURRENT_USER_ID, { currentUserId: '' })
return true
}
}
vuex/mutations.js
import * as types from './mutation_types'
import getters from './getters'
export default {
[types.IS_AUTHENTICATED] (state, payload) {
state.isAuthenticated = payload.isAuthenticated
},
[types.CURRENT_USER_ID] (state, payload) {
state.currentUserId = payload.currentUserId
}
}
vuex/getters.js
export default {
isAuthenticated: (state) => {
return state.isAuthenticated
}
}
Finally , I found a way to test it :
import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import App from '#/App'
import router from '#/router/index'
import { mount } from 'avoriaz'
import sinon from 'sinon'
Vue.use(Vuex)
Vue.use(VueRouter)
describe('App.vue', () => {
let actions
let getters
let store
let sandbox
let routerPush
beforeEach(() => {
sandbox = sinon.sandbox.create()
getters = {
isAuthenticated: (state) => {
return state.isAuthenticated
}
}
actions = {
logout: sandbox.stub().returns(Promise.resolve(true))
}
store = new Vuex.Store({
getters,
state: {
isAuthenticated: true,
currentUserId: ''
},
actions
})
router
})
afterEach(() => {
sandbox.restore()
})
it('calls logout method', (done) => {
const wrapper = mount(App, { store, router })
routerPush = sandbox.spy(wrapper.vm.$router, 'push')
const logoutLink = wrapper.find('#logout a')[0]
logoutLink.trigger('click')
wrapper.vm.$nextTick(() => {
expect(actions.logout.calledOnce).to.equal(true)
actions.logout().then(() => {
expect(routerPush).to.have.been.calledWith('/home')
})
done()
})
})
})