Why actions in SveltKit give "Error: Cannot prerender pages with actions"? - javascript

I have a SvelteKit application, just following the example from the docs https://learn.svelte.dev/tutorial/named-form-actions, the problem is that everything works until I try to write an action:
at: +page.server.js
export const actions = {
default: async () => {
console.log('test')
}
};
vite immediately fails with:
"Cannot prerender pages with actions"
Error: Cannot prerender pages with actions
at render_page (file:///mydir/node_modules/#sveltejs/kit/src/runtime/server/page/index.js:87:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async resolve (file:///mydir/node_modules/#sveltejs/kit/src/runtime/server/index.js:356:17)
at async respond (file:///mydir/node_modules/#sveltejs/kit/src/runtime/server/index.js:229:20)
at async file:///mydir/node_modules/#sveltejs/kit/src/exports/vite/dev/index.js:444:22
Probably I'm missing some configuration or forgot some basics, any idea?

The docs simply state:
Pages with actions cannot be prerendered, because a server must be able to handle the action POST requests.
The assumption is probably that a form action should have an effect on the page when submitted, which would not be possible when a static HTML page is served every time.
You could try to separate any logic to an API endpoint that is not associated with your prerendered page. It depends on what you are trying to do here, maybe the page should simply not be prerendered at all.
This means that +page.ts should set:
export const prerender = false;

Related

How to fetch the API details from a component folder using Next.js?

I have a reusable component that I have created on the components folder where I have all the details from the user that logs in to the system which is a header section.
I am trying to use getInitialProps using fetch with isomorphic-unfetch.
static async getInitialProps(ctx) {
const UserdetailsRespone = await fetch("my API url");
const UserdetailsJson = await UserdetailsRespone.json();
return { UserDetails: UserdetailsJson.root[0] };
}
In the render method when I log this.props.UserDetails I get undefined.
It is the same API fetch as the one I am doing in the pages folder where I am able to fetch an API response. But I am not able to fetch the response in the components folder.
Can someone help me to solve it?
Thanks in Advance.
getInitialProps runs server side and client side. So you will have to be careful how you use it. I usually use typeof Window === 'undefined' to check whether getInitialProps is being called server side or not.
If you have a child component that you need to make a call everytime it is mounted, why not stick with componentDidMount?
async componentDidMount() {
const userDetailsResponse = await fetch("my API url");
const userDetailsJson = await userDetailsResponse.json();
this.setState({userDetails: userDetailsJson})
}
You can access the properties from state instead of props then.
If you're using getInitialProps in a child component, it won't work. It only works in the default export of every page.
From official docs
Edit:
As mentioned by Uzair Ashraf, using fetch is the way to go for fetching data in components. You can take a look at swr too.

Auth0 client is null in Vue SPA on page refresh

I have a Vue SPA based on one of Auth0's quickstart apps (https://github.com/auth0-samples/auth0-vue-samples). Everything works fine out of the box, but as soon as I try using the Auth0 client in my component code I run into problems. I followed the "Calling an API" tutorial (https://auth0.com/docs/quickstart/spa/vuejs/02-calling-an-api), which unhelpfully only shows how to call an API using a button. What I want to do is trigger an authenticated call to my API on initial page load so that I can ensure certain data exists in my own API (or create it if it does not). This seems like it should be pretty straightforward. I just throw this code in my created hook of my Vue component:
await this.$auth.getTokenSilently().then((authToken) => {
// reach out to my API using authToken
});
This actually works fine if the app hot reloads from my npm dev server, it reaches out to my API, which authorizes the request using the token, and sends back the correct data. The problem is when I manually reload the page, which causes this:
Uncaught (in promise) TypeError: Cannot read property 'getTokenSilently' of null
at Vue.getTokenSilently (authWrapper.js?de49:65)
at _callee$ (App.vue?234e:49)
Inside the authWrapper.js file (where the Auth0 client lives), the function call is here:
getTokenSilently(o) {
return this.auth0Client.getTokenSilently(o);
}
When I debug the call, "auth0Client" doesn't exist, which is why it's failing. What I can't understand is the correct way to ensure it does exist before I attempt to make the call. There's nothing in the samples that indicates the right way to do this. I tried putting my component code in different components and different Vue lifecycle hooks (created, beforeMount, mounted, etc), all with the same result. The client becomes available after 800 ms or so, but not when this code executes.
This is clearly a timing problem, but it's not clear to me how to tell my component code to sit and wait until this.auth0Client is non-null without doing something horrible and hacky like a setInterval.
I figured out a workaround for now, which I'll add as an answer in case anyone else has this issue, although it's not really the answer I want. Per the authGuard, you can use the exported "instance" from the authWrapper and watch its "loading" flag before executing your code that depends on the auth0Client being ready, like this:
import { getInstance } from "./auth/authWrapper";
// ... Vue component:
created() {
this.init(this.doSomethingWithAuth0Client);
},
methods: {
init(fn) {
// have to do this nonsense to make sure auth0Client is ready
var instance = getInstance();
instance.$watch("loading", loading => {
if (loading === false) {
fn(instance);
}
});
},
async doSomethingWithAuth0Client(instance) {
await instance.getTokenSilently().then((authToken) => {
// do authorized API calls with auth0 authToken here
});
}
}
It's hardly ideal, but it does work.

Having trouble with persisting React state using localStorage with a SSR (Next.js) app

The app I'm making has customizable settings. I'd like to load default settings, then when a user makes any changes, the custom settings will be stored in localStorage. Now the next time a user comes back, we'll load the setting from their localStorage.
I'm using React context like so...
const SettingsContextProvider = (props: any) => {
const [settings, setSettings] = useState(getSettings());
useEffect(() => {
localStorage.setItem('settings', JSON.stringify(settings))
}, [settings]);
return (...some jsx...);
}
Then getSettings()...
getSettings() {
// get from local storage
if (process.browser) {
const localSettings = localStorage.getItem('settings');
if (localSettings) return JSON.parse(localSettings );
}
// fall back to default settings
return "Something else";
}
The issue I'm having is that the server side load (on the node side), we don't have local storage (which is why I check for process.browser), so it falls back to default settings. THEN when it gets to the browser, it seems to call getSettings() again, in which case we DO have local storage and it loads from there.
That works, but then I get an error:
Text content did not match. Server: "Something else" Client: "Something custom"
So I understand that the server isn't matching the client and it's upset about it. I get WHY it's happening, but I have NO IDEA how to fix it. Maybe I need to implement reducers or use cookies?
Has anyone come across this?
I'm more than happy to share my full repo code if it'll help.
I'm fairly comfortable with react but have pretty much NEVER used react hooks in my own code.
However, I feel like I wouldn't put the updating of the localStorage into a useEffect function like that (and I could be totally wrong about that).
So, first, I would check that that useEffect function is being called when you're expecting it to be.
And then, I would write a function
const updateSettings = (newSettings) => {
localStorage.setItem('settings', JSON.stringify(newSettings))
setSettings(newSettings);
}
And use that updateSettings function to pass down to components.
BUT, I could be totally off there, like I said I don't use react hooks and only have a theoretical understanding.

Access the error response without modifying the Amplify library

I am using the AWS Amplify library to handle the login on my React app. This works by wrapping the entire app in a HOC, and I have added custom UIs for the login screens. By default Amplify displays error messages from Cognito in a toast but I would like to display these as plain text within my custom UI.
I have raised this issue on the Amplify repo and have been informed there is no way to customise the error messages but it is currently a feature request. In the meantime, I think there must be a workaround.
There are 3 ways I believe I might be able to access the error message in React:
When an error is received Amplify sets it in the state of 'Authenticator', which is the parent component (created by the HOC) of my custom UI. Without modifying the actual library I can't pass this down as props or pass it into context/redux but is there some hack to be able to access this?
The request to Cognito is handled by a fetch request generated from the library but I can see the response in the console. A 400 response with the error message in the body of the response. Is there a way to set up some kind of event listener to get the error message off the http response?
The library also generates a toast when an error message is returned. I am currently passing the HOC a css theme (display: none) to hide this. Is there a way to listen for the creation of the toast component and get the message off the span element that lays inside?
Any advice would be greatly appreciated.
Matthew
I found two ways to solve this issue.
Firstly, if you are only interested in ‘signIn’, ‘signUp’ or ‘signOut’ events. You can use the ‘Hub’ utility to listen for an 'auth' event before you attempt an authentication action like signIn and then set the error message to state. Something like this:
import { Hub } from "aws-amplify";
handleLogin = e => {
Hub.listen("auth", res => {
const errorMsg = res.payload.data.message ? res.payload.data.message : null;
this.setState(prevState => ({...prevState, errMsg: errorMsg}));
})
this.signIn(e);
}
Alternatively, if like me you want the error messages off confirm sign in or forgot password events. You can use the Auth API and catch the error message. You have to figure out what needs to be passed into each method but the docs are quite informative. Here's an example for the confirm sign in:
import { Auth } from 'aws-amplify';
confrimSignIn = () => {
Auth.confirmSignIn(this.props.authData, this.inputs.code, 'SMS_MFA')
.then(() => this.changeState('signedIn'))
.catch(err => this.setState({errMsg: err.message}));;
}

JHIPSTER - How can I show a different HOME page in JHipster for each ROLE?

I'm making a JHipster project and I need to show a different home page for each role that I log in with, I'm using Angular 1.x.
For example I have the ROLE_ADMINand the ROLE_USERand I need to show a different dashboard for each on.
I have read that I can put something like this in the home.controller.js
this.eventManager.subscribe('authenticationSuccess', (message) => {
this.principal.identity().then((account) => {
if (account.authorities.indexOf("ROLE_ADMIN") >=0)
{
this.router.navigate(['#/pages/prueba/prueba.html']);
}
else
{
this.account = account;
}
});
});
But I can't make it work, it shows this error: Error: this is undefined
Anyone have a clue about this?
You can have a look at auth.service.js. There is a method called authorize, which in turn calls authThen. These methods are invoked after the user is authenticated and normally redirects the user to the last state (normally the protected state that failed, since the user was not authenticated and therefore was redirected to the login). You may change the code here to redirect the user according to its authorities.
The same methods (authorize and authThen) are also called everytime before a state changes, because it is a "resolve" for each state (have a look at the app.state.js).
Another option would be to add an "onEnter" function to your state definition that redirects to the appropiate view.

Categories

Resources