Relationship between SvelteKit PageData and ActionData for form validation - sveltekit

// +page.svelte
/** #type {import('./$types').PageData} */
export let data;
/** #type {import('./$types').ActionData} */
export let form;
/** #type {import("svelte/store").Writable<Entity>} */
const entity = writable(form?.entity ?? data.entity);
/** #type {import("svelte/store").Readable<ValidationResult>} */
const validations = derived(entity, e => async validate(e));
The above is the pattern I’ve come up to do form validation with SvelteKit. I bind form elements to the $entity store, regardless if it was populated from the page data or the form action. The form?.entity ?? data.entity feels a little hacky. (Or maybe just clever?)
It seems to work, but is this going to cause problems? Should I be thinking about it differently?

Related

Missing JSDoc #param "props.children" type. and Missing JSDoc #param "props.children" type. with React Native FunctionalComponents

I'm using JSDoc & TSDoc on a react native project.
Here is the file:
import React, { createContext, useContext, FC } from 'react'
import useUserData, { UseUserDataType } from './useUserData'
import { AuthContext } from './FirebaseContextProvider'
import { AuthUser } from '../../common'
// Initial state
const initialUserDataContext = {
userData: {},
} as UseUserDataType
// Create the context objects
export const UserDataContext = createContext<UseUserDataType>(initialUserDataContext)
/**
* Context provider for user data from firebase
*
* #param props -
* #param props.children - application parts that are dependent on user data.
* #returns a context provider for user data
*/
const DataContextProvider: FC = ({ children }) => {
const { user } = useContext(AuthContext)
// UserData
const userData = useUserData(user as AuthUser)
return (
<UserDataContext.Provider value={userData}>
{children}
</UserDataContext.Provider>
)
}
export default DataContextProvider
I have two warnings:
On the second #param:
tsdoc-param-tag-with-invalid-name: The #param block should be followed by a valid parameter name: The identifier cannot non-word characterseslinttsdoc/syntax
On the begining of the JSDoc lines:
Missing JSDoc #param "props.children" type.eslintjsdoc/require-param-type
I'm not really getting how I shall document the props I guess. Any insights ?
Thanks
Edit:
If I add the types as #param {Type}, TSDoc complain because it is TypeScript:
tsdoc-param-tag-with-invalid-type: The #param block should not include a JSDoc-style '{type}'eslinttsdoc/syntax
For the time being I deleted the rules, waiting to see if there is a better configuration.
'jsdoc/require-returns-type': 'off',
'jsdoc/require-returns-type': 'off',
This might be a little old but for those who run into similar issues.
Sheraff was right in the comment so I'd like to expand.
Let's first look at the error/s:
Missing JSDoc #param "props.children" type.
So that means you are missing the parameters type.
Then:
tsdoc-param-tag-with-invalid-name: The #param block should be followed by a valid parameter name: The identifier cannot non-word characterseslinttsdoc/syntax
This means this:
* #param props.children - application parts that are dependent on user data.
Should be this without text after the param name:
* #param {Object} children
Then finally this:
tsdoc-param-tag-with-invalid-type: The #param block should not include a JSDoc-style '{type}'eslinttsdoc/syntax
This means you have added a type - {type} - which is not a recognised type.
A valid type means String, Int, Object and this is NOT because you are using TS. It's JSDoc needs them signatured.
So to fix I believe the below would do it:
/**
* Context provider for user data from firebase
*
* #param {Object} children
* #returns a context provider for user data
*/

Property 'calendar' does not exist on type 'typeof client'

