Next JS router not changing page in async function - reactjs

I am very new to promises and JS in general, but I am working on creating a register page using a tutorial on YouTube. I am currently using next.js with react and TypeScript to redirect to the user to the home page if no errors occur when registering (no email, password too short, etc.), but the router won't redirect the user within an async onSubmit function.
Here is the current non-working code. The useRegisterMutation hook just creates a function that takes in the input fields and creates a user in the db for me:
const Register: React.FC<registerProps> = ({}) => {
const router = useRouter();
const [register] = useRegisterMutation();
return (
<Wrapper>
<Formik initialValues={{first_name: "", last_name: "", email: "", password: ""}}
onSubmit={async (values, {setErrors}) => {
const response = await register({
variables: {
email: values.email,
pass: values.password,
fname: values.first_name,
lname: values.last_name
}
});
// A user is returned, meaning there were no errors
if (response.data?.createUser?.user) {
await router.push("/");
// An error was returned, meaning that the user was not created
} else if (response.data?.createUser?.errors) {
setErrors(setErrorMap(response.data.createUser.errors));
}
}}>
// ----------- Unimportant HTML -----------
</Formik>
</Wrapper>
);
}
export default Register;
When I remove async / await from the onSubmit function, the router begins correctly working, but the if statements do not work correctly because they are expecting response to be a promise, so they fail every time. Is there some trick to get the router to push to the correct url while still keeping the function as a promise?

Your async code seems to be fine, and you're saying the redirect works without async.
It seems that you might not be getting what you're expecting to get.
I'd log the response and response.data and all of the subsequent properties that you need for your if statement.
p.s.
Feel free to share the tutorial, you might've missed something or maybe it has a mistake.

Related

Redux toolkit RTK query mutation not getting returning data

