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

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>

Related

Getting an error "A non-serializable value was detected in an action, in the path: `payload`. Value: " in react-redux

I wrote a function in add user and remove user on react-redux. But i getting error on this function
user Slice.js in redux
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "users",
initialState: {
users: {
id: "",
name: "",
email: "",
},
},
reducers: {
addUser: (state, action) => {
state.users = action.payload;
},
removeUser: (state) => {
state.users = "";
},
},
});
export const { addUser, removeUser } = userSlice.actions;
export default userSlice.reducer;
The remove user funcion is returning a empty string. on the screen value is empty.
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { removeUser } from "../Slices/userSlice";
import {} from "react-redux";
const User = () => {
const users = useSelector((state) => state.users.users);
const dispatch = useDispatch();
console.log(users);
const removeuser = (state) => {
dispatch(removeUser(state));
};
return (
<div>
<h1>{users.id}</h1>
<h2>{users.name}</h2>
<h3>{users.email}</h3>
<button type="" onClick={removeuser}>
Remove User
</button>
</div>
);
};
export default User;
what the solution of this function
What I am seeing in your error message is that you are passing a React event in your action payload.
Why is this happening? An onClick prop is a function which gets called with the click event as its argument.
<button type="" onClick={removeuser}>
So the argument of removeuser needs to be (event) => {. Or if you don't need to access the event then you can use () => {. There is no scenario in which a state variable makes sense as an argument.
When you call a Redux Toolkit action creator function, the argument that you pass to the function will become the action.payload.
With the way that your slice is set up now, the removeUser reducer does not use the action.payload so it does not need an argument. You just call dispatch(removerUser()).
const onClickRemove = () => {
dispatch(removeUser());
};
/* ... */
<button onClick={onClickRemove}>
or
<button onClick={() => dispatch(removeUser())}>
Does you app have more than one user? As you begin to understand things more, your userSlice will probably evolve. You may end up with an array of users, in which case your removeUser action will need to know which user to remove.
Something like:
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "users",
initialState: {
users: []
},
reducers: {
// Payload is an array of user objects.
setAllUsers: (state, action) => {
state.users = action.payload;
},
// Payload is a single user object.
addUser: (state, action) => {
state.users.push(action.payload);
},
// Payload is the id of the user to remove.
removeUser: (state, action) => {
state.users = state.users.filter(user => user.id !== action.payload);
},
},
});
export const { setAllUsers, addUser, removeUser } = userSlice.actions;
export default userSlice.reducer;
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { removeUser } from "../Slices/userSlice";
const UsersList = () => {
const users = useSelector((state) => state.users.users);
console.log(users);
return (
<div>
{users.map(user => (
<UserListItem key={user.id} user={user}/>
))}
</div>
);
}
const UserListItem = ({ user }) => {
const dispatch = useDispatch();
return (
<div>
<h1>{user.id}</h1>
<h2>{user.name}</h2>
<h3>{user.email}</h3>
<button onClick={() => dispatch(removeUser(user.id))}>
Remove User
</button>
</div>
);
};
export default UsersList;

How to test react component mocking fetch api?

I'm trying to test a react component where a fetch call occurs. The component:
class SearchResults extends React.Component<{ tag: string; setSpinner: (value: boolean) => void }> {
state: searchResultsState = {
results: [],
tag: '',
cardFull: null,
};
constructor(props: { tag: string; setSpinner: (value: boolean) => void }) {
super(props);
this.handleCardModal = this.handleCardModal.bind(this);
}
componentDidMount() {
getResponse(
this.props.tag,
(res) => {
this.setState({ results: res, tag: this.props.tag });
},
this.props.setSpinner
);
}
componentDidUpdate(prevProps: { tag: string }) {
if (this.props.tag !== prevProps.tag) {
getResponse(
this.props.tag,
(res) => this.setState({ results: res, tag: this.props.tag }),
this.props.setSpinner
);
}
}
handleCardModal() {
this.setState({ cardFull: null });
}
render() {
return (
<Fragment>
{this.state.cardFull && (
<CardFull data={this.state.cardFull} handleClick={this.handleCardModal} />
)}
{this.state.cardFull && <div className="blackout" onClick={this.handleCardModal} />}
<div className="search-results">
{this.state.results.length === 0 && this.state.tag !== '' && <div>Sorry, no matched</div>}
{this.state.results.map((result) => (
<CardUI
key={result.id}
className="card"
variant="outlined"
sx={{ minWidth: 275 }}
onClick={() => {
const currentCard = this.state.results.filter((res) => res.id === result.id)[0];
this.setState({ ...this.state, cardFull: currentCard });
}}
>
<CardMedia component="img" height="194" image={getUrl(result)} alt={result.title} />
<CardContent>
<p>{result.title}</p>
</CardContent>
</CardUI>
))}
</div>
</Fragment>
);
}
}
First I tried to use jest-fetch-mock.
import '#testing-library/jest-dom';
import { render } from '#testing-library/react';
import renderer from 'react-test-renderer';
import SearchResults from '../../src/components/SearchResults/SearchResults';
import sampleSearchResults from '../__fixtures__/sampleSearchResults';
import fetch from 'jest-fetch-mock';
fetch.enableMocks();
beforeEach(() => {
fetch.resetMocks();
});
const setSpinner = jest.fn();
describe('Search Results component', () => {
fetch.mockResponseOnce(JSON.stringify({ photos: sampleSearchResults }));
test('Search Results matches snapshot', () => {
const searchResults = renderer
.create(<SearchResults tag={''} setSpinner={setSpinner} />)
.toJSON();
expect(searchResults).toMatchSnapshot();
});
test('search results renders correctly', () => {
render(<SearchResults setSpinner={setSpinner} tag={'dove'} />);
});
});
But it gives the error during tests:
console.error
FetchError {
message: 'invalid json response body at reason: Unexpected end of JSON input',
type: 'invalid-json'
}
So, I've decided to mock fetch manually
import React from 'react';
import '#testing-library/jest-dom';
import { render, screen } from '#testing-library/react';
import renderer from 'react-test-renderer';
import SearchResults from '../../src/components/SearchResults/SearchResults';
import sampleSearchResults from '../__fixtures__/sampleSearchResults';
const setSpinner = jest.fn();
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ photos: sampleSearchResults }),
})
) as jest.Mock;
describe('Search Results component', () => {
test('Search Results matches snapshot', () => {
const searchResults = renderer
.create(<SearchResults tag={''} setSpinner={setSpinner} />)
.toJSON();
expect(searchResults).toMatchSnapshot();
});
test('search results renders correctly', () => {
render(<SearchResults setSpinner={setSpinner} tag={'dove'} />);
const title = screen.getByText(/Eurasian Collared Dove (Streptopelia decaocto)/i);
expect(title).toBeInTheDocument(); //mistake
});
});
Now fetch mock works correct, but it renders only one div - search results and doesn't render card. How can I test my component? Thank you.