I'm trying to connect my Google Calender to my React website. I've got a component called Calendar. I've used the JS tutorial from Google and I've changed it to work in Typescript. I've got the authentication and authorization already working, however fetching data from the calendar is not working. I'm getting the following error when compiling/editing.
[ts] Property 'calendar' does not exist on type 'typeof client'. Did you mean 'calendars'?
I've already downloaded the types for the gapi.client.calendar and as you can see in the image below, they are also found in the #types folder. I'm kind of stuck and I don't know how I can fix this issue..
Here is my code from my Calendar.tsx
import * as React from 'react';
import { Button } from 'semantic-ui-react'
import googleApiKey from '../googleapi-key.json';
const CLIENT_ID = googleApiKey.CLIENT_ID;
const API_KEY = googleApiKey.API_KEY;
const DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"];
const SCOPES = "https://www.googleapis.com/auth/calendar.readonly";
class Calendar extends React.Component {
constructor(props: any) {
super(props);
console.log(CLIENT_ID);
console.log(API_KEY);
this.handleClientLoad = this.handleClientLoad.bind(this);
this.handleAuthClick = this.handleAuthClick.bind(this);
this.handleSignoutClick = this.handleSignoutClick.bind(this);
this.initClient = this.initClient.bind(this);
this.updateSigninStatus = this.updateSigninStatus.bind(this);
this.listUpcomingEvents = this.listUpcomingEvents.bind(this);
}
componentDidMount() {
this.initClient();
}
public render() {
return (
<div>
<Button onClick={this.handleAuthClick}>
authorizeButton
</Button>
<Button onClick={this.handleSignoutClick}>
signoutButton
</Button>
</div>
);
}
/**
* On load, called to load the auth2 library and API client library.
*/
public handleClientLoad() {
gapi.load('client:auth2', this.initClient);
}
/**
* Sign in the user upon button click.
*/
public handleAuthClick(event: any) {
gapi.auth2.getAuthInstance().signIn();
}
/**
* Sign out the user upon button click.
*/
public handleSignoutClick(event: any) {
gapi.auth2.getAuthInstance().signOut();
}
/**
* Initializes the API client library and sets up sign-in state
* listeners.
*/
public async initClient() {
await gapi.client.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES
})
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(this.updateSigninStatus);
// Handle the initial sign-in state.
this.updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
}
/**
* Called when the signed in status changes, to update the UI
* appropriately. After a sign-in, the API is called.
*/
public updateSigninStatus(isSignedIn: any) {
if (isSignedIn) {
this.listUpcomingEvents();
}
}
/**
* Print the summary and start datetime/date of the next ten events in
* the authorized user's calendar. If no events are found an
* appropriate message is printed.
*/
public listUpcomingEvents() {
console.log(gapi.client.calendar); // <--- Compile error: Does not recognize calendar
}
}
export default Calendar;
EDIT
When performing console.log(gapi.client) I can see that the client contains a calendar object (see image). But why can't I reach it in my own code?
I managed to fix my own problem. After performing console.log(gapi.client) I noticed that calender was already there, so I tried the following gapi.client['calendar'] and it worked as it should. I don't know why Typescript does not recognize the calendar in the first place, so if anybody has an idea feel free to leave a comment.
Try the following
Install types npm i #types/gapi.client.calendar
Include https://apis.google.com/js/api.js & https://apis.google.com/js/platform.js in index.html
Add the following inside types in tsconfig.app.json
"types": [
"gapi",
"gapi.auth2",
"gapi.client",
"gapi.client.calendar"
]
You have to add:
"types": ["gapi", "gapi.auth2", "gapi.client", "gapi.client.calendar"]
in tsconfig.app.js and in tsconfig.json.

Redux observable - resolving multiple actions - async

