Why does RTK query response handling not work? - react-redux

I tried to use the RTK query on my login request, but I got some trouble when printing out the result. Here is my code.
authRTK.ts
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
import { loginForm, UserResponse } from "../type/type";
import { RootState } from "./store";
export const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: 'http://localhost:3001',
prepareHeaders: (headers, { getState }) => {
// By default, if we have a token in the store, let's use that for authenticated requests
const token = (getState() as RootState).auth.token;
if (token) {
headers.set("authentication", `Bearer ${token}`);
}
return headers;
}
}),
endpoints: (build) => ({
login: build.mutation<UserResponse, loginForm>({
query: (credentials) => ({
url: "login",
method: "POST",
body: credentials
}),
transformResponse: (response: { data: UserResponse }) => {
return response.data
},
}),
protected: build.mutation({
query: () => "protected"
})
})
});
export const { useLoginMutation,useProtectedMutation } = api;
store.ts
import { configureStore } from '#reduxjs/toolkit'
import cartReducer from './cartRedux';
import userReducer from './authRedux';
import { api } from './authRTK';
export const store = configureStore({
reducer:{
cart: cartReducer,
auth: userReducer,
[api.reducerPath]: api.reducer,
},
middleware: (gDM) => gDM().concat(api.middleware),//getDefaultMiddleware
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
Login.tsx
const Login = () => {
const [login, { isLoading,error,isError}] = useLoginMutation();
const [showPassword,setShowPassword] = useState<boolean>(false);
return (
<Container>
<Wrapper>
{/* <button onClick={()=>testCookie()}>測試一下cookie</button> */}
<Title>SIGN IN</Title>
<Formik
initialValues={{ email: "", password: "" }}
validationSchema={Yup.object({
password: Yup.string()
.min(8, 'Must be 8 characters or higher')
.required(),
email: Yup.string().email('Invalid email address').required(),
})}
onSubmit = { async (values, actions) => {
try{
const result = await login(values);
if("data" in result){
console.log(result.data)
}else{
console.log((result.error as RequestError).data) ////this will printout the expected result , but I have to cast error to RequestError type to print the nested data inside , and I can't use this data else where like error above
console.log(error) //This printout undefined,mean there's no error data inside,but not supposed to happen
console.log(isError) //print out false , but supposed to be true
}
}catch(err){
console.log(err)
}
}}>
{({
errors,
values,
handleChange,
handleBlur,
handleSubmit,
validateField
}) => (
<Form onSubmit={handleSubmit}>
<InputContainer>
<Input
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
type="text"
name="email"
placeholder="Email"
data-testid="email"
/>
</InputContainer>
{errors.email && <Error data-testid="emailError">{errors.email}</Error>}
<InputContainer>
<Input
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
type={showPassword ? "text" : "password"}
name="password"
placeholder="Password"
data-testid="password"
/>
{showPassword ? <VisibilityOff onClick={()=>setShowPassword(false) }/> : <Visibility onClick={()=>setShowPassword(true) }/> }
</InputContainer>
{errors.password && <Error data-testid="passwordError">{errors.password}</Error>}
<Button
data-testid="submit"
type="submit">Submit</Button>
</Form>
)}
</Formik>
</Wrapper>
</Container>
);
};
export default Login;
So My main problems are with the login.tsx,Error didn't work as expected, and my response data have to determine if "data" is in it, even though I used transformResponse.
BTW my response type looks like below
RequestError:
{
data:string;
status:string
}

data is not the data from your response. It is the data property of the trigger function result.
trigger always returns an object in the form { data: ... } or { error: ... }.
So without your transformResult you would end up with result.data.data instead of result.data.
You can also unwrap that, to directly get the data and throw an error in the error case, but that's not the default as it might lead to uncaught promise rejection errors if you don't handle it.
async (values, actions) => {
try{
const result = await login(values).unwrap();
console.log(result.data)
} catch(err){
console.log(err)
}
}

Related

Next JS Login form not working. Back end used is springboot

Hi we have purchased a theme. In the theme they have include JWT based login but used fake db(dummy values) and local storage. I want to change it to our logic which uses JWT based authentication. Backend used is spring boot.
I could not fully understand the code here. In the docs, they mentioned to make changes in the authcontext file only. However, it didnt worked after I made changes in the handlelogin function(authcontext.js file). Also I gave the loginEndpoint as the backend API URL, but not sure what to replace in place of meEndpoint. If anybody gets any better idea, please try to help. Will it work properly if me make changes in the handleLogin function of authContext.js file alone? or do we have to make changes in the initAuth async function that is defined inside useEffect hook? Also what is the onSubmit function(the function which executes when login button is clicked) doing
please find the code in AuthContext.js file
import { createContext, useEffect, useState } from 'react'
// ** Next Import
import { useRouter } from 'next/router'
// ** Axios
import axios from 'axios'
// ** Config
import authConfig from 'src/configs/auth'
// ** Defaults
const defaultProvider = {
user: null,
loading: true,
setUser: () => null,
setLoading: () => Boolean,
isInitialized: false,
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
setIsInitialized: () => Boolean,
register: () => Promise.resolve(),
token:null,
setToken:()=>null
}
const AuthContext = createContext(defaultProvider)
const AuthProvider = ({ children }) => {
// ** States
const [user, setUser] = useState(defaultProvider.user)
const [loading, setLoading] = useState(defaultProvider.loading)
const [isInitialized, setIsInitialized] = useState(defaultProvider.isInitialized)
const [token,setToken]=useState(defaultProvider.token)
// ** Hooks
const router = useRouter()
useEffect(() => {
const initAuth = async () => {
setIsInitialized(true)
const storedToken = window.localStorage.getItem(authConfig.storageTokenKeyName)
if (storedToken) {
setLoading(true)
await axios
.get(authConfig.meEndpoint, {
headers: {
Authorization: storedToken
}
})
.then(async response => {
setLoading(false)
setUser({ ...response.data.userData })
})
} else {
setLoading(false)
}
}
initAuth()
}, [])
const handleLogin = (params, errorCallback) => {
axios
.post(authConfig.loginEndpoint, params)
.then(async res => {
window.localStorage.setItem(authConfig.storageTokenKeyName, res.data.accessToken)
})
.then(() => {
axios
.get(authConfig.meEndpoint, {
headers: {
Authorization: window.localStorage.getItem(authConfig.storageTokenKeyName)
}
})
.then(async response => {
const returnUrl = router.query.returnUrl
setUser({ ...response.data.userData })
await window.localStorage.setItem('userData', JSON.stringify(response.data.userData))
const redirectURL = returnUrl && returnUrl !== '/' ? returnUrl : '/'
router.replace(redirectURL)
})
})
.catch(err => {
if (errorCallback) errorCallback(err)
})
}
const values = {
user,
loading,
setUser,
setLoading,
isInitialized,
setIsInitialized,
login: handleLogin,
logout: handleLogout,
register: handleRegister
}
return <AuthContext.Provider value={values}>{children}</AuthContext.Provider>
}
export { AuthContext, AuthProvider }
the authConfig file as below:-
export default {
meEndpoint: '/auth/me',
loginEndpoint: 'qortex-dev-backoffice-portal.westus2.cloudapp.azure.com:8080/auth-user/login',
registerEndpoint: '/jwt/register',
storageTokenKeyName: 'accessToken'
}
the handlesubmit function executes on login button click as shown below
const onSubmit = data => {
const { email, password } = data
auth.login({ email, password }, () => {
setError('email', {
type: 'manual',
message: 'Email or Password is invalid'
})
})
}
Login form controls code as shown below
<form noValidate autoComplete='off' onSubmit={handleSubmit(onSubmit)}>
<FormControl fullWidth sx={{ mb: 4 }}>
<Controller
name='email'
control={control}
rules={{ required: true }}
render={({ field: { value, onChange, onBlur } }) => (
<TextField
autoFocus
label='Email'
value={value}
onBlur={onBlur}
onChange={onChange}
error={Boolean(errors.email)}
placeholder='admin#materio.com'
/>
)}
/>
{errors.email && <FormHelperText sx={{ color: 'error.main' }}>{errors.email.message}</FormHelperText>}
</FormControl>
<FormControl fullWidth>
<InputLabel htmlFor='auth-login-v2-password' error={Boolean(errors.password)}>
Password
</InputLabel>
<Controller
name='password'
control={control}
rules={{ required: true }}
render={({ field: { value, onChange, onBlur } }) => (
<OutlinedInput
value={value}
onBlur={onBlur}
label='Password'
onChange={onChange}
id='auth-login-v2-password'
error={Boolean(errors.password)}
type={showPassword ? 'text' : 'password'}
endAdornment={
<InputAdornment position='end'>
<IconButton
edge='end'
onMouseDown={e => e.preventDefault()}
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOutline /> : <EyeOffOutline />}
</IconButton>
</InputAdornment>
}
/>
)}
/>
{errors.password && (
<FormHelperText sx={{ color: 'error.main' }} id=''>
{errors.password.message}
</FormHelperText>
)}
</FormControl>

Uncaught TypeError: Cannot read properties of undefined (reading 'target'),

My problem is that how do i access 'handleInputChange', because i cant write 'handleInputChange' function outside the useEffect hook since it is performing a sideeffect. I would love it if someone can help me out with this situation.
1. const [values, setValue] = useState({});
const dispatch = useDispatch();
let handleInputChange
useEffect(()=>{
handleInputChange = (e) =>{
setValue(
{
values: { ...values, [e.target.name]: e.target.value }
},
() => {
dispatch({
type: "SET_FORMVALUES",
payload: values
})
}
)}
handleInputChange();
},[dispatch])
<TextField id="outlined-basic" label="First Name" variant="outlined"
name="firstName"
className='app__input'
placeholder='First Name'
type="text"
value={values['firstName']}
onChange = {handleInputChange} />
//Reducer.js
const initialState = {
formValues: {},
message: ""
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "SET_FORMVALUES":
console.log("--- Changing form data ---");
return {
...state,
formValues: action.payload
};
case "SUBMIT_FORM":
return {
...state,
message: "Form submitted!!"
};
default:
return state;
}
};
First, you don't need the core React useState hook, because you are using React Redux. This is actually creating a second state local to your component. Instead, use the centralized Redux store you've configured and React-Redux hooks. As long as you have wrapped your app in a context provider and passed your store to it, you can use useDispatch to update state and useSelector to retrieve state and subscribe to changes.
Second, you don't need useEffect, as you are just updating state.
Here's an example:
import { useDispatch, useSelector } from 'react-redux';
export default function App() {
const formValues = useSelector((state) => state.formValues);
const dispatch = useDispatch();
const handleInputChange = (name, value) => {
dispatch(
{
type: "SET_FORMVALUES",
payload: {
...formValues,
[name]: value
}
}
);
}
return (
<div className="App">
<input type="text" name="FirstName" onChange={ (e) => handleInputChange(e.target.name, e.target.value)} />
<span>{formValues["FirstName"]}</ span>
<input type="text" name="LastName" onChange={ (e) => handleInputChange(e.target.name, e.target.value)} />
<span>{formValues["LastName"]}</ span>
</div>
);
}
Much of this is probably not directly related to the error in the question title, but simplifying your code should help you debug more easily. That error may have been simply because you didn't explicitly pass the event in your onChange handler. I.e. onChange = {handleInputChange} vs. onChange = {(e) => handleInputChange(e)}

