Consuming external API using SvelteKit works but only after reloading route - fetch

Using SvelteKit 1.0.0-next.95 to get a JSON array back from an external API endpoint and display in a template like this:
<script context="module">
export async function load({ fetch }) {
const url = 'https://www.schoolhouseyoga.com/api/announcement'
const res = await fetch(url, {
method: 'GET',
mode: 'cors',
headers: {
'content-type': 'application/json'
}
})
if (res.ok) {
return {
props: {
sections: await res.json()
}
}
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
}
}
</script>
<script>
export let sections = []
</script>
<template>
<section>
{#if sections.length > 0}
<div class="callout">
<h1>Announcements</h1>
{#each sections as section}
<div>
{#if section.announcements.length > 0}
<h2>{section.section}</h2>
{/if}
{#each section.announcements as announcement}
<p><b>{announcement.title} </b>- {#html announcement.description}</p>
{/each}
</div>
{/each}
</div>
{/if}
</section>
</template>
If you try https://www.schoolhouseyoga.com/api/announcement (CORS) in a browser or using curl, you'll get a JSON array with two elements.
When I run this in dev mode, npm run dev -- --open and navigate to this route on Safari 14.1 (macOS), I get a 500 error and the message, "Origin http://localhost:3000 is not allowed by Access-Control-Allow-Origin." If I try to navigate to that route on Google Chrome, I get a 500 error and "TypeError: Failed to fetch".
But with either browser, if I refresh the page, the data loads successfully. Navigating to a different route then back again, the error reappears.
I am guessing this has to do with SSR but not sure what to do about it.
Any thoughts?

The problem was related to server-side rendering and a CORS issue with the endpoint. When the server-side code performed the fetch, it worked fine. Subsequent fetches were being performed by the client (which ran into the CORS issue).
While the endpoint appeared to have CORS enabled...
import { Router } from 'express';
import cors from 'cors';
import * as controller from './announcement.controller';
const router = Router();
router.get('/', cors(), controller.index);
But the endpoint was also using helmet and needed
app.use(helmet.permittedCrossDomainPolicies());
prior to loading the routes.
Hope this helps others.

Related

nextjs reload page with Link component

I have a simple nextjs 13 application (appDir) with 2 pages and a <Link/> component navigation.
first page Home - static
second page Test - receiving dynamic random content on the server side (fetch) from a third-party source.
Problem: When the application is rendered everything works fine, but when I try to switch between pages, my test page shows the old content, I can refresh the browser to get the actual data, is very similar to navigating through regular links <a/>, but i need without reloading the entire application.
Q: How can I force nextjs 13 to reload the Test page when I switch between pages with <Link/> component?
// src/components/navbar.tsx
'use client'
import {usePathname} from "next/navigation";
import Link from "next/link";
const navItems = [
{text: 'Home', href: '/'},
{text: 'Test', href: '/test'}
];
const Navbar = () => {
const pathname = usePathname();
return <nav className="nav nav-masthead justify-content-center float-md-end">
{navItems.map((item: { text: string, href: string, link?: boolean }, idx: number) => (
<Link key={idx} href={item.href} className={`nav-link${item.href === pathname ? ' active' : ''}`}>
{item.text}
</Link>
)
)}
</nav>
}
export default Navbar;
// src/app/test/page.tsx
import * as crypto from "crypto";
const getData = async () => {
const res = await fetch('http://localhost:3000/random-data', {cache: 'no-store'});
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
return <p>{crypto.createHash('sha256').update(JSON.stringify(await getData())).digest('hex')}</p>
};
I've recently asked about this same topic on their feedback discussion on github: https://github.com/vercel/next.js/discussions/41745?sort=new#discussioncomment-4620262
The cause of the problem is Link is only doing client side navigation and seems to serve a cached state of the previously visited component. You'll notice that the client never calls back to the server and thus the server component never runs the second time.
I've been searching for days, but haven't found a way to force Link to reload or force refresh the component to re-render.
My conclusion is that if you have dynamic data that needs to refreshed periodically, it's best to render it in a client component and not use a server component for now.
Also, if you'd like to use Suspense, you'll need to use a library like SWR or React Query for any client side data fetching.

NextJS server side renders even without getXProps?

This code is in a page file in NextJS. Although I'm not using getStaticProps or getServerSideProps it still performs server side rendering.
Is this by design? The docs would imply that these get functions are required: https://nextjs.org/docs/basic-features/data-fetching
import React from "react";
import Head from "next/head";
import Link from "next/link";
import { ApolloProvider } from "#apollo/react-hooks";
import ApolloClient from "apollo-boost";
import { gql } from "apollo-boost";
import { useQuery } from "#apollo/react-hooks";
const client = new ApolloClient({
uri: "https://48p1r2roz4.sse.codesandbox.io",
});
const EXCHANGE_RATES = gql`
{
rates(currency: "USD") {
currency
rate
}
}
`;
const Home: React.FC = () => {
const { loading, error, data } = useQuery(EXCHANGE_RATES);
if (loading) {
return (
<div>
<p>Loading</p>
</div>
);
}
if (error) {
return (
<div>
<p>Error</p>
</div>
);
}
return (
<div>
<ul>
{data.rates.map((item) => (
<li key={item.currency}>
{item.currency} - {item.rate}
</li>
))}
</ul>
</div>
);
};
export default () => (
<ApolloProvider client={client}>
<Home />
</ApolloProvider>
);
Production mode
The page provided has no getInitialProps or getServerSideProps. It's statically optimized (pre-rendered at build time).
So, when you request this page in a browser, you will see a pre-rendered HTML content in the response. Disabling JavaScript does not affect it.
If you navigate to the page by using client-side next/link or router the page will be rendered at client-side without making a request to a server.
Development mode
In Development mode this page would be both - server-side rendered and client-side rendered.
If you'd request the page by typing address in a browser it will be pre-rendered at server-side.
If you navigate to the page by using client-side next/link or router the page will be rendered at client-side without making a request to a server (you will see only Webpack Hot Module Replacement request).

Next.js Amplify Serverless AppSync graphql (Amplify error)

I'm trying to create an App that uses Amplify and AppSync with Next.js.
I was able to deploy the application successfully but when I'm trying to access the application (trying to fetch data from AppSync) It is failing.
I'm kinda lost what needs to be looked at, the application seems to be working fine locally but when I'm publishing it on AWS it is failing.
I'm getting error: TypeError: Cannot read property 'getName' of undefined
//quotes.js
import { Connect } from 'aws-amplify-react';
........
<Connect
query={graphqlOperation(queries.getName)}
subscription={graphqlOperation(subscriptions.onCreateName)}
onSubscriptionMsg={(prev, { onCreateName }) => ({
...prev,
getName: {
...prev.getName,
items: [
...prev.getName.items,
onCreateName,
],
},
})}
>
Has anybody come across such an issue or any idea to resolve the issue? Thanks in advance.
Running the subscription when doing SSR is not needed / useful and requires a WebSocket client in the server, you can try Dynamic Imports with no SSR to have the subscription only happen on the client. (using { ssr: false })
import dynamic from 'next/dynamic';
const DynamicComponentWithNoSSR = dynamic(() => import('../components/hello3'), { ssr: false });
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
);
}
export default Home;

VueJS rendering data from REST service

I've attempted to render data from a http request to a component which is working fine, the issue is that it's null while the data is being fetched. While the data is null the console is throwing a TypeError until all the data is loaded and committed to the Vuex store.
All is working how I'd suspect, I'm just trying to figure how I can prevent the errors being thrown and to wait until all the appropriate data is fetched. I've seen others using v-if to check if the data is null which will work. It just seems tedious and that there surly is a better way to achieve the same outcome, without an application riddled with v-if statements checking every single state.
I came across this solution but it's still not working how I thought it would, I'm still receiving the same console errors. Am I using these key words correctly and are they in the correct location? since nothing has changed with every variation I've tried.
Vuex Action:
const actions = {
getThread ({ commit }, payload) {
Vue.http
.get(`http://localhost:9000/threads/${payload.id}`)
.then(async response => {
commit(FETCH_THREAD, await response.data)
})
}
}
This is within my vue file calling upon the action:
created () {
this.$store.dispatch('getThread', {id: '59280ab5acbafb17af9da902'})
}
I assume you are trying to display something from your store in your template. The problem is, Vue cannot render something that does not exist yet. The solution is to check whether the data exists or not.
Let's take this component example:
<template>
<div>
{{ someObject.name }}
</div>
</template>
<script>
export default {
data () {
return {
someObject: null
}
},
methods: {
fetchTheObject () {
this.someObject = {
id: 1,
name: 'My object'
}
}
},
created () {
setTimeout( () => {
this.fetchTheObject()
}, 3000)
}
}
</script>
As you can see, you will get an error in your console because someObject.name does not exist until fetchTheObject() has been called.
The solution is to put some v-if attribute to control that:
<template>
<div>
<span v-if="someObject === null">Fetching the object</span>
<span v-else>{{ someObject.name }}</span>
</div>
</template>
In general, you would want to display some spinner to show the user that something is loading...
Hope this helps
EDIT: And forget about the async await in your code, you don't need that here

Meteor Login doesn't work when deployed to *.meteor.com

I have the following code on my Meteor app.
main.js (partial):
Template.login.events({
'click .login-button': function( e ) {
var serviceName = e.currentTarget.id.replace( 'login-buttons-', '' );
Accounts._loginButtonsSession.resetMessages();
var loginWithService = Meteor["loginWith" + (serviceName === 'meteor-developer' ? 'MeteorDeveloperAccount' : capitalize(serviceName))];
var options = {}; // use default scope unless specified
if (Accounts.ui._options.requestPermissions[serviceName])
options.requestPermissions = Accounts.ui._options.requestPermissions[serviceName];
if (Accounts.ui._options.requestOfflineToken[serviceName])
options.requestOfflineToken = Accounts.ui._options.requestOfflineToken[serviceName];
if (Accounts.ui._options.forceApprovalPrompt[serviceName])
options.forceApprovalPrompt = Accounts.ui._options.forceApprovalPrompt[serviceName];
loginWithService(options, function (err) {
console.log('user logged in');
});
}
})
And index.html (partial):
<div class="service-login-buttons">
<div class="login-text-and-button">
<div class="login-button single-login-button" id="login-buttons-twitter">
<div class="login-image" id="login-buttons-image-twitter"></div>
<span class="text-besides-image sign-in-text-twitter">Sign in to save your Pomodoros</span>
</div>
</div>
</div>
On the localhost it works just fine (twitter login).
I changed the twitter app settings to point to the correct *.meteor.com address, but the button doesn't do anything (no errors either).
Is there perhaps some configuration I need to redo?
The answer is simple: I had to configure the Twitter API on the deployed version as well.
Since I was using custom login buttons, I simply added
{{> loginButtons }}
To get the configuration dialog (where the API keys are added), then removed that part after configuration was done.

Resources