Bind component state with Apollo local state - apollo

I want to use Apollo local state to keep the information to track if the user logged in or not.
So I modified the Apollo client with clientState as follows.
clientState: {
defaults: {
loggedIn: 'false',
},
resolvers: {
Mutation: {
toggleLoggedIn(_: any, variables: any, { cache }: any) {
// read the cart open value from the cache
const { loggedIn } = cache.readQuery({
query: LOGIN_STATE_QUERY,
});
const data = {
data: {
loggedIn: !loggedIn,
},
};
cache.writeData(data);
return data;
},
},
},
},
I am planning to use two routers for my application.
and
so that here is my application state.
App.js file
const { loading, user } = this.state;
<ApolloProvider client={client}>
{user ? <AppRouter /> : <SessionRouter />}
</ApolloProvider>
State of the app.js file
this.state = {
loading: true,
user: null,
};
What I want to do is bind the state of the app component with the apollo local state. so that mutating the local state "loggedIn" variable will rerender with the related router (Session router or Approuter)
if there is ambiguity in this question please feel free to ask in the comments.

If you really want to bind Apollo state to component state, you could do the following:
Create a container component that performs the local state query. When the local state query receives the data, render the child component with said data as a prop.
class container extends React.Component {
render() {
return (
<Query query={login_query}>
{(data, loading) => {
if(loading) return;
return <LoginComponent data={data} />
}}
)
}
}

Related

SvelteKit Pass Data From Server to Browser

I am trying to pass data from the server to the client to load my app faster and prevent multiple calls to the database.
Via Fetch
SvelteKit is made to do this via the fetch function. This is great if you have an endpoint that allows for custom fetch. But what if you don't?
Firebase is a perfect example of not having a custom fetch function.
Cookies
I would think I could use cookies, but when I set the cookie, it just prints 'undefined' and never gets set.
<script lang="ts" context="module">
import Cookies from 'js-cookie';
import { browser } from '$app/env';
import { getResources } from '../modules/resource';
export async function load() {
if (browser) {
// working code would use JSON.parse
const c = Cookies.get('r');
return {
props: {
resources: c
}
};
} else {
// server
const r = await getResources();
// working code would use JSON.stringify
Cookies.set('resources', r);
// no cookies were set?
console.log(Cookies.get());
return {
props: {
resources: r
}
};
}
}
</script>
So my code loads correctly, then dissapears when the browser load function is loaded...
Surely there is a functioning way to do this?
J
So it seems the official answer by Rich Harris is to use and a rest api endpoint AND fetch.
routes/something.ts
import { getFirebaseDoc } from "../modules/posts";
export async function get() {
return {
body: await getFirebaseDoc()
};
}
routes/content.svelte
export async function load({ fetch }) {
const res = await fetch('/resources');
if (res.ok) {
return {
props: { resources: await res.json() }
};
}
return {
status: res.status,
error: new Error()
};
}
This seems extraneous and problematic as I speak of here, but it also seems like the only way.
J
You need to use a handler that injects the cookie into the server response (because load functions do not expose the request or headers to the browser, they are just used for loading props I believe). Example here: https://github.com/sveltejs/kit/blob/59358960ff2c32d714c47957a2350f459b9ccba8/packages/kit/test/apps/basics/src/hooks.js#L42
https://kit.svelte.dev/docs/hooks#handle
export async function handle({ event, resolve }) {
event.locals.user = await getUserInformation(event.request.headers.get('cookie'));
const response = await resolve(event);
response.headers.set('x-custom-header', 'potato');
response.headers.append('set-cookie', 'name=SvelteKit; path=/; HttpOnly');
return response;
}
FYI: This functionality was only added 11 days ago in #sveltejs/kit#1.0.0-next.267: https://github.com/sveltejs/kit/pull/3631
No need to use fetch!
You can get the data however you like!
<script context="module">
import db from '$/firebaseConfig'
export async function load() {
const eventref = db.ref('cats/whiskers');
const snapshot = await eventref.once('value');
const res = snapshot.val();
return { props: { myData: res.data } } // return data under `props` key will be passed to component
}
</script>
<script>
export let myData //data gets injected into your component
</script>
<pre>{JSON.stringify(myData, null, 4)}</pre>
Here's a quick demo on how to fetch data using axios, same principle applies for firebase: https://stackblitz.com/edit/sveltejs-kit-template-default-bpr1uq?file=src/routes/index.svelte
If you want to only load data on the server you should use an "endpoint" (https://kit.svelte.dev/docs/routing#endpoints)
My solution might solve it especially for those who work with (e.g: laravel_session), actually in your case if you want to retain the cookie data when loading on each endpoint.
What you should gonna do is to create an interface to pass the event on every api() call
interface ApiParams {
method: string;
event: RequestEvent<Record<string, string>>;
resource?: string;
data?: Record<string, unknown>;
}
Now we need to modify the default sveltekit api(), provide the whole event.
// localhost:3000/users
export const get: RequestHandler = async (event) => {
const response = await api({method: 'get', resource: 'users', event});
// ...
});
Inside your api() function, set your event.locals but make sure to update your app.d.ts
// app.d.ts
declare namespace App {
interface Locals {
r: string;
}
//...
}
// api.ts
export async function api(params: ApiParams) {
// ...
params.event.locals.r = response.headers.get('r')
});
Lastly, update your hooks.ts
/** #type {import('#sveltejs/kit').Handle} */
export const handle: Handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
const response = await resolve(event);
if (!cookies.whatevercookie && event.locals.r) {
response.headers.set(
'set-cookie',
cookie.serialize('whatevercookie', event.locals.r, {
path: '/',
httpOnly: true
})
);
}
return response;
});
Refer to my project:
hooks.ts
app.d.ts
_api.ts
index.ts