Why does RTK query response handling not work?

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)
}
}

BeforeUpload do not trigger upload on promise resolved

Using React, and antd
I have the following code in my component:
<Upload
action={HttpService.getBaseUrl(`post_import_csv`, HttpService.AuditcoreAPIBasePath)}
headers={{"Authorization": `Bearer ${AuthHelper.getAuthKey()}`}}
showUploadList={false}
multiple={false}
beforeUpload={(file: RcFile): PromiseLike<any> => {
this.setCSV(file);
return new Promise((resolve) => {
this.state.requestUpload.pipe(take(1)).subscribe(() => {
resolve(file);
console.log('resolved')
});
})
}}></Upload>
Basically I want my beforeUpload to wait for the user to click on a button before uploading the file. I did so by returning a Promise and waiting for a rxjs Suject that is triggered on button click to resolve the promise. Pretty much following the doc
Here is the button code :
<Button
onClick={(e): void => {
this.state.requestUpload.next(true);
}}
>
Upload
</Button>
It works nice, but the file is never uploaded, I do see my log resolved but there is no trace of network call in my console.
I fixed using this approach which is cleaner :
https://codesandbox.io/s/xvkj90rwkz
Basically, having a custom function that handle upload. It doesn't explain why my tricky solution was not working, but I got it working.
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Upload, Button, Icon, message } from 'antd';
import reqwest from 'reqwest';
class Demo extends React.Component {
state = {
fileList: [],
uploading: false,
};
handleUpload = () => {
const { fileList } = this.state;
const formData = new FormData();
fileList.forEach(file => {
formData.append('files[]', file);
});
this.setState({
uploading: true,
});
// You can use any AJAX library you like
reqwest({
url: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
method: 'post',
processData: false,
data: formData,
success: () => {
this.setState({
fileList: [],
uploading: false,
});
message.success('upload successfully.');
},
error: () => {
this.setState({
uploading: false,
});
message.error('upload failed.');
},
});
};
render() {
const { uploading, fileList } = this.state;
const props = {
onRemove: file => {
this.setState(state => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
return {
fileList: newFileList,
};
});
},
beforeUpload: file => {
this.setState(state => ({
fileList: [...state.fileList, file],
}));
return false;
},
fileList,
};
return (
<div>
<Upload {...props}>
<Button>
<Icon type="upload" /> Select File
</Button>
</Upload>
<Button
type="primary"
onClick={this.handleUpload}
disabled={fileList.length === 0}
loading={uploading}
style={{ marginTop: 16 }}
>
{uploading ? 'Uploading' : 'Start Upload'}
</Button>
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById('container'));

Reject promise in redux-form in fetch catch-block

How does one throw a new SumbmissionError in a .catch-block without getting a unhandled rejection?
This throws a Unhandled Rejection (SubmissionError)
import { SubmissionError } from 'redux-form';
const submit = (data) => {
console.log(data);
const config = {
method: 'GET'
};
return fetch('http://www.notAvailable.com/', config)
.then(response => {
console.log(response)
})
.catch(err => {
throw new SubmissionError({_error: 'Error'});
});
}
export default submit;
If you return the promise to the onSubmit config / prop or handleSubmit reduxForm should catch the SubmissionError:
const { createStore, combineReducers } = Redux;
const { Provider } = ReactRedux;
const { reduxForm, SubmissionError, reducer } = ReduxForm;
const submit = () => new Promise((resolve, reject) => {
setTimeout(() => reject('500'), 100);
}).catch(e => {
throw new SubmissionError({ _error: 'service error' })
});
const Form = reduxForm({
form: 'example',
onSubmit: submit
})(props => (
<form onSubmit={props.handleSubmit}>
<pre>{JSON.stringify(props, null, 4)}</pre>
<button type="submit">submit</button>
</form>
));
const store = createStore(combineReducers({ form: reducer }));
ReactDOM.render((
<Provider store={store}>
<Form />
</Provider>
), document.getElementById('root'));
JSFiddle: https://jsfiddle.net/pariesz/6srugtwr/

Resources