I'd like to intercept all Form Actions / POST events send to the server to do data validation once, instead each time per page in sveltekit.
I figured the best place might be hooks.server, but it only exposes the handle function, not the actions: Actions that is needed for using invalid( ... ) for returning data validation.
Is there a way to return invalid(...) in hooks.server or access actions:, or is there a better way to handle this?
There is nothing particularly special about invalid. It ultimately causes a JSON response of the form:
{
type: 'invalid',
status: number, // HTTP code
data: any, // Object that will be passed to `form` property of page
}
So you can use the handle hook to do validation, e.g.
import type { Handle } from '#sveltejs/kit';
import { json } from '#sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
if (event.request.method == 'POST') {
const errorInfo = await validate(event.request);
if (errorInfo) {
return json({
type: 'invalid',
status: errorInfo.status,
data: errorInfo.data,
}, { status: errorInfo.status });
}
}
return await resolve(event);
}
async function validate(r: Request) {
// `clone()` so the rest of the code that might also
// try to read the request should not be affected
const data = await r.clone().formData();
// Validation logic here
}
Related
In TypeScript / Angular, you would usually call a function that returns an observable and subscribe to it in a component like this:
this.productsService.getProduct().subscribe((product) => { this.product = product });
This is fine when the code runs in a class that manages data, but in my opinion this should not be handled in the component. I may be wrong but i think the job of a component should be to ask for and display data without handling how the it is retrieved.
In the angular template you can do this to subscribe to and display the result of an observable:
<h1>{{ product.title | async }}</h1>
Is it possible to have something like this in the component class? My component displays a form and checks if a date is valid after input. Submitting the form is blocked until the value is valid and i want to keep all the logic behind it in the service which should subscribe to the AJAX call, the component only checks if it got a valid date.
class FormComponent {
datechangeCallback(date) {
this.dateIsValid$ = this.dateService.checkDate(date);
}
submit() {
if (this.dateIsValid$ === true) {
// handle form submission...
}
}
}
You can convert rxjs Observables to ES6 Promises and then use the async-await syntax to get the data without observable subscription.
Service:
export class DateService {
constructor(private http: HttpClient) {}
async isDateValid(date): Promise<boolean> {
let data = await this.http.post(url, date, httpOptions).toPromise();
let isValid: boolean;
// perform your validation and logic below and store the result in isValid variable
return isValid;
}
}
Component:
class FormComponent {
async datechangeCallback(date) {
this.dateIsValid = await this.dateService.isDateValid(date);
}
submit() {
if (this.dateIsValid) {
// handle form submission...
}
}
}
P.S:
If this is a simple HTTP request, which completes on receiving one value, then using Promises won't hurt. But if this obersvable produces some continuous stream of values, then using Promises isn't the best solution and you have to revert back to rxjs observables.
The cleanest way IMHO, using 7.4.0 < RxJS < 8
import { of, from, tap, firstValueFrom } from 'rxjs';
const asyncFoo = () => {
return from(
firstValueFrom(
of('World').pipe(
tap((foo) => {
console.info(foo);
})
)
)
);
};
asyncFoo();
// Outputs "World" once
asyncFoo().subscribe((foo) => console.info(foo));
// Outputs "World" twice
The "more cleanest" way would be having a factory (in some service) to build these optionally subscribeable function returns...
Something like this:
const buildObs = (obs) => {
return from(firstValueFrom(obs));
};
const asyncFoo = () => {
return buildObs(
of('World').pipe(
tap((foo) => {
console.info(foo);
})
)
);
};
Featherjs find service unable to pass extra parameters through find function. In below find service passing extra params data to service.
but unable to receive the value at service hook.
Client code :
return this.app.service('userlist').find({
query: { usersIds: { "$in" : [this.user._id]} },
paginate: false,
params:{ name:'sam' }
}).then(response => {
}).catch(error => {
console.log(error);
});
Server code (Service hook ) :
module.exports = function (options = {}) {
return async function dogetUsr (context) {
const { data } = context;
console.log('Client Param data -->',context.params.name);
return context;
};
};
params data not receiving at server -->
params:{ name:'sam' }
Output at server/service hook :
Client Param data -->undefined
For security reasons, only params.query is passed between the client and the server. In general I wouldn't recommend letting the client disable pagination unless you are guaranteed to only get a few (less than 100) records. Otherwise requests with many records can cause major issues on both sides.
If it is still something you need, you can use the disablePagination hook which lets you set the $limit to -1 if you want to disable pagination:
const { disablePagination } = require('feathers-hooks-common');
module.exports = { before: {
find: disablePagination()
} };
With React-Apollo, is it possible to refetch again until the fetched data has a certain value?
Say I have a component who keeps pinging the server until the server gives back a certain response.
graphql(gql`
query {
ping {
response
}
}
`)(MyComponent)
The server either returns
ping: {
response: "busy"
}
or
ping: {
response: "OK"
}
I want this component to keep pinging the server every one second (polling) until the response is "OK". What is the easiest way to do it with Apollo?
Basically all you need to do is set up a query with an option pollInterval and when you get the wanted response call the stopPolling function that arrives on the data in the props function. And make sure the fetchPolicy is set to 'network-only' that is compatible with polling.
Read about options.pollInterval here, about options.fetchPolicy here and about the structure of the data prop here.
This code should work for you:
const PingQuery = gql`
query {
ping {
response
}
}
`
graphql(PingQuery, {
options: {
fetchPolicy: 'network-only', // we don't want to get the response from the cache
pollInterval: 1000 // in milliseconds,
},
props: ({data: {loading, ping, stopPolling}) => {
if (loading) {
return // still loading, ping is undefined
}
if (ping.response === 'busy') {
// still busy
return
}
// not busy.. stop polling and do something
stopPolling()
}
})(MyComponent)
I may not have a perfect answer but I may have something to point you in the right direction.
The graphql() higher order component, as you are probably aware, takes a second parameter of options. You can specify things like a polling interval to continually repeat the query.
In this article, they explain how they were able to dynamically change this polling interval based on specific conditions.
https://dev-blog.apollodata.com/dynamic-graphql-polling-with-react-and-apollo-client-fb36e390d250
The example is using the recompose library, but I imagine you could do something similar like this.
import { graphql } from "react-apollo";
import gql from "graphql-tag";
import { compose, withState, lifecycle } from "recompose";
const QUERY = gql`
query {
ping {
response
}
}
`;
const withPing = compose(
withState("polling", "setPolling", true),
graphql(
QUERY,
{
options: props => {
if (props.polling === true) {
return {
pollInterval: 1000 // Repeat query every 1 second
};
} else {
return { } // or return nothing
}
}
}
),
lifecycle({
componentWillReceiveProps({
data: { loading, ping },
polling,
setPolling
}) {
if (loading) {
return;
}
if (ping.response === 'OK') {
return setPolling(false) // Stops polling
} else if (ping.response === 'busy') {
return setPolling(true) // Continues polling
}
}
})
);
const ComponentWithPing = withPing(Component);
I don't know if this would acctually work, but it should be close.
Another avenue you could checkout is the data.refetch() method on the data response object.
https://www.apollographql.com/docs/react/basics/queries.html#graphql-query-data-refetch.
Best of luck!
You can read more about the options here https://www.apollographql.com/docs/react/basics/queries.html#graphql-query-options and specifically the pollInterval here https://www.apollographql.com/docs/react/basics/queries.html#graphql-config-options-pollInterval
You may want to use Subscriptions.
Example with Hooks:
useSubscription(
gql`
subscription {
ping {
response
}
}
`
)
And of course, the useSubscription Hook accepts a second parameter for options, so you could set your arguments like this:
useSubscription(YOUR_SUBSCRIPTION, { variables: { foo: bar } })
I have a domain property and I want to validate two things;
URL exists (is reachable)
URL exists in my local DB.
In order to check these things I created to async validation rules using https://github.com/Knockout-Contrib/Knockout-Validation and applied both of them on my property.
What happens is that each time the response from one of the rules comes earlier and it sets isValidating property to false and I want this property to be true until the response from my second rule came.
Custom rules:
export function enableCustomValidators() {
(ko.validation.rules as any)["urlValidationServicePath"] = {
async: true,
validator: function (url: string, baseUrl: string, callback: any) {
getRequest(url, baseUrl, callback, "true");
},
message: 'You must enter a reachable domain.',
},
(ko.validation.rules as any)["customerValidationServicePath"] = {
async: true,
validator: function (url: string, baseUrl: string, callback: any) {
getRequest(url, baseUrl, callback, "false");
},
message: "This url already exists in our system. Please contact us at hello#ve.com",
}
ko.validation.registerExtenders();
}
function getRequest(url: string, baseUrl: string, callback: any, method: string) {
var restClient = new RestClient();
restClient.downloadString(baseUrl.concat(url), (responseText) => {
method === "true" ? callback(responseText === "true" ? true : false) :
callback(responseText === "true" ? false : true);
});
}
Using of the rules:
export class CompanySetupVM extends BasePageVM {
public websiteUrl: KnockoutObservable<string> = ko.observable(undefined);
public isValidating: KnockoutObservable<boolean> = ko.observable(false);
public constructor() {
this.websiteUrl.extend({
required: {
params: true,
message: CompanySetupVM.ErrorMessageNullWebsiteUrl
},
urlValidationServicePath: CompanySetupVM.DomainValidationPath,
customerValidationServicePath: CompanySetupVM.CustomerValidationPath
});
this.isValidating = ko.computed(() => this.websiteUrl.isValidating(), this);
}
}
In cshtml:
data-bind="text: currentPage().nextButtonText, css: {'button-overlay': currentPage().isValidating(), 'button': !currentPage().isValidating()}, click: nextAction"
I've looked at the source code of knockout validation (here) and it's pretty clear that two independent async validators are not supported.
The isValidating property is set to true as soon as an async rule is begins to run and set to false again as soon as that rule finishes. Therefore, multiple async rules clash.
There is only one solution. Remove the second async validator.
You can collapse the two checks into one either on the client side or on the server side.
To do it on the client side, you would need to write a validator that runs two Ajax requests and invokes the validation callback only after both of them have returned.
To do it on the server side, you would have to run the "is reachable" and "is in DB" checks in succession before giving an overall response to the client.
Personally I would prefer changing the server side, because
it keeps the client code tidy and manageable
it saves one HTTP round-trip per check
semantically, the URL check is one thing that fail for more than one reason
it's easy to let the server send a custom validation result and -message
Besides plain true or false, the validation plugin understands responses in this format:
{isValid: false, message: "something is wrong"}
So make your server send a JSON response with the appropriate validation result and error message and your REST client download JSON instead of text.
Then all you need to do is pass the server's response directly to the validation callback.
ko.validation.rules.urlValidationServicePath = {
async: true,
validator: function (url, baseUrl, callback) {
restClient.downloadJSON(baseUrl.concat(url), callback);
},
message: 'The URL you entered is not valid.'
};
Here the message is only a default. The server's message always takes precedence over the setting in the validation rule.
Yes, as Tomalak pointed out it is not possible to have multiple async validators. But I solved it on the client side and solution is quite manageable and flexible IMHO.
The trick here is to implement different async validators as regular knockout extenders and having single async rule to call them. Here is the async rule:
interface HasAsyncValidator {
asyncValidators: Validator[];
}
interface Validator {
name: string,
validator: (params: any) => boolean | PromiseLike<any>,
params: any
}
interface KnockoutObservable<T> extends HasAsyncValidator {}
ko.validation.rules["validateAsync"] = {
validator: async (value: any, paramsAccessor: () => HasAsyncValidator, callback: (result: boolean | ValidationResult) => void) => {
const params = paramsAccessor();
if (!params || !params.asyncValidators) {
callback(true);
return;
}
try {
const results = await Promise.all(params.asyncValidators.map(v => v.validator(v.params)));
const invalidResult = results.find(r => r.isValid === false);
callback(!!invalidResult ? invalidResult : true);
} catch (error) {
callback(false);
throw error;
}
},
message: 'default message',
async: true
}
As you can see, we extended observable with asyncValidators property, which keeps all the registered validators. All that is left for the rule is just call validators (if any) and then pass result to the knockout validation callback.
Here is an example of validator as regular extender:
ko.extenders["validationRule"] = (target: any, option: any) => {
const validatorObj: Validator = {
name: "validationRule",
params: option,
validator: async (): Promise<boolean | ValidationResult> => {
const unwrappedValue = ko.unwrap(target);
const result = await callServer();
return {
isValid: result.isValid,
message: result.message
};
}
}
addOrUpdateAsyncValidator(target, validatorObj);
};
function addOrUpdateAsyncValidator(target: HasAsyncValidator, validatorObj: Validator) {
target.asyncValidators = target.asyncValidators || [];
const existingRule = target.asyncValidators.find(v => v.name == validatorObj.name);
!!existingRule
? existingRule!.params = validatorObj.params
: target.asyncValidators.push(validatorObj);
}
Note that each validator must register itself to asyncValidators property on observable.
The usage of this solution is quite straightforward:
let value = ko.observable();
value.extend({ validationRule: true, validateAsync: () => value });
Note that we should pass value accessor to the validateAsync instead of value itself. That is needed so async rule don't miss validators that can be added later.
I want to post data to a server.. my action is like this:
export function addNewTodo(text) {
return {
type: 'ADD_NEW_TODO',
payload: addNewTodoApi(text)
};
}
let addNewTodoApi = function(text) {
return new Promise(function(resolve, reject) {
//implement fake method for get data from server
setTimeout(function () {
resolve({
text: text,
done: ??,
assignTo: ??,
Project: ??,
Category: ??,
id: ??
});
}, 10);
});
};
I have three way. The first way is import store and and call getState method in my action and the second way is dispatch action in reducer and the last way is pass all data in my action as argument. Which one is correct? I read this question and I'm worried about antipatterns.
You should consider using import { connect } from 'react-redux' and using containers.
You access your store on function mapStateToProps, you send all your data for your action from the container.
The signature for it is
const mapStateToProps = (state, ownProps) => {}
There you get the state which a part could be passed to your action.
The action perform post to your api using ajax with data you passed from state.
In your action your could consider using isomorphic-fetch.
Quick example:
import 'isomorphic-fetch'
const postData = ()=> ({
type: types.POST_YOURACTION,
payload: new Promise((resolve, reject) => {
fetch(api.postData(), {method: 'POST', }).then(response => {
resolve(response.json())
})
})
})
In case if you need to access the store directly you could use .getState(), accessing state directly could be considered an anti-pattern but it really depends on your architecture design.
I would suggest to look look at mapStateToProps.
Example of how to access your store.
import store from 'app/store'
store.getState().yourReducer.data
I always use the third way, pass data to the action. The data can be the data in store.getState() or from params or whatever you need to get data from server.