State is changing but transitions are not triggered in Xstate - state-machine

I am working with xstate with Nextjs. Now I am stuck somewhere.
import { assign, createMachine, interpret } from "xstate";
export interface toggleAuth {
isAuthenticated: boolean;
user: {
name: string | undefined;
};
}
// console.log(getCachedData());
export const authMachine = createMachine<toggleAuth>({
id: "auth",
initial: "unauthenticated",
context: {
isAuthenticated: false,
user: {
name: undefined,
},
},
states: {
authenticated: {
on: {
toggle: {
target: "unauthenticated",
},
},
entry: assign({
user: (ctx) => (ctx.user = { name: "Pranta" }),
isAuthenticated: (ctx) => (ctx.isAuthenticated = true),
}),
},
unauthenticated: {
on: {
toggle: {
target: "authenticated",
},
},
entry: assign({
user: (ctx) => (ctx.user = { name: undefined }),
isAuthenticated: (ctx) => (ctx.isAuthenticated = false),
}),
},
},
});
const service = interpret(authMachine);
service.onTransition((state) => console.log(state));
So I was watching the docs. According to them, whenever I transition from unauthenticated to authenticated and authenticated to unauthenticated, it should console log it for me. But it doesn't. It does only one time. What's happening here. Also, is it okay to define my machine like this? Thanks in advance.

It's not logging because you're not changing state; no event is ever being sent.
Please re-read the documentation on assigning to context - you are mutating context instead of assigning new values; the assigners should always be pure.
If you want to see the state change, you need to send a toggle event in this case:
service.send('toggle');
Also, there is no need for isAuthenticated; this is redundant, since that state is represented by the finite state (state.value) of your machine.

Related

How to make NextAuth update the User on a role change?

I'm using NextAuth with the Prisma adapter and AWS Cognito and it works perfectly, but my problem is that my User model doesn't get updated if I change the groups on Cognito. This is how I configured NextAuth:
// I copied the original and changed some of the fields
export type CognitoProfile = {
email: string;
sub: string;
preferred_username: string;
"cognito:groups": string[];
};
const CognitoProvider = (
options: OAuthUserConfig<CognitoProfile>
): OAuthConfig<CognitoProfile> => {
return {
id: "cognito",
name: "Cognito",
type: "oauth",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
idToken: true,
profile: (profile) => {
return {
id: profile.sub,
name: profile.preferred_username,
email: profile.email,
image: "",
roles: profile["cognito:groups"],
};
},
options,
};
};
export const authOptions: NextAuthOptions = {
// Include user.id on session
callbacks: {
session: ({ session, user }) => {
console.log(`User: ${JSON.stringify(user)}`);
if (session.user) {
session.user.id = user.id;
}
return session;
},
},
adapter: PrismaAdapter(prisma),
providers: [
CognitoProvider({
clientId: process.env.COGNITO_CLIENT_ID!,
clientSecret: process.env.COGNITO_CLIENT_SECRET!,
issuer: process.env.COGNITO_ISSUER,
}),
],
};
This works perfectly when a new user logs in (their groups are saved properly).
The problem is that the database is not updated when I log out and log back in after I add/remove group(s) to a Cognito user. This problem is not Cognito-specific it would be the same with things like Keycloak.
I checked the NextAuth docs, but I didn't find a solution for this. What's the recommended way of keeping the User model up to date? I don't want to reinvent the wheel 😅

How to get random records from Strapi v4 ? (I answered this question)