Hi I recently learned the new react toolkit with the rtk query tool, and I am trying to put in a login system together using the createApi from the rtk package.
After giving it a test on the login button pressed, I see the network request going through without any issue(status code 200), and I get a response object providing user, token, however, when I try to get the returning data using useLoginMutation I get an undefined value.
below is the code for my endpoint which is injected in a base api:
export const apiLogin = theiaBaseApi.injectEndpoints({
endpoints: (build) => ({
loginUser: build.mutation<UserReadonly, loginValuesType | string>({
query: (values: loginValuesType, redirect?: string) => {
const { username, password } = values;
const header = gettingSomeHeaderHere
return {
url: "login",
method: "GET",
headers,
crossDomain: true,
responseType: "json",
};
},
}),
}),
});
export const { useLoginUserMutation } = apiLogin
then inside my React component I destructure the mutation result such like below:
const [login, {data, isLoading}] = useLoginUserMutation();
const submitLogin = () => {
// pass in username password from the form
login({username, password});
}
Suppose if I console log out data and isLoading I assume that I will see data: {user: "abc", token: "xyz"}, because under network tab of my inspect window I can see the response of this network request, but instead I am seeing data: undefined
Does any have experience on solving this?
Oh I found the reason, it was a very careless mistake. I had to wrap the reducer to my store, which was what I was missing
In my case the issue was that I was trying to access the UseMutationResult object inside onClick callback. And the object was not updating inside the callback, even though in the component the values were accurate.
If I put the log outside it's working just fine.
here is an example for better understanding (inside handleAddPost the mutationResult is not updating)
Here is a code sample (in case link is not working):
const Component = () => {
const [addPost, mutationResult] = useAddPostMutation();
...
const handleAddPost = async () => {
...
console.log("INSIDE CALLBACK isLoading and other data is not updating:");
console.log(JSON.parse(JSON.stringify(mutationResult)))
...
};
// in the example this is wrapped in an useEffect to limit the number of logs
console.log(mutationResult.data,"OUTSIDE CALLBACK isLoading and other data is working:")
console.log(JSON.parse(JSON.stringify(mutationResult)))
return (
...
<Button
...
onClick={handleAddPost}
>
Add Post
</Button>
...

How to make localStorage persist even after reloading of the browser?

I have made a react app with mongodb and express with a simple authentication system. After signup, the User model with my credentials gets saved to the localStorage like this:
{
bio: ""
email: "myemail#gmail.com"
name: "myName"
role: "user"
_id: "5e9b42d31040cb0fc54c6936"
}
After login I want to update the bio property, or to add some since is empty initialy, so in order to do that I have made a axios call like this:
async function updateBiography (evt) {
evt.preventDefault();
try {
const res = await axios.put(`http://localhost:5000/biography/${id}`, { bioVal });
setBioVal(res.bio);
} catch (error) {
console.log(error);
}
}
So this thing is returning the updated value of bio, saving it to the database, no problem here. But I want to update the value of bio in the browser as well, and I want that to happen instantly as I submit. In order to achieve that I have done this:
React.useEffect(() => {
const currentUser = JSON.parse(localStorage.getItem('user'));
currentUser['bio'] = bioCurrentValue;
localStorage.setItem('user', JSON.stringify(currentUser));
}, [reload]);
After this the item in localStorage is like this:
{
bio: "the new value",
...
}
Awesome. The problem I have is that after reloading the browser the localStorage does not keep the update.
After reloading the item in the localStorage it becomes as before:
{
bio: "",
...
}
How can I make the updates persist in the localStorage?
Local storage is normally persistent, but you're subject to the browser settings of the user.
If in some private browsing mode, or with some other privacy extensions, it's common for the storage to be wiped out. Nothing you can do about this if this is what the user wants.

How to test a function has been called when submitted within a form using react-testing-library?

In my React Signup component is a form where the user inputs their email, password, and password confirmation. I am trying to write tests using jest/react-testing-library, however I keep getting a test failed as the received number of function calls is 0 with an expected number of calls being 1.
I have tried variations of the Jest matcher such as .toHaveBeenCalled(), .toHaveBeenCalledWith(arg1, arg2, ...), toBeCalled() all of which still expect a value of 1 or greater but fail because the received number is 0. I have tried both fireEvent.click and fireEvent.submit both of which still fail.
Signup.js
export const Signup = ({ history }) => {
const classes = useStyles();
const [signup, setSignup] = useState({
email: null,
password: null,
passwordConfirmation: null,
});
const [signupError, setSignupError] = useState('');
const handleInputChange = e => {
const { name, value } = e.target;
setSignup({ ...signup, [name]: value });
console.log(signup);
};
const submitSignup = e => {
e.preventDefault();
console.log(
`Email: ${signup.email}, Pass: ${signup.password}, Conf: ${signup.passwordConfirmation}, Err: ${signupError}`
);
};
return (
<main>
<form onSubmit={e => submitSignup(e)} className={classes.form}>
<TextField onChange={handleInputChange}/>
<TextField onChange={handleInputChange}/>
<TextField onChange={handleInputChange}/>
<Button
type="submit">
Submit
</Button>
Signup.test.js
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { render, cleanup, fireEvent } from '#testing-library/react';
import { Signup } from '../Components/Signup';
afterEach(cleanup);
const exampleSignup = {
email: 'test123#test123.com',
password: 'test123',
passwordConfirm: 'test123',
};
describe('<Signup />', () => {
test('account creation form', () => {
const onSubmit = jest.fn();
const { getByLabelText, getByText } = render(
<BrowserRouter>
<Signup onSubmit={onSubmit} />
</BrowserRouter>
);
const emailInput = getByLabelText(/Enter your Email */i);
fireEvent.change(emailInput, { target: { value: exampleSignup.email } });
const passInput = getByLabelText(/Create a Password */i);
fireEvent.change(passInput, { target: { value: exampleSignup.password } });
const passCInput = getByLabelText(/Confirm Password */i);
fireEvent.change(passCInput, {
target: { value: exampleSignup.passwordConfirm },
});
fireEvent.submit(getByText(/Submit/i));
expect(onSubmit).toHaveBeenCalledTimes(1);
});
});
Results from test run
account creation form
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
in your SignUp.test.js you are passing your onSubmit function as a prop however that prop is never used in your SignUp component. That is why your onSubmit function is never called.
Now regarding the answer to your question, the react testing library discourages testing implementation details (like testing a function has been called) therefor there is no way to test that using the react testing library (although you can do that with other frameworks like using jest.spyOn on your function so that would be a way to do it)
What is recommended though is testing the outcome of your submit function. For example let's say you want to display (Thank you for signing up) after clicking the submit button, in that case you would test that using expect(screen.getByText("Thank you for signing up")).toBeInTheDocument() after running fireEvent.submit(getByText(/Submit/i))
FYI : Since you are using jest, you don't need to call the cleanup function afterEach test.
I have previously answered a similar question here: https://stackoverflow.com/a/61869203/8879071
Your submit action is not doing any side effect really here (except for logging it).
You can assert console.log called with something. NOT A GOOD IDEA!
Usually, on form submissions, we do some side-effect as follows:
FUNCTION CALLS:
You would be calling something (a prop function, API library util) that is not a part of the component (aka dependency.)
Prop: pass a jest.fn() as a prop and can be asserted.
API util: axios/fetch can be mocked and asserted.
Only the dependencies can be mocked.
VISUAL SIDE-EFFECTS:
example, errors/success messages etc.
Assert on the elements being toBeInTheDocument()
FORM CONTENTS
Form contents can be asserted, if you're very interested.
e.g toHaveFormValues()
Summarising, internal functions (which have no reference outside) can't be mocked. AND SHOULD NOT BE!
The submit event has to be called at form tag. You can add a data-testid attribute at form tag to get it on test.
fireEvent.submit(getByTestid('form'));

Stub a function that runs a promise

I've been attempting to write unit tests for a registration page on a new react application
However I am very new to the concept of Sinon stubs/spies and have been having issues with intercepting a function call and forcing a resolve.
This is my initial test:
test('Success notification is displayed if all information is valid', () => {
wrapper.setState({ username: 'test', password: 'Test1234!#', confirmPassword: 'Test1234!#' });
const stub = sinon.stub(Register.prototype, 'signUp').resolves('Test');
wrapper.find('#submitRegistration').simulate('click');
});
onClick runs this event handler: (Simplified)
public onSubmitHandler(e: React.FormEvent) {
// Perform password match check
if (password === confirmPassword) {
this.signUp(this.state.username, password);
} else {
// Set success state
}
} else {
// Set error state
}
}
And finally, Signup:
public signUp(username: string, password: string) {
// Perform Cognito Signup
return Auth.signUp({
password,
username,
})
.then(() => {
// Set success State
})
.catch((err) => {
// Set error state
});
}
How can I intercept the call to signUp and force it down the resolve path, Currently due to the fact i do not configure my AWS Amplify Auth module it catches every time with "No userPool"
Jest provides many ways of mocking out dependencies, primarily there are 2 flavours:
Manual Mocks - These let you control mocks in far more details.
Mock functions - If you need a simple mock for a common problem, translation for example, these are very helpful.
For your case, you will also need to handle Promises, for this reason, you need to follow the advice on testing async behaviour.
For your case, I am assuming:
Auth is a module named 'auth-module'
you just need to setup the mock once and have 1 test data which resolves to success and 1 for failure.
// telling jest to mock the entire module
jest.mock("auth-module", () => ({
// telling jest which method to mock
signUp: ({ password, username }) =>
new Promise((resolve, reject) => {
// setting the test expectations / test data
process.nextTick(
() =>
username === "SuccessUser"
? resolve(userName)
: reject({
error: "failure message"
})
);
})
}));
Note : Adding the snippet even though it will not run, as the formatting seems to be broken with code block.
With a little thought push from dubes' answer I managed to get the following:
const signUpSpy = jest.spyOn(Auth, 'signUp').mockImplementation((user, password) => {
return new Promise((res, rej) => {
res();
});
});
Instead of spying directly onto the function I wrote and call moved it to the Modules function and resolved the promise from there!

Navigation failed after logging in with react-native-fbsdk

I am using the FBSDK to do the registration in react native. I need the name, last name and email in order to pass it to the registration screen and fill the mentioned fields there automatically. Here is my code in login screen:
async _fbAuth() {
//console.log("that", that);
let { isCancelled } = await LoginManager.logInWithReadPermissions(['public_profile','user_posts', 'email','user_birthday']);
if ( !isCancelled ) {
let data = await AccessToken.getCurrentAccessToken();
let token = data.accessToken.toString();
// await afterLoginComplete(token);
const response = await fetch(
`https://graph.facebook.com/me?fields=id,email,name,first_name,last_name,gender,picture,cover&access_token=${token}`);
let result = await response.json();
this.setState({result});
console.log('result result result ', result);
//navigate to complete the registration.
this.props.navigation.navigate('Reg_1', {name: this.state.result.name, email: this.state.result.email, surname: this.state.result.last_name })
}
else {
console.log('Login incomplete');
}
}
Also I have this button to call the function:
<ButtonFB
onPress={ this._fbAuth}
isLoading={false}
isEnabled={true}
label={I18n.t("Accedi_con_Facebook")}
style={commonStyle.Button}
/>
Everything works fine and the data retrieved well. The only problem is the part of navigation and setSate. My problem is that the 'this' has been lost during the login with the facebook. I got the following warning after doing the login with facebook.
Possible: Unhandled promise rejection (id:0): type error:
this.setState is not a function
I also tried to bind the function of _fbAuth, but it doesn't work. Can you help me to solve this problem. Thanks in advance.
You need to bind the function as
_fbAuth = async () => {}
Since this is not being referenced in _fbAuth function.
For more info checkout this artice

Resources