What is the best way to mock ember services that use ember-ajax in ember-cli-storybook to post and fetch data?

I'm using Ember CLI Storybook to create a story of a component than internally relies upon services that communicate to the internet, to fetch and post information to the backend. The way I'm doing that is using ember-ajax.
I see how to mock an ember model from this section but wondering if there is a workaround for ember ajax service.
I like to use mswjs.io for mocking remote requests. It uses a service worker so you can still use your network log as if you still used your real API.
I have an example repo here showing how to set it up: https://github.com/NullVoxPopuli/ember-data-resources/
But I'll copy the code, in case I change something.
Now, in tests, you'd want something like this: https://github.com/NullVoxPopuli/ember-data-resources/blob/main/tests/unit/find-record-test.ts#L17
module('findRecord', function (hooks) {
setupMockData(hooks);
But since you're using storybook, you'd instead want the contents of that function. (And without the setup/teardown hooks unique to tests)
https://github.com/NullVoxPopuli/ember-data-resources/blob/main/tests/unit/-mock-data.ts#L22
import { rest, setupWorker } from 'msw';
let worker;
export async function setupMockData() {
if (!worker) {
worker = setupWorker();
await worker.start();
// artificial timeout "just in case" worker takes a bit to boot
await new Promise((resolve) => setTimeout(resolve, 1000));
worker.printHandlers();
}
let data = [
{ id: '1', type: 'blogs', attributes: { name: `name:1` } },
{ id: '2', type: 'blogs', attributes: { name: `name:2` } },
{ id: '3', type: 'blogs', attributes: { name: `name:3` } },
];
worker.use(
rest.get('/blogs', (req, res, ctx) => {
let id = req.url.searchParams.get('q[id]');
if (id) {
let record = data.find((datum) => datum.id === id);
return res(ctx.json({ data: record }));
}
return res(ctx.json({ data }));
}),
rest.get('/blogs/:id', (req, res, ctx) => {
let { id } = req.params;
let record = data.find((datum) => datum.id === id);
if (record) {
return res(ctx.json({ data: record }));
}
return res(
ctx.status(404),
ctx.json({ errors: [{ status: '404', detail: 'Blog not found' }] })
);
})
);
}
Docs for msw: https://mswjs.io/

How to use getServerSideProps for every pages in next.js?

I have set a cookie with nookies which store the values of all the products selected by user.
I want to fetch the cookie in server side using getServerSideProps and pass the value as props. I have to display the value of cookie on all pages.
When I tried getServerSideProps in _app.js. It did not worked and it did not even run the code.
Is there any way to do it?
As of now, there isn't a built-in way to do it, so I've resorted to doing the following.
First, I created a file that holds the getServerSideProps function I want to run on every page:
// lib/serverProps.js
export default async function getServerSideProps(ctx) {
// do something
return {
// data
};
}
Then in every page (yes, every, I can't find a workaround; it might even be helpful if you don't need the code to execute on server pages), do:
import getServerSideProps from "../lib/serverProps";
// other stuff...
export { getServerSideProps };
or
// other stuff...
export { default as getServerSideProps } from "../lib/serverProps";
If you want to add other code to run inside getServerSideProps for a specific page, you could do something along the lines...
import serverProps from "../lib/serverProps";
// other stuff...
export async function getServerSideProps(ctx) {
// do custom page stuff...
return {
...await serverProps(ctx),
...{
// pretend this is what you put inside
// the return block regularly, e.g.
props: { junk: 347 }
}
};
}
getServerSideProps does not work in _app.js. see docs.
you could use the older getInitialProps in your custom app component but then the automatic static optimisation is disabled, which is something Next.js bets on heavily.
it might be worth digging into your cookie use case and figure out if you really need to read it on the server side.
For those wanting to share state received from a page's getServerSideProps function to global components in pages/_app.tsx, I've pieced this solution together.
Create a shared getServerSideProps function to include on all pages
Create a shared useSetUserStorage custom hook to include on all pages
Listen for localStorage changes with custom event listener in global component (e.g. GlobalNav)
It's a work around, but is working for me so far (note that it includes some specifics to my use of getServerSideProps function).
It's a fair amount of code but hopefully this helps someone:
// src/pages/_app.tsx
import type { AppProps } from "next/app";
import GlobalNav from "../components/GlobalNav";
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<>
<GlobalNav /> // <— your global component
<Component {...pageProps} />
</>
);
}
export default MyApp;
// src/utils/getServerSideProps.ts
import { ppinit, ppsession, sess } from "../authMiddleware";
import nc from "next-connect";
import { NextApiRequest, NextApiResponse } from "next";
import { User } from "../types/types";
interface ExtendedReq extends NextApiRequest {
user: User;
}
interface ServerProps {
req: ExtendedReq;
res: NextApiResponse;
}
interface ServerPropsReturn {
user?: User;
}
//
// Here we use middleware to augment the `req` with the user from passport.js
// to pass to the page
// src: https://github.com/hoangvvo/next-connect/tree/21c9c73fe3746e66033fd51e2aa01d479e267ad6#runreq-res
//
const getServerSideProps = async ({ req, res }: ServerProps) => {
// ADD YOUR CUSTOM `getServerSideProps` code here
const middleware = nc()
.use(sess, ppinit, ppsession)
.get((req: Express.Request, res: NextApiResponse, next) => {
next();
});
try {
await middleware.run(req, res);
} catch (e) {
// handle the error
}
const props: ServerPropsReturn = {};
if (req.user) props.user = req.user;
return { props };
};
export interface Props {
user?: User;
}
export default getServerSideProps;
// src/hooks.ts
import { useEffect } from "react";
import { User } from "./types/types";
export const useSetUserStorage = (user?: User) => {
useEffect(() => {
if (user) {
localStorage.setItem("user", JSON.stringify(user));
} else {
localStorage.removeItem("user");
}
// whether setting or removing the user, dispatch event so that `GlobalNav`
// component (which is above the page implementing this hook in the
// component hierarchy) can be updated to display the user status. we
// can't use `window.addEventListener('storage', handler)` as this only
// works for listening for events from other pages
document.dispatchEvent(new Event("localStorageUserUpdated"));
});
return null;
};
// src/pages/index.tsx (or any page)
import { useSetUserStorage } from "../hooks";
import { Props } from "../utils/getServerSideProps";
export { default as getServerSideProps } from "../utils/getServerSideProps";
export default function Home({ user }: Props) {
useSetUserStorage(user);
return (
<>
<h1>Welcome to my app {user?.username}</h1>
</>
);
}
// src/components/GlobalNav.ts (or another global component)
import { useEffect, useState, MouseEvent } from "react";
import { User } from "../types/types";
const GlobalNav = () => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const handleUserLocalStorage = () => {
const userString = localStorage.getItem("user");
try {
if (userString) {
setUser(JSON.parse(userString));
} else {
setUser(null);
}
} catch (e) {
// handle parse error
}
};
handleUserLocalStorage();
// this component (`GlobalNav`) lives at the application level, above the
// pages, but the pages receive the user object from `getServerSideProps`,
// so this listener listens for when a page tells us the user object has
// changed so we can update the `user` state here.
document.addEventListener(
"localStorageUserUpdated",
handleUserLocalStorage,
false,
);
return () => {
// remove listener if component unmounts
document.removeEventListener(
"localStorageUserUpdated",
handleUserLocalStorage,
);
};
}, []);
return (
<div>
{user?.username}
</div>
);
};
export default GlobalNav;
I used a slightly different technique. Every page, in my case, has its own getServerSideProps and I was looking for a more functional approach. Also I'm using GraphQL, but the idea is the same no matter which data fetching API you choose. A regular getServerSideProps would look like this -
export const getServerSideProps: GetServerSideProps = async (context) => {
const { slug } = context.query
const { data: profile } = await client.query({ query: GetProfileDocument, variables: { slug } })
return {
props: {
...(await getSelf(context)),
profile: profile?.GetProfile[0],
},
}
}
In the props you can see the await statement, which is called in all pages. And in the few cases I don't need it, it's gone. This is what getSelf looks like -
const getSelf = async (context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>) => {
const session = await getSession(context)
let self = null
if (session) {
const { data } = await client.query({
query: GetProfileDocument,
variables: { secret: session?.secretSauce as string },
})
self = data.GetProfile[0]
}
return { self, sessionData: session }
}
Hope it helped.