How to properly pass input values to a function using the composition api in vue?

I have an input field that contains a postcode. On submit I want to pass the postcode as an object to an axios request. I have created a CodeSandbox here: https://codesandbox.io/s/determined-beaver-8ebqc
The relevant code is:
App.vue
<template>
<div id="app">
<input v-model="postcode" type="text" placeholder="Enter Postcode">
<button #click="getAddress">Submit</button>
</div>
</template>
<script>
import useAxios from "#/composition/use-axios";
export default {
name: "App",
setup() {
const { sentData, response, fetchData } = useAxios(
"api/address/lookup-address",
"postcode",
"Failed Finding Address"
);
return {
postcode: sentData,
address: response,
getAddress: fetchData
};
}
};
</script>
use-axios.js
import { reactive, toRefs } from "#vue/composition-api";
import axios from "axios";
export default function (url, objectData, errorMessage) {
const state = reactive({
sentData: null,
response: null
});
const fetchData = async () => {
console.log("Sent Data:", state.sentData);
console.log("Response:", state.response);
console.log("URL:", url);
console.log("Object Data:", objectData);
console.log("Object:", { [objectData]: state.sentData });
console.log("Error Message:", errorMessage);
const config = { headers: { "Content-Type": "application/json" } };
try {
const res = await axios.post(url, [objectData]: state.sentData, config);
state.response = await res.data;
} catch (error) {
// Error handling stuff
}
}
return { ...toRefs(state), fetchData };
}
Converting the postcode input string to an object in this way seems very hacky. Also, this would get very messy if I needed to send multiple parameters to the axios request. Say if I want to pass { id: "1234", user: "Me" }, I would like to be able to construct that like:
sentData = { id: ${id}, user: ${user} }
But I'm not able to do this. What is the proper way to do this so that I can keep use-axios generic?
You will need to import ref, reactive and computed from the composition-api and then use them like this:
<script>
import useAxios from "#/composition/use-axios";
import { ref, reactive, computed } from "#vue/composition-api";
export default {
name: "App",
setup() {
let object = ref("");
let state = reactive({ postcode: "" });
const sentDataObject = computed(() => {
state.postcode = object;
return state;
});
const addressList = useAxios(
"api/address/lookup-address",
sentDataObject.value,
"Failed Finding Address"
);
return {
addresses: addressList.response,
postcode: object,
getAddress: addressList.fetchData
};
}
};
</script>
change use-axios.js to:
import { reactive, toRefs } from "#vue/composition-api";
import axios from "axios";
export default function (url, objectData, errorMessage) {
const state = reactive({
sentData: null,
response: null
});
const fetchData = async () => {
console.log("Sent Data:", state.sentData);
console.log("Response:", state.response);
console.log("URL:", url);
console.log("Object Data:", objectData);
console.log("Error Message:", errorMessage);
const config = { headers: { "Content-Type":
"application/json" } };
try {
const res = await axios.post(url, objectData, config);
state.response = await res.data;
} catch (error) {
// Error handling stuff
}
};
return { ...toRefs(state), fetchData };
}
See Codesandbox demo here: https://codesandbox.io/s/dawn-glade-ewzb7