I'm trying to inject a third party script onto a page and once its loaded initialise a video player.
I want to be able to have multiple video players on any page so I have some logic to check whether the script is already loaded and fire a different actions depending on whether it is or not.
My "script loading" helper looks like this:
/**
* injectScript - Inject player script into page
* #param {String} id - script tag id
* #param {String} src - script src
* #return {Promise} - script load callback
*/
injectScript (id, src) {
return new Promise(resolve => {
if (!document.getElementById(id)) {
const script = document.createElement('script');
script.setAttribute('id', id);
script.type = 'text/javascript';
script.src = src;
document.body.appendChild(script);
script.onload = () => resolve('script-loaded');
} else {
resolve('script-exists');
}
});
}
I then have an epic which calls this helper and fires an action depending on the outcome. The epic looks like this:
/**
* loadPlayerScript
* #param {Object} action$ - action observable
* #param {Object} store - redux store
* #param {Object} dependencies - injected dependencies
* #return {Object} - action observable
*/
export default function loadPlayerScript (action$, store, { scriptLoaderHelper }) {
return action$.ofType(PLAYER_SCRIPT_LOAD)
.switchMap(action => Observable.fromPromise(scriptLoaderHelper.injectScript(action.data.id, action.data.script)))
.map(data => {
if (data === 'script-loaded') {
return playerScriptSuccess();
}
return playerScriptExists();
})
.catch(error => Observable.of(playerScriptFailure(error)));
}
The problem:
The PLAYER_SCRIPT_LOAD gets fired twice (I can validate this in Redux dev tools), this is expected. Whats happening though is the epic is only resolving the promise once (and it only fires one of the actions in the map). How do I get the epic to fire the actions for each promise resolve?
I'm sure its something super simple that I'm overlooking but any helps is appreciated!
Change switchMap to mergeMap to avoid cancellation:
The main difference between switchMap and other flattening operators is the cancelling effect. On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed. You can remember this by the phrase switch to a new observable.
source: https://www.learnrxjs.io/operators/transformation/switchmap.html

Relay Modern: Connecting websocket to network layer