Avoid replacing/mixing user contexts in apollo-server datasources during the execution of asynchronous code

This question might be somewhat stupid but I like to make sure to not mess this up:
If you use this.context in your datasource, it's critical to create
a new instance in the dataSources function and to not share a single
instance. Otherwise, initialize may be called during the execution of
asynchronous code for a specific user, and replace the this.context
by the context of another user.
Source
Question: Is the following appropriate to avoid mixing/replacing user context during execution of async code?
main.js
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => {
return {
foo: new FooAPI() <- is this considered 'create a new instance in the dataSources function'?
}
},
context: (event) => {
return {
use_id: event.user_id <- (provided by auth middleware)
}
}
})
MyCustomDatasource.js
class MyCustomDatasource extends DataSource {
constructor ({ type, tableName, region, apiVersion }) {
super()
// db specific stuff
}
initialize (config) {
this.context = config.context <- save due to 'new instance in the dataSources function'?
this.cache = config.cache
.
.
.
}
FooAPI.js
class FooAPI extends MyCustomDatasource {
constructor () {
super({
// foo db params
})
}
// Query
async allByUser () {
return await super.query({
KCE: `
#user_id = :user_id
AND begins_with(#id, :id)
`,
EAN: {
'#user_id': 'user_id',
'#id': 'id'
},
EAV: {
':user_id': this.context.user_id, <- save due to 'new instance in the dataSources function'?
':id': 'foo'
},
Limit: 10
})
}
foo resolver
{
Query: {
foos (parent, args, context, info) {
const { foo } = context.dataSources
return foo.allByUser()
}
},

Unit testing Sails/Waterline models with mocha/supertest: toJSON() issue

I'm setting up unit tests on my Sails application's models, controllers and services.
I stumbled upon a confusing issue, while testing my User model. Excerpt of User.js:
module.exports = {
attributes: {
username: {
type: 'string',
required: true
},
[... other attributes...] ,
isAdmin: {
type: 'boolean',
defaultsTo: false
},
toJSON: function() {
var obj = this.toObject();
// Don't send back the isAdmin attribute
delete obj.isAdmin;
delete obj.updatedAt;
return obj;
}
}
}
Following is my test.js, meant to be run with mocha. Note that I turned on the pluralize flag in blueprints config. Also, I use sails-ember-blueprints, in order to have Ember Data-compliant blueprints. So my request has to look like {user: {...}}.
// Require app factory
var Sails = require('sails/lib/app');
var assert = require('assert');
var request = require('supertest');
// Instantiate the Sails app instance we'll be using
var app = Sails();
var User;
before(function(done) {
// Lift Sails and store the app reference
app.lift({
globals: true,
// load almost everything but policies
loadHooks: ['moduleloader', 'userconfig', 'orm', 'http', 'controllers', 'services', 'request', 'responses', 'blueprints'],
}, function() {
User = app.models.user;
console.log('Sails lifted!');
done();
});
});
// After Function
after(function(done) {
app.lower(done);
});
describe.only('User', function() {
describe('.update()', function() {
it('should modify isAdmin attribute', function (done) {
User.findOneByUsername('skippy').exec(function(err, user) {
if(err) throw new Error('User not found');
user.isAdmin = false;
request(app.hooks.http.app)
.put('/users/' + user.id)
.send({user:user})
.expect(200)
.expect('Content-Type', /json/)
.end(function() {
User.findOneByUsername('skippy').exec(function(err, user) {
assert.equal(user.isAdmin, false);
done();
});
});
});
});
});
});
Before I set up a policy that will prevent write access on User.isAdmin, I expect my user.isAdmin attribute to be updated by this request.
Before running the test, my user's isAdmin flag is set to true. Running the test shows the flag isn't updated:
1) User .update() should modify isAdmin attribute:
Uncaught AssertionError: true == false
This is even more puzzling since the following QUnit test, run on client side, does update the isAdmin attribute, though it cannot tell if it was updated, since I remove isAdmin from the payload in User.toJSON().
var user;
module( "user", {
setup: function( assert ) {
stop(2000);
// Authenticate with user skippy
$.post('/auth/local', {identifier: 'skippy', password: 'Guru-Meditation!!'}, function (data) {
user = data.user;
}).always(QUnit.start);
}
, teardown: function( assert ) {
$.get('/logout', function(data) {
});
}
});
asyncTest("PUT /users with isAdmin attribute should modify it in the db and return the user", function () {
stop(1000);
user.isAdmin = true;
$.ajax({
url: '/users/' + user.id,
type: 'put',
data: {user: user},
success: function (data) {
console.log(data);
// I can not test isAdmin value here
equal(data.user.firstName, user.firstName, "first name should not be modified");
start();
},
error: function (reason) {
equal(typeof reason, 'object', 'reason for failure should be an object');
start();
}
});
});
In the mongoDB console:
> db.user.find({username: 'skippy'});
{ "_id" : ObjectId("541d9b451043c7f1d1fd565a"), "isAdmin" : false, ..., "username" : "skippy" }
Yet even more puzzling, is that commenting out delete obj.isAdmin in User.toJSON() makes the mocha test pass!
So, I wonder:
Is the toJSON() method on Waterline models only used for output filtering? Or does it have an effect on write operations such as update().
Might this issue be related to supertest? Since the jQuery.ajax() in my QUnit test does modify the isAdmin flag, it is quite strange that the supertest request does not.
Any suggestion really appreciated.