Strapi doesn't have any endpoint to get random data for this purpose you should write some custom code for your endpoint
custom route for that endpoint you want
// path: ./src/api/[your-endpiont]/routes/[custom-route].js
module.exports = {
"routes": [
{
"method": "GET",
"path": "/[your-endpiont]/random", // you can define everything you want for url endpoint
"handler": "[your-endpiont].random", // random is defined as a method
"config": {
"policies": []
}
}
]
}
now you have to run yarn develop or npm ... to display a random method in your strapi panel
Save this setting and retry to reach the random endpoint.
create a function as a service for getting random data in your endpoint API services.
// path: ./src/api/[your-endpiont]/services/[your-endpiont].js
'use strict';
/**
* news-list service.
*/
const { createCoreService } = require('#strapi/strapi').factories;
module.exports = createCoreService('api::news-list.news-list', ({ strapi }) => ({
async serviceGetRandom({ locale, id_nin }) { // these parametrs come from query
function getRandomElementsFromArray(array, numberOfRandomElementsToExtract = 1) {
const elements = [];
function getRandomElement(arr) {
if (elements.length < numberOfRandomElementsToExtract) {
const index = Math.floor(Math.random() * arr.length)
const element = arr.splice(index, 1)[0];
elements.push(element)
return getRandomElement(arr)
} else {
return elements
}
}
return getRandomElement([...array])
}
const newsListArray = await strapi
.db
.query("api::news-list.news-list")
.findMany({
where: {
locale: locale, // if you have multi-language data
$not: {
id: id_nin, // depend on where this endpoint API use
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
},
//? filter object throws an error when you used populate object, everything you want to filter properly best write into where{}
// filters: {
// publishedAt: {
// $notNull: true,
// },
// locale: locale
// }
})
if (!newsListArray.length) {
return null
}
return getRandomElementsFromArray(newsListArray, 2)
}
}));
explain code:
Strapi provides a Query Engine API to interact with the database layer at a lower level
strapi.db.query("api::news-list.news-list").findMany({})
The Query Engine allows operations on database entries,
I wrote this for my purpose probably you should change based on what you needed
{
where: {
locale: locale,
$not: {
id: id_nin
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
}
}
when you get data from your query, passed it to that function getRandomElementsFromArray(newsListArray, 2) to get some random item (how many random items do you want ? pass the second parameter)
At least if your array is null return null otherwise return data
create the controller
Controllers are JavaScript files that contain a set of methods, called actions, reached by the client according to the requested route so we going to call our services in this section
// path: ./src/api/[your-endpoint]/controllers/[your-endpoint].js
'use strict';
/**
* news-list controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::news-list.news-list', ({ strapi }) => ({
async random(ctx) { // name of this methods related to something we define in route ("handler": "[your-endpiont].random",)
const entity = await strapi.service('api::news-list.news-list').serviceGetRandom(ctx.query) // call our services, you can send all query you get from url endpoint (notice that you should write your endpoint api in strapi.service("your-endpoint"))
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return this.transformResponse(sanitizedEntity);
// console.log(entity);
}
}));
I call this endpoint in my project nextjs & stapi cms
export const getRandomNewsItem = (id, locale) => {
return API
.get(`/news-list/random?locale=${locale}&id_nin=${id}`)
.then(res => res.data);
};
That's it, I'll hope you all get what to do
all resources you need
https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#creating-custom-routers
https://docs.strapi.io/developer-docs/latest/development/backend-customization/services.html#implementation
https://docs.strapi.io/developer-docs/latest/development/backend-customization/controllers.html#adding-a-new-controller
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.html
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.html#and
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/populating.html

testing a vue3 component with <router-link> with jest

I got this test from another site. They are injecting a mock route. I think I need to mock router itself or pass a real one into the test so the page can run. There is a way to do this in vue 2, but I haven't found an example for vue 3.
import { mount } from "#vue/test-utils";
import Nav from "./Nav.vue";
test("it displays a menu item", () => {
const mockRoute = {
params: {
id: 1,
},
};
const mockRouter = {
push: jest.fn(),
};
const wrapper = mount(Nav, {
props: {
isAuthenticated: true,
},
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
},
},
});
expect(wrapper.find("#navLabel_0").text()).toEqual("Appointments");
});
The component I'm testing has tags.
The test fails with:
Failed to resolve component: router-link
You have to pass the router-link as a stub: stubs: ['router-link'] when you mount the component:
const wrapper = mount(Nav, {
props: {
isAuthenticated: true,
},
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
},
},
stubs: ['router-link'] });

react js request result

Hello I have a problem getting a value and setting in my state in react
I can see data in console of my api response and everything goes well.
export default class index extends Component {
constructor(props){
super(props)
this.state={ products: [], filteredProducts:[]}
}
componentDidMount(){
api.get('/products').then( result => this.setState({
products: result.data.listProducts,
filteredProducts: result.data.listProducts
}))
console.log(this.state.products)
}
but when I console my state value, it appears an empty array
index.js:16 [] console.log(this.state
index.js:11 (5) [{…}, {…}, {…}, {…}, {…}] console.log( data request
Well I don't know if it's a problem with my back end
I made a map to filter what I will return to my front end since I have
an array of 3 objects
I don't know if I made the best option or if I can do better, if I can improve the code I would be happy if someone could alert me:
async getAllProduct(req,res){
try {
const results = await Products.findAll({
// raw: true, <= remove
attributes:['id','name', 'float', 'price'],
include: [{
model: SubCategory,
as: 'subcategory',
attributes: ['id','name'],
},
{
model:Exteriors,
as: 'exteriors',
attributes: ['id','name']
},
{
model:Types,
as: 'types',
attributes: ['id','name']
},
],
})
const listProducts = []
results.map(record =>
record.get({ plain: true }));
results.map( (products) => {
const model = {
id: products.id,
name: products.name,
float: products.float,
price: products.price,
id_sub: products.subcategory.id,
subcategory: products.subcategory.name,
id_types: products.types.id,
type: products.types.name,
id_ext: products.exteriors.id,
exterior: products.exteriors.name,
}
listProducts.push(model);
})
if(listProducts){return res.status(200).json({listProducts})}
else{return res.status(400).json({result: 'failed to get Products'})}
} catch (error) {
console.error(error);
}
}
setState is async, you can't see updated state right after setting the state,
You can have callback in setState to check the updated state,
this.setState({
products: result.data.listProducts,
filteredProducts: result.data.listProducts
}, () => console.log(this.state.products)) //callback method
If you console.log right after a state update you will log the old state. Try logging the state in componentDidUpdate to see if the state is actually empty:
componentDidUpdate() {
console.log(this.state)
}

Formik, jest, yup : how to test validation?

i can't find a way to test form yup validation:
it('displays error on submit if name is empty', async () => {
const wrapper = mount(<MyFormik/>)
const getForm = () => wrapper.find('form')
wrapper.find('input[name="name"]').simulate('change', {
persist: () => {},
target: {
name: 'name',
value: ''
}
})
wrapper
.find('MyInnerForm')
.props()
.submitForm()
await wait(0) // await next tick or even 1s...
wrapper.update()
expect(
wrapper
.update()
.find('.error')
.exists()
)
.toBeTruthy() // FALSE!
})
No matter if i wait after submit, update wrapper errors prop is always empty.
And the solution here are not working for me:
https://github.com/jaredpalmer/formik/issues/1146
https://github.com/jaredpalmer/formik/issues/110
Looks like wrapper won't update
Here's the log of formik props after submit:
{ errors: {},
label: '',
name: 'name',
type: 'text',
values: { name: '' },
touched: { name: true },
isValidating: false,
status: undefined,
initialValues: { name: '' },
validateOnChange: true,
validateOnBlur: true } }
...
submitCount: 1,
isValid: false,
You can validate the form values directly on your validation schema.
const yup = require('yup')
const contactSchema = yup.object({
name: yup.string()
.required(),
age: yup.number()
.required()
.positive()
.integer()
})
const errors = await contactSchema.validate({
name: 'Kenneth',
age: -35.5
}).catch(function(err) {
return err
});
console.log("errors", errors);
https://runkit.com/kluplau/5defa8cd122cf6001a3034c7
Without seeing your component I'm not entirely sure what's going wrong. This is likely not to be working:
wrapper
.find('MyInnerForm')
.props()
.submitForm()
If your component MyInnerForm contains a Formik form calling submitForm() there will not cause Formik's validation to run. I would instead do something like this:
wrapper.find("form").simulate("submit");
However if that isn't solving your issue I made a full example that you can have a look at here.

Resources