Using Form.Select from React Semantic UI with React-hooks

as the question suggests, I'm using RSUI with React-Hooks in a Next.js project and I'm trying to figure out how to send a payload from a Form.Select to a graphql endpoint. I've included a lot of extra code for context but really what I'm after is to successfully set "security_type" using setValues
import React, { useState } from 'react'
import Router from 'next/router'
import { Button, Checkbox, Form, Segment, Table } from 'semantic-ui-react'
import Layout from '../../components/layout'
import Loading from '../../components/loading'
import Error from '../../components/error'
import { useFetchUser } from '../../lib/user'
import { useQuery, useMutation } from '#apollo/react-hooks';
import query from '../../graphql/project/query';
import mutation from '../../graphql/project/mutation';
const security_types = [
{ key: 'Bank Guarantee', value: 'Bank Guarantee', text: 'Bank Guarantee', name: 'Bank Guarantee' },
{ key: 'Cash Retention', value: 'Cash Retention', text: 'Cash Retention', name: 'Cash Retention' },
{ key: 'Surety Bond', value: 'Surety Bond', text: 'Surety Bond', name: 'Surety Bond' },
];
function CreateProject() {
const { loading, error, data } = useQuery(query);
const [createProject] = useMutation(mutation,
{
onCompleted(data) {
Router.replace("/create_project", "/project/" + data.createProject.project_id, { shallow: true });
}
});
const { user, user_loading } = useFetchUser()
let [form, setValues] = useState({
project_number: '',
project_name: '',
security_type: '',
});
let updateField = e => {
console.log('e: ', e)
setValues({
...form,
[e.target.name]: e.target.value
});
};
let mutationData = ''
if (loading) return <Loading />;
if (error) return <Error />;
return (
<Layout user={user} user_loading={user_loading}>
<h1>Let's create a project</h1>
{user_loading ? <>Loading...</> :
<div>
<Segment>
<Form
onSubmit={e => {
e.preventDefault();
console.log('form: ', form)
createProject({ variables: { ...form } });
form = '';
}}>
<Form.Input
fluid
label='Project Number'
name="project_number"
value={form.project_number}
placeholder="Project Number"
onChange={updateField}
/>
<Form.Input
fluid
label='Project Name'
name="project_name"
value={form.project_name}
onChange={updateField}
/>
<Form.Select
fluid
selection
label='Security Type'
options={security_types}
placeholder='Security Type'
name="security_type"
value={form.security_type}
onChange={(e, { value }) => setValues(console.log('value: ', value), { "security_type": value })}
/>
<Button>Submit</Button>
</Form>
</Segment>
</div>
}
</Layout>
);
}
export default CreateProject;
I think all my troubles relate to the onChange section so any help would be great.

