I've got the following function that gets triggered on a form submission
const [register, { loading }] = useMutation(RegisterDocument);
const router = useRouter();
const onSubmit = async (values: FormValues) => {
const v = { ...values };
delete v.confirmPassword;
const res = await register({
variables: { options: v },
update: (cache, { data }) => {
cache.writeQuery<MeQuery>({
query: MeDocument,
data: {
__typename: 'Query',
me: data?.register.user,
},
});
},
});
if (res.data?.register.user) {
router.push('/');
}
};
I then have the following test to submit the form
test('it should submit form without error', async () => {
const firstName = faker.name.firstName();
const surname = faker.name.lastName();
const username = faker.internet.userName().replace('#', '');
const email = faker.internet.email();
const password = faker.internet.password(6, false, /^[a-zA-Z0-9_.-]*$/);
const cache = new InMemoryCache().restore({});
const variables = {
options: { email, firstName, password, surname, username },
};
const user = { email, firstName, surname, username, id: 1, activated: false, photo: null };
const mocks = [
{
request: { query: RegisterDocument, variables },
result: { data: { register: { errors: null, user } } },
},
];
const { queryByTestId, container } = renderWithTheme(
<MockedProvider mocks={mocks} cache={cache}>
<Register />
</MockedProvider>,
);
await updateRegisterInputs(container); // util function that updates input values for submission
await submitForm({ queryByTestId, testId: 'register-submit', loadingTestId: 'register-loading' }); // util function that submits form
await waitFor(() => expect(onPush).toBeCalledWith('/'));
});
When I run this test res returns the following
{ data: { register: {} } }
However, once I remove the update property inside the register mutation function, res returns the following.
{ data: { register: { errors: null, user: [Object] } } }
Any ideas why the mocked return value returns an empty object for the register property only when the update property function is added?
Even just instantiating the update property like so;
update: () => {}
still breaks the response from the mutation.
I realised that the graphql doc required the __typename property in the relevant places in my mocks
So I have to update the mock to include the typenames.
const user = { email, firstName, surname, username, id: 1, activated: false, photo: null, __typename: 'User' };
const mocks = [
{
request: { query: RegisterDocument, variables },
result: { data: { register: { errors: null, user, __typename: 'UserResponse' } } },
},
];
Related
import { DataTypes } from "sequelize";
import dbConnect from "./dbConnect";
async function UserModel() {
const sequelize = await dbConnect();
const User = sequelize.define(
"User",
{
userKey: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
},
username: {
type: DataTypes.STRING(50),
allowNull: false,
},
},
{
freezeTableName: true, // enforces that table name = model name
}
);
return User;
}
export default UserModel;
The above module returns the UserModel correctly, but I need to resolve it twice --
const users = await (await User()).findAll();
-- in the module that imports it b/c I'm calling a method that also returns a promise.
I'd like to call it like this --
const users = await User().findAll();
-- but everything I've tried has failed.
See usage here:
export async function getServerSideProps() {
const users = await (await User()).findAll();
return {
props: {
users,
},
};
}
Can you shed some light on how to do this?
Created an initialState and will be updated the totalPage and currentPage after got the users list.
I found out onQueryStarted from docs, it able to update the store state in this method but only look like only for builder.mutation.
what's the correct way to get the user list and update the store page value in redux toolkit?
Listing two part of the code below:
apiSlice
component to use the hook
// 1. apiSlice
const usersAdapter = createEntityAdapter({})
export const initialState = usersAdapter.getInitialState({
totalPage: 0,
currentPage: 0,
})
export const usersApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getUsers: builder.query({ // <--- the docs are using builder.mutation, but i needed to pass params
query: (args) => {
const { page, limit } = args;
return {
url: `/api/users`,
method: "GET",
params: { page, limit },
}
},
validateStatus: (response, result) => {
return response.status === 200 && !result.isError
},
transformResponse: (responseData) => { // <<-- return { totalPages: 10, currentPage: 1, users: [{}] }
const loadedUsers = responseData?.users.map((user) => user)
return usersAdapter.setAll(initialState, loadedUsers)
},
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled
const {totalPages, currentPage} = data; <----- totalPages & currentPage values are still 0 as initialState
dispatch(setPages({ currentPage, totalPages }))
} catch (error) {
console.error("User Error: ", error)
}
},
providesTags: (result, error, arg) => {
if (result?.ids) {
return [
{ type: "User", id: "LIST" },
...result.ids.map((id) => ({ type: "User", id })),
]
} else return [{ type: "User", id: "LIST" }]
},
})
})
});
export const {
useGetUsersQuery,
} = usersApiSlice
component to use the hook
Try to use the hook in user landing page
const UsersList = () => {
const { data: users, isLoading, isSuccess, isError } = useGetUsersQuery({page: 1, limit: 10 })
return (
<div>return the users data</div>
)
}
update the store value after get the data return
I am currently converting the state management from old redux to reduxjs/toolkit.
This is how it looked previously:
ACTION:
export const RECEIVED_FUNCTION_SUCCESS = 'RECEIVED_FUNCTION_SUCCESS ';
export const getFunctionSuccess = (result, callbackParams) => {
const append = callbackParams.pageNumber > 0;
return {
type: RECEIVED_FUNCTION_SUCCESS,
payload: result,
append,
pageSize: callbackParams.pageSize,
};
};
REDUCER:
{
case ActionTypes.RECEIVED_FUNCTION_SUCCESS:
return {
...state,
canLoadMore: payload.length >= pageSize,
data: append ? [...state.data, ...payload] : payload,
};
This is how it looks now:
ACTION:
export const getFunctionSuccess = createAction('RECEIVED_FUNCTION_SUCCESS ');
REDUCER:
extraReducers: {
[getFunctionSuccess ]: (state, { payload }) => {
const { pageSize, pageNumber } = payload;
const append = pageNumber > 0;
return {
...state,
canLoadMore: payload.length >= pageSize,
data: append ? [...state.data, ...payload] : payload,
};
},
I can't make it work, pageSize and pageNumber is always undefined... I don't know how to include the callbackParams in the toolkit reducer and action.
For more context this is the main part of the fetching:
request({
requestId: 'getFunction',
params: {
data: {
filters: mappedFilters,
sorts: sort,
pageSize,
pageNumber,
},
},
callbackParams: {
pageNumber,
pageSize,
},
}),
I do fetch successfully and all, just the callbackParams, what is additional to the action after payload, I can't manage to make it work.
I'd appreciate some help.
I'm trying to register a user and I get an error:
[uncaught application error]: TypeError - Cannot read properties of undefined (reading 'where')
Here is the code:
async register(context: any) {
const body = JSON.parse(await context.request.body().value);
const existing = await Users.where("email", body.email).get();
if (existing.length) {
context.response.status = 400;
return (context.response.body = { message: "User already exists" });
}
const hashedPassword = await Users.hashPassword(body.password);
const user = await Users.create({
email: body.email,
hashedPassword,
});
context.response.body = { message: "User created" };
}
Here is my model:
// import { Model, DataTypes } from "https://deno.land/x/denodb/mod.ts";
import { DataTypes, Model } from "https://deno.land/x/denodb/mod.ts";
import * as bcrypt from "https://deno.land/x/bcrypt/mod.ts";
import {
create,
getNumericDate,
verify,
} from "https://deno.land/x/djwt/mod.ts";
import { JwtConfig } from "../middleware/jwt.ts";
import { db } from "../db.ts";
class Users extends Model {
static table = "users";
static timestamps = true;
static fields = {
id: {
primaryKey: true,
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
unique: true,
},
hashedPassword: {
type: DataTypes.STRING,
},
};
static defaults = {
id: crypto.randomUUID(),
};
// ...
static async hashPassword(password: string) {
const salt = await bcrypt.genSalt(8);
return bcrypt.hash(password, salt);
}
static generateJwt(id: string) {
// Create the payload with the expiration date (token have an expiry date) and the id of current user (you can add that you want)
const payload = {
id,
iat: getNumericDate(new Date()),
};
// return the generated token
return create({ alg: "HS512", typ: "JWT" }, payload, JwtConfig.secretKey);
}
}
//db.link([Users]);
//await db.sync();
export default Users;
Had to uncomment this:
db.link([Users]);
I'm currently creating an authentication feature in Nuxt and Firebase.
The login and logout process itself can be done and the header display changes accordingly, but there is an error in console when I press the login button.
Error content (in console)
Uncaught RangeError: Maximum call stack size exceeded
at Function.keys (<anonymous>)
code
Header.vue(This is the page containing the login button.)↓
googleLogin () {
const provider = new firebase.auth.GoogleAuthProvider()
auth.signInWithPopup(provider)
.then(res => {
this.dialogAuthVisible = false
this.$store.dispatch('auth/setUser',res.user)
}).catch(e => console.log(e))
}
store/auth.js↓
export const strict = false
export const state = () => ({
user: null
})
export const mutations = {
SET_USER (state, payload) {
state.user = payload
}
}
export const actions = {
setUser ({ commit }, user) {
commit('SET_USER',user)
}
}
export const getters = {
isAuthenticated (state) {
return !!state.user
}
}
default.vue↓
mounted () {
auth.onAuthStateChanged(user => {
const { uid, displayName, photoURL} = user
if (user) {
this.$store.dispatch('auth/setUser', { uid, displayName, photoURL})
} else {
this.$store.dispatch('auth/setUser', null)
}
})
}
If there's any information I'm missing, please let me know 🙇️.
Please teach me how to do this 🙇️.
I think the problem is in this code lines :
export const mutations = {
SET_USER (state, payload) {
state.user = payload
}
}
export const actions = {
setUser ({ commit }, user) {
commit('SET_USER',user)
}
}
There is a loop between this mutations and actions
Instead of setting the entire payload into the store object, I just picked the fields I needed, and that resolved the problem for me.
Before:
AUTH_STATUS_CHANGED ({commit}, data: any): any {
if (data && data.authUser) {
commit('SetAuthUser', data.authUser);
} else {
commit('SetAuthUser', null);
}
}
After:
AUTH_STATUS_CHANGED ({commit}, data: any): any {
if (data && data.authUser) {
const user = data.authUser;
commit('SetAuthUser', {
uid: user.uid,
email: user.email,
emailVerified: user.emailVerified,
displayName: user.displayName,
isAnonymous: user.isAnonymous,
photoURL: user.photoURL,
stsTokenManager: user.stsTokenManager,
createdAt: user.createdAt,
lastLoginAt: user.lastLoginAt,
apiKey: user.apiKey,
});
} else {
commit('SetAuthUser', null);
}
}
Inside the mutation, just add the value received from the mutation payload.