I'm used to using Express with a templating engine, like Handlebars. I want to start working with Svelte and SvelteKit, but I'm unsure how to start working with both. I am stuck on passing data to the frontend from the server. In Express, I'd normally connect to the database, and then pass the data to res.render, where the templating engine would then take over. So far, I think I have to run a handle function to pass the data, and then I can access it from my page. But it seems that the handle function runs for every request, and if all my pages require different data, does that mean I have to use a giant switch statement or something for every page?
Can anybody tell me if that's the right way to pass data over, or if there's a better way. Sorry, I'm fairly new to metaf rameworks and Svelte.
There are two ways to achieve what you want to do.
Let for both cases assume you have an about page and want to show dynamic data on this page like some team members. You would have a file called about.svelte (this makes the /about route) with in it something like:
<script>
export let team = [];
</script>
{#each team as member}
...
{/each}
Now how to get the team data to the page itself ?
the load function
The first option is the load function, this is a function that runs before the page is loaded and can be used to fetch data for this page. You would put the following block in about.svelte, usually before the other script block:
<script context="module">
export async function load({ fetch }) {
const team = await fetch('/api/team').then(res => res.json());
return {
props: {
team
}
}
}
</script>
Important to note with this one is that you need some sort of api (/api/team in this case) that can give you the data.
a page endpoint
The second option is to make a so called page endpoint this acts as a kind of api and lives next to the page itself. In the same folder as about.svelte add a file about.js:
export async function get() {
const team = []; // add team data here somehow
return {
status: 200,
body: {
team
}
}
what's the difference ?
When to use which approach is mostly up to you, but you have to remember the following two things:
The load function will likely need another api somewhere (this does not have to be SvelteKit)
The load function is, on the first page, executed on the server and afterwards will always run on the client. The endpoint on the other hand always runs serverside.
Related
I'm new to remix Js and wanted to understand what the best way is to get this done.
I have an app that calls a function which in turn calls an API to load user details. This user information is used to paint multiple things like header, footer and load the appropriate view for the user.
The thing I notice is that currently for a single page load request from the browser, the function (and in turn the API) is called independently multiple times by the module that renders the header, footer, page etc. What I wanted to know is if it is possible to have the user details saved in a variable so that for a single request all the modules can use this variable and it needs to be only fetched once per browser request.
I tried the following but it did not work
let userDetails;
export function getUserDetails(userId){
if(!userDetails){
console.log("Calling API to fetch user details);
//call the API
userDetails = async getUserById(userId);
}
return userDetails;
}
One option we have is to save the user details in the browser/session but that is not allowed due to some policies. What would be the best way to achieve this so that we only need to call the API once per request from the browser and save subsequent calls for a single page load to fetch the same info ?
TLDR : what would be the best way to initialize an object in an remix server module so that its value could be accessible by other modules and it has to be set only once per a request from a browser.
Dataloader can be used to solve this issue.
It offers request-level caching (and batching) which means parallel loaders on the initial HTML request can cooperate while remaining independent.
Example usage with remix can be found here.
// Root.tsx
export const loader = async ({ context }) => {
const user = await context.loaders.userById.get("user_1");
return json({ user });
}
// ...
// Child.tsx
export const loader = async ({ context }) => {
// This will await the same promise created in the Root.tsx loader
const user = await context.loaders.userById.get("user_1");
return json({ user });
}
// ...
Having said that, I would warn that the abstraction cost of dataloader is quite high and to only use it if you really can't afford to call into your API multiple times (loaders run in parallel so it's more an issue of upstream load over performance).
It's also worth noting that SPA navigations won't see the benefit because each loader is called as a separate request from the browser so a request-level cache will be ineffective.
I need to consume Context API within modules (not components).
In my app, when a user logs in, I store his data in a context (just to sync his data between components).
Storing this data in other place, and not in the state of a Context, is not the best option to synchronize the data between all the routes of my app.
I also have my "API" to connect with the server and make things like "getContent", "getMessage"... Until now, I was passing a "parse callback" (just to parse the server responses) to the "API" methods.
function ComponentA() {
const parseContent = (contentDoc) => {
// get current user data from context (the component is a consumer)
// parse the data
}
...
const content = await api.getContent(contentId, parseContent)...
...
}
function ComponentB() {
const parseMessage = (contentDoc) => {
// get current user data from context (the component is a consumer)
// parse the message data...
// if the message has "content", then parse the content (same as parseContent)
}
...
const messages = await api.getMessages(messageId, parseMessage)...
...
}
As you can see, this is something that makes me duplicating code. Because "parseContent" is can perfectly be used inside "parseMessage"
Now, I am trying to move the "parsers" methods to other modules, but some of these methods needs to consume the current user data (which is in a context). Something which makes my idea impossible to implement.
Is it common to pass "parse" callbacks to api methods in React? (Honestly, for me, this seems shitty coding)
Is there any way to consume the context within a module? Any ideas?
Thank you.
I am relatively new to Meteor, and I'm trying to create a web store for my sister-in-law that takes data from her existing Etsy store and puts a custom skin on it. I've defined all of my Meteor.methods to retrieve the data, and I've proofed the data with a series of console.log statements... So, the data is there, but it won't render on the screen. Here is an example of some of the code on the server side:
Meteor.methods({
...
'getShopSections': function() {
this.unblock();
var URL = baseURL + "/sections?api_key="+apiKey;
var response = Meteor.http.get(URL).data.results;
return response;
}
...
});
This method returns an array of Object. A sample bit of JSON string from one of the returned Objects from the array:
{
active_listing_count: 20,
rank: 2,
shop_section_id: 1******0,
title: "Example Title",
user_id: 2******7
}
After fetching this data without a hitch, I was ready to make the call from the client side, and I tried and failed in several different ways before a Google search landed me at this tutorial here: https://dzone.com/articles/integrating-external-apis-your
On the client side, I have a nav.js file with the following bit of code, adapted from the above tutorial:
Template.nav.rendered = function() {
Meteor.call('getShopSections', function(err, res) {
Session.set('sections', res);
return res;
});
};
Template.nav.helpers({
category: function() {
var sections = Session.get('sections');
return sections;
}
});
And a sample call from inside my nav.html template...
<ul>
{{#each category}}
<li>{{category.title}}</li>
{{/each}}
</ul>
So, there's a few things going on here that I'm unsure of. First and foremost, the DOM is not rendering any of the category.title String despite showing the appropriate number of li placeholders. Secondly, before I followed the above tutorial, I didn't define a Session variable. Considering that the list of shop categories should remain static once the template is loaded, I didn't think it was necessary from what I understand about Session variables... but for some reason this was the difference between the template displaying a single empty <li> tag versus a number of empty <li>'s equal to category.length --- so, even though I can't comprehend why the Session variable is needed in this instance, it did bring me one perceived step closer to my goal... I have tried a number of console.log statements on the client side, and I am 100% sure the data is defined and available, but when I check the source code in my Developer Tools window, the DOM just shows a number of empty li brackets.
Can any Meteor gurus explain why 1) the DOM is not rendering any of the titles, and 2) if the Session variable indeed necessary? Please let me know if more information is needed, and I'll be very happy to provide it. Thanks!
You set the data context when you use #each, so simply use:
<li>{{title}}</li>
If a Session is the right type of reactive variable to use here or not is hard to determine without knowing what you are doing but my rough guess is that a Mini Mongo collection may be better suited for what it appears you are doing.
To get you started on deciding the correct type of reactive variable to use for this head over to the full Meteor documentation and investigate: collections, sessions, and reactive vars.
Edit: To step back and clarify a bit, a Template helper is called a reactive computation. Reactive computations inside of helpers will only execute if they are used in their respective templates AND if you use a reactive variable inside of the computation. There are multiple types of reactive variable, each with their own attributes. Your code likely didn't work at all before you used Session because you were not using a reactive variable.
For example I have the following server routes set up for my user entity:
GET /users/ // gets collection of users
GET /users/:id // gets user :id
GET /users/me // gets the current user
At the beginning of my app I want to get the current user from the server and store it... Something along the lines of:
App.addInitializer(function () {
$.get('/users/me')
.done(function processCurrentUser (userJson) {
App.user = new User(userJson);
});
});
My question is where this API call should actually reside. Would it be better to have something along the lines of:
App.addInitializer(function () {
App.user = new User();
App.user.fetchMe(); // performs the api call above
});
Or should I be doing something inside of a controller?
Thanks for the help!
When doing a fetch, I always worry about how its asyn behavior is going to affect the components that depend on that data. If there are no downriver components that will need the data before it can be reasonably expected to return, then there's technically nothing wrong with your approach.
There is, however, another possible way of loading your globals. What I often do (and for a user's list, too, it so happens) is bootstrap the data to the initial load page. I generally load it on the window variable. So for your example, in your backend template,
<script>
window.globals = {};
window.globals.currentUser = #Html.Raw(Json.Encode(ViewBag.User))
</script>
Of course, you can replace #Html.Raw(Json.Encode(ViewBag.User)) (we use C#) with your favorite backend model.
Then in your app start you're guaranteed to have the models:
App.addInitializer(function () {
App.user = new User(window.globals.currentUser);
});
Still on my meteor app, i'd like to now how to improve my data handling from minimongo.
Used to SQL / PHP, I'd like to know how to find() an object from my minimongo collection only one time, and access each of its properties with helpers, without having to re-access the collection each time.
Until now, what I did was something like that :
Template.profile.helpers({
name: function(e, tmpl){
return Meteor.users.FindOne({_id: Meteor.userId()}.profile.name;
},
phone: function(e, tmpl){
return Meteor.users.FindOne({_id: Meteor.userId()}.profile.phone;
}
[...]
});
But it's getting boring and i guess there must be a more efficient way to deal with it, something where I could load my entire users information only one time, and then display with a helper taking one parameter, to display the data like that : {{data name}}, {{data phone}}
With only one helper like that :
Template.profile.helpers({
data: function(aString){
if (aString == "phone)
return [...].phone;
}
}
[...]
});
Of course, I can use a session value, but I'm not sure it's as relevant as I could do.
Another thing : how to end a Meteor session ? Because with PHP, the session ends at the closure of the browser, and cookies stay for a given duration, but with meteor session, i never have to reconnect as day after day, my logs seems to stay.
Would someone guide me through this or give me some good habits / tips ? I'm still reading the doc, but it's quite huge :S
Thanks you !
One thing to note here -- you're not actually sending a request to the server when you call Mongo.Collection.find() on the client -- that's getting handled by the miniMongo instance on the client, so it's not really that inefficient. Also, in this particular instance, the current user is always available as Meteor.user() -- no need to use Meteor.users.find({_id: Meteor.userId()}); (see docs for more info)
However, if you really wanted to cache that value, you could do something like this:
// JS
var data; // scoped so it's available to the whole file
Template.profile.onCreated({
data = Meteor.users.findOne({_id: Meteor.userId()}); // above scope makes this new definition available to the whole file
});
Then, to do what you're describing with the string arguments, you can do something like...
Template.profile.helpers({
data: function(aString) {
if (data) { return data[aString]; }
}
});
Perhaps a better option even is to send the Template a data context -- something like this in your HTML file:
{{> profile currentUser}} <!-- currentUser returns Meteor.user() -->
Which is directly available to your helpers as this:
Template.profile.helpers({
data: function(aString) {
return this[aString];
}
});
You can also pass data contexts through IronRouter if you're using that for your routing.
As for the Meteor session, the model is different than the model for PHP. In PHP, you store session information on the server, and every time you access the server, the browser (or client, more generally) sends along the session ID so it can look up anything pertaining to your session (see this question for a potentially better explanation). Meteor keeps track of clients that are connected to it, so the session will stay open as long as your browser is open. If you need to manipulate Meteor user sessions, take a look at this question.
I recommend finding the collection helpers package on Atmosphere. This will enable you to write currentUser.name in a template and it'll automatically return users name helper function returns.
There is no problem working like this. It doesn't matter if the function is called multiple times. It won't hurt your apps performance.
PHP and meteor sessions are different. A meteor session lasts for as long as browser window remains open or the page is refreshed. You are right that meteor sessions are not the way to go for your problem.