React-Apollo Authentication in Next.js app

I am trying to setup authentication in an app using React-Apollo and Next.js. It is setup to return a cookie containing a jwt token after sending in the credentials, which works perfectly well. When trying to run a query for the current user, it comes back as null. If I refresh the page, the query works and I get back the current user. I did notice that the cache is updated with the correct data even though the response is null.
Component:
class Index extends Component{
constructor(props){
super(props)
this.usernameInput = ''
this.passwordInput = ''
this.state = {
error: null,
}
}
getCurrentAccount = async () => {
return await this.props.getCurrentAccount
}
sendCreds = async (e) => {
e.preventDefault()
const result = await this.props.authenticate({
variables: {
username: this.usernameInput,
password: this.passwordInput,
},
update: (store, {data: { currentAccount } } ) => {
const data = store.readQuery({
query: QUERY_CURRENT_ACCOUNT,
})
console.log('after authenitcation', data)
}
})
if (result.error) {
console.log('error on authentication', result.error)
this.setState({error: 'There was an error with our server.'})
} else if (result) {
//this.login()
}
}
login = async () => {
const data = await this.getCurrentAccount()
if (data.error) {
console.log('error on fetching account', data.error)
this.setState({error: 'There was an error with our server.'})
} else if (data.currentAccount.role) {
Router.push('/dashboard')
} else {
this.setState({error: 'The ID or password you entered is incorrect.'})
}
}
render() {
return (
<form onSubmit={this.sendCreds}>
{ this.state.error ? <FormError>{this.state.error}</FormError> : null}
<fieldset>
<InputField id="username" label="usrname" placeholder="username" pattern="^[a-zA-Z0-9_]*$" required inputValue={val=>this.usernameInput = val}/>
<InputField id="password" type="password" label="Password" placeholder="Password" required inputValue={val=>this.passwordInput = val}/>
</fieldset>
<ButtonPrimary type="submit" label="Send Credentials" width="full"/>
<ButtonPrimary label="Get Current User to Login" width="full" clickAction={()=>this.login()}/>
<footer>
<Link href="forgot-username">
<a>Forgot my username</a>
</Link>
<Link href="forgot-password">
<a>Forgot my password</a>
</Link>
</footer>
</form>
)}
}
GrahpQL:
const MUTATION_AUTH = gql`
mutation authenticate($username: String!, $password: String!) {
authenticate(input: {
username: $username,
password: $password
}) {
jwtToken
}
}
`
const QUERY_CURRENT_ACCOUNT = gql`
{
currentAccount {
account {
firstName
profileImageUrl
}
role
}
}
`
export default withData(compose(
graphql(MUTATION_AUTH, { name: 'authenticate' }),
graphql(QUERY_CURRENT_ACCOUNT, { name: 'getCurrentAccount'} )
)
(withApollo(Index)))

Resources