I’m having issues figuring out how to connect the Relay Modern network layer with my websocket instance.
I’m currently instantiating a websocket instance as:
const subscriptionWebSocket = new ReconnectingWebSocket('ws://url.url/ws/subscriptions/', null, options);
I'm specifying the subscription and creating a new instance of requestSubscription:
const subscription = graphql`
subscription mainSubscription {
testData {
anotherNode {
data
}
}
}
`;
requestSubscription(
environment,
{
subscription,
variables: {},
onComplete: () => {...},
onError: (error) => {...},
onNext: (response) => {...},
updater: (updaterStoreConfig) => {...},
},
);
Which then allows me to send any subscription requests:
function subscriptionHandler(subscriptionConfig, variables, cacheConfig, observer) {
subscriptionWebSocket.send(JSON.stringify(subscriptionConfig.text));
return {
dispose: () => {
console.log('subscriptionHandler: Disposing subscription');
},
};
}
const network = Network.create(fetchQuery, subscriptionHandler);
through to my server (currently using Graphene-python), and I’m able to interpret the received message on the server.
However, what I’m having issues figuring out is how to respond to a subscription; for example, when something changes in my DB, I want to generate a response and return to any potential subscribers.
The question being, how do I connect the onMessage event from my websocket instance into my Relay Modern Network Layer? I've browsed through the source for relay but can't seem to figure out what callback, or what method should be implementing an onreceive.
Any tips are appreciated.
I've managed to make subscriptions with Relay Modern work as well and wanted to share my minimal setup, maybe it's helpful for someone!
Note that I'm not using WebSocket but the SubscriptionClient that can be found in subscriptions-transport-ws to manage the connection to the server.
Here's my minimal setup code:
Environment.js
import { SubscriptionClient } from 'subscriptions-transport-ws'
const {
Environment,
Network,
RecordSource,
Store,
} = require('relay-runtime')
const store = new Store(new RecordSource())
const fetchQuery = (operation, variables) => {
return fetch('https://api.graph.cool/relay/v1/__GRAPHCOOL_PROJECT_ID__', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then(response => {
return response.json()
})
}
const websocketURL = 'wss://subscriptions.graph.cool/v1/__GRAPHCOOL_PROJECT_ID__'
function setupSubscription(
config,
variables,
cacheConfig,
observer,
) {
const query = config.text
const subscriptionClient = new SubscriptionClient(websocketURL, {reconnect: true})
const id = subscriptionClient.subscribe({query, variables}, (error, result) => {
observer.onNext({data: result})
})
}
const network = Network.create(fetchQuery, setupSubscription)
const environment = new Environment({
network,
store,
})
export default environment
NewLinkSubscription.js
import {
graphql,
requestSubscription
} from 'react-relay'
import environment from '../Environment'
const newLinkSubscription = graphql`
subscription NewLinkSubscription {
Link {
mutation
node {
id
description
url
createdAt
postedBy {
id
name
}
}
}
}
`
export default (onNext, onError, onCompleted, updater) => {
const subscriptionConfig = {
subscription: newLinkSubscription,
variables: {},
onError,
onNext,
onCompleted,
updater
}
requestSubscription(
environment,
subscriptionConfig
)
}
Now you can simply use the exported function to subscribe. For example, in one of my React components in componentDidMount I can now do the following:
componentDidMount() {
NewLinkSubscription(
response => console.log(`Received data: `, response),
error => console.log(`An error occurred:`, error),
() => console.log(`Completed`)
)
}
Note that the SubscriptionClient can only be used if your server implements this protocol!
If you want to learn more, check out the fullstack How to GraphQL tutorial that describes in detail how to make subscriptions work with Relay Modern.
I’ll just write down how I’ve approached this issue after the assistance found in this thread. It might be usable for someone else. This is very dependent on the server-side solution that you've chosen.
My approach:
Firstly I built a SubscriptionHandler that will handle the requestStream#subscribeFunction through SubscriptionHandler#setupSubscription.
The SubscriptionHandler instantiates a WebSocket (using a custom version of ReconnectingWebSockets) and attaches the onmessage event to an internal method (SubscriptionHandler#receiveSubscriptionPayload) which will add the payload to the corresponding request.
We create new subscriptions through SubscriptionHandler#newSubscription which will use the internal attribute SubscriptionHandler.subscriptions to add a keyed entry of this subscription (we use an MD5-hash util over the query and variables); meaning the object will come out as:
SubscriptionHandler.subscriptions = {
[md5hash]: {
query: QueryObject,
variables: SubscriptionVariables,
observer: Observer (contains OnNext method)
}
Whenever the server sends a subscription response the SubscriptionHandler#receiveSubscriptionPayload method will be called and it will identify what subscription the payload belongs to by using the query/variables md5 hash, then use the SubscriptionHandler.subscriptions observer onNext method.
This approach requires the server to return a message such as:
export type ServerResponseMessageParsed = {
payload: QueryPayload,
request: {
query: string,
variables: Object,
}
}
I do not know if this is a great way of handling subscriptions, but it works for now with my current setup.
SubscriptionHandler.js
class SubscriptionHandler {
subscriptions: Object;
subscriptionEnvironment: RelayModernEnvironment;
websocket: Object;
/**
* The SubscriptionHandler constructor. Will setup a new websocket and bind
* it to internal method to handle receving messages from the ws server.
*
* #param {string} websocketUrl - The WebSocket URL to listen to.
* #param {Object} webSocketSettings - The options object.
* See ReconnectingWebSocket.
*/
constructor(websocketUrl: string, webSocketSettings: WebSocketSettings) {
// All subscription hashes and objects will be stored in the
// this.subscriptions attribute on the subscription handler.
this.subscriptions = {};
// Store the given environment internally to be reused when registering new
// subscriptions. This is required as per the requestRelaySubscription spec
// (method requestSubscription).
this.subscriptionEnvironment = null;
// Create a new WebSocket instance to be able to receive messages on the
// given URL. Always opt for default protocol for the RWS, second arg.
this.websocket = new ReconnectingWebSocket(
websocketUrl,
null, // Protocol.
webSocketSettings,
);
// Bind an internal method to handle incoming messages from the websocket.
this.websocket.onmessage = this.receiveSubscriptionPayload;
}
/**
* Method to attach the Relay Environment to the subscription handler.
* This is required as the Network needs to be instantiated with the
* SubscriptionHandler's methods, and the Environment needs the Network Layer.
*
* #param {Object} environment - The apps environment.
*/
attachEnvironment = (environment: RelayModernEnvironment) => {
this.subscriptionEnvironment = environment;
}
/**
* Generates a hash from a given query and variable pair. The method
* used is a recreatable MD5 hash, which is used as a 'key' for the given
* subscription. Using the MD5 hash we can identify what subscription is valid
* based on the query/variable given from the server.
*
* #param {string} query - A string representation of the subscription.
* #param {Object} variables - An object containing all variables used
* in the query.
* #return {string} The MD5 hash of the query and variables.
*/
getHash = (query: string, variables: HashVariables) => {
const queryString = query.replace(/\s+/gm, '');
const variablesString = JSON.stringify(variables);
const hash = md5(queryString + variablesString).toString();
return hash;
}
/**
* Method to be bound to the class websocket instance. The method will be
* called each time the WebSocket receives a message on the subscribed URL
* (see this.websocket options).
*
* #param {string} message - The message received from the websocket.
*/
receiveSubscriptionPayload = (message: ServerResponseMessage) => {
const response: ServerResponseMessageParsed = JSON.parse(message.data);
const { query, variables } = response.request;
const hash = this.getHash(query, variables);
// Fetch the subscription instance from the subscription handlers stored
// subscriptions.
const subscription = this.subscriptions[hash];
if (subscription) {
// Execute the onNext method with the received payload after validating
// that the received hash is currently stored. If a diff occurs, meaning
// no hash is stored for the received response, ignore the execution.
subscription.observer.onNext(response.payload);
} else {
console.warn(Received payload for unregistered hash: ${hash});
}
}
/**
* Method to generate new subscriptions that will be bound to the
* SubscriptionHandler's environment and will be stored internally in the
* instantiated handler object.
*
* #param {string} subscriptionQuery - The query to subscribe to. Needs to
* be a validated subscription type.
* #param {Object} variables - The variables for the passed query.
* #param {Object} configs - A subscription configuration. If
* override is required.
*/
newSubscription = (
subscriptionQuery: GraphQLTaggedNode,
variables: Variables,
configs: GraphQLSubscriptionConfig,
) => {
const config = configs || DEFAULT_CONFIG;
requestSubscription(
this.subscriptionEnvironment,
{
subscription: subscriptionQuery,
variables: {},
...config,
},
);
}
setupSubscription = (
config: ConcreteBatch,
variables: Variables,
cacheConfig: ?CacheConfig,
observer: Observer,
) => {
const query = config.text;
// Get the hash from the given subscriptionQuery and variables. Used to
// identify this specific subscription.
const hash = this.getHash(query, variables);
// Store the newly created subscription request internally to be re-used
// upon message receival or local data updates.
this.subscriptions[hash] = { query, variables };
const subscription = this.subscriptions[hash];
subscription.observer = observer;
// Temp fix to avoid WS Connection state.
setTimeout(() => {
this.websocket.send(JSON.stringify({ query, variables }));
}, 100);
}
}
const subscriptionHandler = new SubscriptionHandler(WS_URL, WS_OPTIONS);
export default subscriptionHandler;
For anyone stumbling across this recently, I did not have success with either the solutions above because of recent updates in the libraries involved. Yet they were a great source to start and I put up together a small example based on the official relay modern todo example, it is very minimalistic and uses helpers libraries from Apollo but works well with relay modern:
https://github.com/jeremy-colin/relay-examples-subscription
It includes both server and client
Hope it can help
I think this repo would fit your needs.
Helps you creating your subscriptions server-side

changing ZF2 form behavior when retrieving form

I'm wondering if there is a way to either pass additional parameters to the constructor (preferred) or retrieve the Request object to check the headers from within the Form constructor so that when I do a getForm in a controller, the form will be customized depending on how it is called?
I'm working on integrating AngularJs bindings and model tags into my form elements but I will need to modify how the submit button works whenever a form is called from Ajax vs being pulled into a Zend template via the framework.
Thus I would like to throw conditional parameters around where the submit button is added to the form, but I need to know if the rendered form is being viewed in zend or is being sent via an ajax call. I can detect the ajax call in the controller by looking at the request headers with isXmlHttpRequest(), but I'm not sure how to let the form know what the controller saw when it's retrieving the form with $this->getForm()
You can inject any options you like using a factory class.
use MyModule\Form\MyForm;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class MyFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $formElementManager)
{
$serviceManager = $formElementManager->getServiceLocator();
$request = $serviceManager->get('Request');
// I would recommend assigning the data
// from the request to the options array
$options = [
'is_ajax' => $request->isXmlHttpRequest(),
];
// Although you could also pass in the request instance into the form
return new MyForm('my_form', $options, $request);
}
}
If you inject the request you will need modify MyForm::__construct.
namespace MyModule\Form;
use Zend\Form\Form;
use Zend\Http\Request as HttpRequest;
class MyForm extends Form
{
protected $request;
public function __construct($name, $options, HttpRequest $request)
{
$this->request = $request;
parent::__construct($name, $options);
}
}
Update your module.config.php to use the factory
return [
'form_elements' => [
'factories' => [
'MyModule\Form\MyForm' => 'MyModule\Form\MyFormFactory'
]
]
]
Then ensure you request the form from the service manager (in a controller factory)
$myForm = $serviceManager->get('FormElementManager')->get('MyModule\Form\MyForm');
My AbstractForm with helper functions (I just added the getRequest() to the bottom). Of course in a wider scale application I'd probably add error checking to make sure these were not called from the constructor (when the service manager would not yet be available)
namespace Application\Form;
use Zend\Form\Form as ZendForm;
use Zend\Http\Request as HttpRequest;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Form\FormElementManager as ZendFormElementManager;
use Zend\ServiceManager\ServiceLocatorAwareInterface as ZendServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface as ZendServiceLocatorInterface;
use Doctrine\ORM\EntityManager as DoctrineEntityManager
class AbstractForm extends ZendForm implements ZendServiceLocatorAwareInterface {
/**
* #var Request
*/
protected $request;
/**
* in form context this turns out to be Zend\Form\FormElementManager
*
* #var ZendFormElementManager $service_manager
*/
protected static $service_manager;
/**
* #var DoctrineEntityManager $entity_manager
*/
protected $entity_manager;
/**
* #var ZendServiceLocatorInterface $service_locator_interface
*/
protected $service_locator_interface;
public function __construct($name = null)
{
parent::__construct($name);
}
/**
* in form context this turns out to be Zend\Form\FormElementManager
*
* #param ZendFormElementManager $serviceLocator
*/
public function setServiceLocator(FormElementManager $serviceLocator)
{
self::$service_manager = $serviceLocator;
}
/**
* in form context this turns out to be Zend\Form\FormElementManager
*
* #return ZendFormElementManager
*/
public function getServiceLocator()
{
return self::$service_manager;
}
/**
* wrapper for getServiceLocator
* #return ZendFormElementManager
*/
protected function getFormElementManager() {
return $this->getServiceLocator();
}
/**
* this returns an actual service aware interface
*
* #return ZendServiceLocatorInterface
*/
protected function getServiceManager() {
if(!($this->service_locator_interface instanceof ZendServiceLocatorInterface)) {
$this->service_locator_interface = $this->getFormElementManager()->getServiceLocator();
}
return $this->service_locator_interface;
}
/**
* #return DoctrineEntityManager
*/
protected function getEntityManager() {
if(!($this->entity_manager instanceof \DoctrineEntityManager)) {
$this->entity_manager = $this->getServiceLocator()->getServiceLocator()->get('Doctrine\ORM\EntityManager');
}
return $this->entity_manager;
}
/**
* Get request object
*
* #return Request
*/
public function getRequest()
{
if (!$this->request) {
$this->request = $this->getServiceManager()->get('Request');
}
return $this->request;
}
}

Resources