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.
Related
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
}
};
};
first of all i want to apologize for my title. I just dont know how to describe my problem.
I am trying to get a bad response from my server and when I try to display that my object is undefined
I have a base query methods here:
export const accountSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
login: builder.mutation({
query: credentials => ({
url: 'account/login',
method: 'POST',
body: { ...credentials },
})
}),
register: builder.mutation({
query: credentials => ({
url: 'account/register',
method: 'POST',
body: { ...credentials },
})
})
})
})
My handle submit on register page ->
const [register, { isLoading, isError }] = useRegisterMutation();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const result = await register({ name, nickName, email, password }).unwrap();
setRegisterResponse(result);
} catch (error) {
setRegisterResponse(error);
}
}
And my logic to show it. When i use console.log(registerResponse) it returnes two logs in console - first object is empty, second object with properties ->
{
isError &&
<h2>
Ooops.. something went wrong:
{
console.log(registerRespnse)
}
</h2>
}
Error in google console
You shouldn't need to call a setRegisterResponse state setter, because that response will just be available for you:
// see data and error here
const [register, { isLoading, isError, data, error }] = useRegisterMutation();
As why it logs undefined once: first the query finishes with an error (which will rerender the component and already fill error I showed above and set isError) and then the Promise resolves and your custom code sets your response local state, which causes a second rerender (and only on the second render, response is set)
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
...
}
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
I'm trying to display a toast when a async request is finished.
I've implemented this process:
Single File Component calls updateUserProfile() actions in my VueX store
updateUserProfile() actions makes a outgoing HTTP request on a server using Axios
When succeeded, I use a mutation to update the user profile in my store and i would like to show a toast from my single file component.
Problem is that the response object is always undefined in my component. Where is my mistake ?
Error :
profile.vue?a62a:328 Uncaught (in promise) TypeError: Cannot read
property 'data' of undefined
at eval (profile.vue?a62a:328)
Store:
/*
* Action used to fetch user data from backend
*/
updateUserProfile ({commit, state}, userData) {
// Inform VueX that we are currently loading something. Loading spinner will be displayed.
commit('SET_IS_LOADING', true);
axiosBackend.put('/user/profile', userData, { headers: { Authorization: state.authString } } ).then(res => {
console.log('PUT /user/profile', res);
// Set user Data in VueX Auth store
commit('SET_USER_DATA', {
user: res.data.data
});
// Reset is Loading
commit('SET_IS_LOADING', false);
return res.data;
})
.catch(error => {
// Reset isLoading
commit('SET_IS_LOADING', false);
});
}
Component:
methods: {
// mix the getters into computed with object spread operator
...mapActions([
'updateUserProfile'
]),
// Function called when user click on the "Save changes" btn
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname,
}
this.updateUserProfile(userData).then( (response) => {
console.log('COMPONENT', response);
if (response.data.status === 200) {
toastr.success("Your profile has been successfully updated.");
}
});
}
}
Well,
It would be better idea if You trigger the toast from the Vuex store itself as mentioned below.
callAddToCart: ({ commit }, payload) => {
axiosBackend.put('/user/profile', userData, { headers: { Authorization:
state.authString }}).then(response => {
commit("setLoading", false, { root: true });
payload.cartKey = response.key;
commit("setNotification", {
type: 'success',
title: `title`,
});
commit("ADD_TO_CART", payload);
});
},
and inside mutation you can have a general notification toast and you can pass type, message and title as below.
setNotification(state, {type, message, title}) {
state.flash = {
type,
title,
message
}
}
NOTE: Do not forget to load toast element at the root level in order to display in the UI.
Here is working example
Hope this helps!