I have a login component which makes an API call with email and password to Ruby API and sets the isLoggedIn = true if the credentials are correct. When this is true, the navbar shows "logout" link and shows signin/signout when false. However, the issue is that it only works fine when page is not reloaded since I am not storing the session anywhere.
I am trying to set login state in local storage when the login is successful. It works fine in the login component, but doesn't when I try to read the local storage value in the other component. I assume this is because I am not storing it in shared state. Is it possible to store the value in state?
Login.js
axios
.post(apiUrl, {
user: {
email: email,
password: password,
},
})
.then((response) => {
if ((response.statusText = "ok")) {
setLoginStatus({ props.loginState.isLoggedIn: true });
localStorage.setItem('props.loginState.isLoggedIn', true);
console.log(response)
history.push("/");
}
})
App.js
let data = {
isLoggedIn: false,
user: {},
setSession:null
};
const [loginState, setLoginStatus] = useState(data);
const rememberMe = localStorage.getItem('loginState.isLoggedIn')
When I use console log the below either in App.js or Nav.js the value is null always.
localStorage.getItem('props.loginState.isLoggedIn');
The localStorage items as are saved as key value pairs. This is a string 'props.loginState.isLoggedIn', and will be access the same way from any component. Since you saved it with that key name it can only be accessed with that key name from any component.
Related
In a React project, I have pay() method. A wallet balance is shown with certain coins. When pay() is clicked data is updated in purchase data. Coins data is stored in sessionStorage, but, after refresh of page data is missing.
Payment method
const onPayment = async (coins, _id) => {
try {
await fetch(`${config.baseUrl}customers/purchaseproduct?content_id=${_id}&coins=${coins}&v=1.0`, {
method:'POST',
headers: {
'Content-Type': 'application/json',
'ApiKey': config.apiKey,
'Authorization':sessionStorage.getItem('tokenNew'),
'platform':'web'
},
}).then((data) => data.json()).then((data2) => {
const balanceUpdated = data2.data.purchase.coins_after_txn
console.log('new balance', balanceUpdated) //new balance: 49990
{/* Here coins data is updated only on click of pay() method later on page refresh sessionStorage
value is empty */}
sessionStorage.setItem('walletData', balanceUpdated)
})
}
catch({error}) {
toast.error(error)
}
}
As you can see 'coins' are the price of product whereas '_id' is the id of specific product in onPayment method. walletData is updated only on payment and when refreshed the page, data is empty.
Take an example, A product price is $90 when clicked on pay() method $90 is cut from wallet 'balanceUpdated' and then passed on to sessionStorage in 'walletData' which is shown at that instance i.e 4900 but when page is refreshed 'walletData' is empty.
How to store the new updated value in sessionStorage and will remain it even after refresh?
LoginNow file
const onSubmit = async (data) => {
try {
let newData = await fetch('customers/auth/login', {
method:'POST',
headers: {
'Content-Type': 'application/json',
'ApiKey': config.apiKey,
'Platform': 'web',
},
body: JSON.stringify(data)
});
newData = await newData.json();
if(newData.error == true) {
toast.error(newData.error_messages[0])
} else {
const tokenData = newData.data.token
const walletData = newData.data.coinsxp.coins
sessionStorage.setItem('tokenNew', tokenData);
sessionStorage.setItem('walletData', walletData);
}
}
catch({error}) {
toast.error(error)
}
}
The data is probably there in the sessionStorage, you just have to retrieve it and write it in memory, on startup.
Probably you want something like this (or the other way around, depending on which value you want to have precedence):
const walletData = newData.data.coinsxp.coins || sessionStorage.getItem('walletData');
Putting data in sessionstorage is not equivalent to making it persistent. Putting data in sessionStorage means that it will just stay in session storage, it doesn't mean that your app data will be updated automatically based on it. This is why every time you call setItem, you should also call getItem - otherwise you are just stashing stuff somewhere, without accessing it.
I want to change the credentials of state in a separate Javascript file which is a function() called LoginConfirmation.js. The credentials are in a class called login.js:
state = {
credentials: {
username: '',
password: '',
collapseID: '',
logged_in: true,
usernameChecked: LoginConfirmation.usernameChecked,
passwordChecked: LoginConfirmation.passwordChecked
},
isLoginView: true,
userAccount: []
}
I am trying to change the values of usernameChecked and passwordChecked inside LoginConfirmation.js without using any HTML onChange or onClick etc.
I am mapping through my database using:
{props.userAccount.map(userAccount => { ...
And then setting:
let usernameChecked = userAccount.username
Is it possible to set this usernameChecked value to usernameChecked in the credentials in login.js
It's hard to say without seeing the entire set up.
But I guess you could add a public method to the class in login.js that makes the changes you want, something like (assuming it's a React class component, not a js class):
updateUsernameChecked = (newValue) => {
this.setState(state => state.credentials.usernameChecked = newValue);
}
then pass the method to LoginConfirmation.js and call it with the new value.
Hope this helps.
Basic scenario -
I have an input element for e-mail address. On click of save, the e-mail is saved into the database. When I refresh, I want the saved value from database to show up as the initial value of the input. After that, I want to control the input's value through regular component state.
The problem I am having is setting the initial state value from props.
I thought I can set the state from props in CDU by making a prevProps to currentProps check.
Actual scenario -
The props I am trying to compare is an array of objects. So CDU shallow comparison won't help. How do I go about doing this?
Please note that I am not using or do not want to use any form library as I just have 3 input fields in my applications.
Thanks
You need to get data from the database on the component mount and set the state of your email. Like this below example (I am doing it on edit user) -
componentDidMount() {
var headers = {
"Content-Type": "application/json",
Authorization: `Token ${authToken}`
};
axios
.get(`${serverURL}users/${this.props.match.params.id}`, {
headers: headers
})
.then(res => {
//console.log(res.data);
let userdetails = res.data;
this.setState({
first_name: userdetails.first_name,
last_name: userdetails.last_name,
email: userdetails.email,
});
})
.catch(function(error) {
console.log(error);
});
}
Possible solution for this:
1. Create local state for email e.g
state = {
email: '';
}
dispatch the action in componentDidMount which will fetch the data and store it in redux state
use static getDerivedStateFromProps(props, state) to get updated values from props (ref. link :https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops) like
static getDerivedStateFromProps(nextProps, prevState) {
// do things with nextProps.email and prevState.email
return {
email: nextProps.someProp,
};
}
I'm using a very basic CASL implementation. Unfortunately, the docs aren't that detailed. I have the following code (basically copy-pasted from the docs).
import { abilitiesPlugin } from '#casl/vue'
import defineAbilitiesFor from './ability'
const ability = defineAbilitiesFor({name: 'guest'})
Vue.use(abilitiesPlugin, ability )
where defineAbilitiesFor is defined as (in ./ability.js)
import { AbilityBuilder } from '#casl/ability'
function defineAbilitiesFor(user) {
return AbilityBuilder.define((can, cannot) => {
can(['read'], 'foo', { username: user.name})
})
}
I know it's possible to update the rules/conditions (i.e. ability.update([])). But how do I update the user's information after initializing CASL? (e.g. after the user has logged in
CASL has nothing to do with user. What eventually it cares is only user's permissions. So, after login you need to update rules, basically use ability.update(myRules)
In your Login component, after login request to API (or after you receive information about currently logged in user), you need to call ability.update(defineRulesFor(user)).
ability can be just an empty Ability instance. For example:
const ability = new Ability([])
function defineRulesFor(user) {
const { can, rules } = AbilityBuilder.extract()
can(['read'], 'foo', { username: user.name })
return rules
}
// Later after login request to API (or after you receive information about currently logged in user)
login() {
return http.post('/login')
.then((response) => {
ability.update(defineRulesFor(response.user))
// after that Ability instance contains rules for returned user
})
}
Quick story of my problem:
Absolutely no data is stored in my vuex state when the page loads
If the user is logged in(or has info stored in window.localStorage and therefore gets auto logged in) my vuex store retrieves all the info from a socket that requires authentication.
Then the user logs out, But my vuex state save still retains all its data
This would be a security issue as not logged in people(or hackers) on a public pc could view what the state was before the user logged out.
I have seen How to clear state in vuex store?
But I feel that this is a hack and should be avoided.
My current solution is just to refresh the page using location.reload();
Is there a better way to prevent this data leak?
All objects stored in Vue act as an observable. So if the reference of a value is changed/mutated it triggers the actual value to be changed too.
So, In order to reset the state the initial store modules has to be copied as a value.
On logging out of a user, the same value has to be assigned for each module as a copy.
This can be achieved as follows:
// store.js
// Initial store with modules as an object
export const initialStoreModules = {
user,
recruitment,
};
export default new Vuex.Store({
/**
* Assign the modules to the store
* using lodash deepClone to avoid changing the initial store module values
*/
modules: _.cloneDeep(initialStoreModules),
mutations: {
// reset default state modules by looping around the initialStoreModules
resetState(state) {
_.forOwn(initialStoreModules, (value, key) => {
state[key] = _.cloneDeep(value.state);
});
},
}
});
Then call commit("resetState"); when the user logs out.
Normal Approach
If user logs in, then you can add few boolean flags to ensure that user has been loggedin/loggedout.
So initial approach would be -
this.$store.commit('insertToken', {realtoken, isLoggedIn: true})
In vuex than,
insertToken (state, payload) {
state.token = payload.realtoken
state.isLoggedIn = payload.isLoggedIn
localStorage.setItem('token', payload.realtoken)
}
And when user logs out you should set all flags to false,
In component -
logout () {
this.$store.commit('logOut')
this.$router.replace('/login')
}
and in vuex,
logOut (state, payload) {
state.token = null
state.isLoggedIn = false
localStorage.setItem('token', null)
},
So by means of isLoggedIn and token you can tell router where to navigate by using term called Navigation Guards
Example -
const checkToken = () => {
if ((localStorage.getItem('token') == null) ||
(localStorage.getItem('token') == undefined)) {
return false
} else {
return true
}
}
// Navigation guards
if (to.path === '/') {
if (checkToken()) {
next()
} else {
router.push('/login')
}
}
This is the way I use when authentication is done by means of using token as part of interacting with Vuex.
This extension does a nice job
https://www.npmjs.com/package/vuex-extensions
With it installed I can just call reset in the Vuex Logout Action
logout(context) {
// do the logout stuff, such as
context.commit("setUser", {});
// On logout, clear all State, using vuex-extensions
this.reset();
// if using router, change to login page
router.replace("/login");
}
This might be late but I found window.localStorage.removeItem('vuex') useful. Thanks to Thomas von Deyen, https://github.com/championswimmer/vuex-persist/issues/52#issuecomment-413913598