How to have a request scoped variable in remix JS? - javascript

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.

Related

How to Pass Data from Server using SSR in SvelteKit?

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.

How to pass data from ActionFunction to Loader function in Remix.run?

I'm making an application with Remix.run. It consists of multiple steps where user fills forms and in the end gets success screen. All form steps are located on single route, but success is on it's own route.
How everything works
I have redux-like (actually XState, but it is not important) message processing on server. It receives current (or initial for initial load) state, message type and data from fields. It then returns new state to client and page is rendered based on that state. Server don't have any storage an I don't want to introduce one, since it will complicate things.
// form page
export async function loader() {
return json(await getInitialState())
}
export async function action({request}) {
let fd = await request.formData();
let {current_state, message_type, ...data} = Object.fromEntries(fd.entries());
return json(await getNextState(current_state, {type: message_type, data}));
}
export function unstable_shouldReload({submission}) {
return !submission
}
export default FormPage() {
let loaderData = useLoaderData();
let actionData = useActionData();
// awful, isn't it?
let currentState = actionData || loaderData;
}
My problems
First my problem was that loader was triggered on every form submit. So, ActionFunction returns new state, but LoaderFunction returns initial state. It would be fine, but to get initial state, loader runs API, so it's wasted calls. I solved it with unstable_ShouldReload, and it's kind of ok, but not really.
Now I got to final step of the form and feel that I have same problem, but now I have separate route, so I can't just prevent running LoaderFunction. I need LoaderFunction to actually run on new page, and get data passed from previous steps for it. Basically, it's the same problem - I need to use data posted in form usable inside loader.
I tried to replace code above using session cookie with all the data, but I have other problems with it. Action returns cookie with session data, loader reads it and returns new state. Unfortunately it fails in few scenarios. If there is current session, user can reload page, prevent sending info again and get result as data was sent. It is not how web "usually" works. I can change it, by destroying session on each load, but then very first POST request yields no result, if there is no JS on user side. Otherwise it is best solution yet (but limited by size of the cookie).
Question
How to pass data from last action to next loader, or process POST request in loader itself?
How to pass data from last action to next loader
The only way is using sessions and or cookies. If you want to only be able to read the session data once and ensure it's not there anymore after a reload you could use session.flash(key, value), then in your loader, when you do session.get(key) it will remove the flash key from the session, if you commit the session in the loader then the cookie will be updated to don't have that key anymore, after a reload it will not be there.
process POST request in loader itself
This is not possible, loader functions only handle GET and OPTIONS requests, for every other request method only the action function is used.

React JS - How to move method from a component which consumes context, to another module

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.

What's the best way of achieving common tasks from within a Vuex store

I'm currently trying to achieve a common task when making API calls from within a Vuex store action object, my action currently looks like this:
/**
* check an account activation token
*
*/
[CHECK_ACTIVATION_TOKEN] ({commit}, payload) {
Api.checkActivationToken(payload.token).then((response) => {
if (response.fails()) {
return commit('NEW_MESSAGE', {message: responses.activation[response.code]})
}
return commit('SET_TOKEN')
})
}
I have several such methods carrying out various actions. What I want to be able to do is present a loader when each API call is made, and hide it again once the response is received. I can achieve this like so:
/**
* check an account activation token
*
*/
[CHECK_ACTIVATION_TOKEN] ({commit}, payload) {
commit('SHOW_LOADER')
Api.checkActivationToken(payload.token).then((response) => {
commit('HIDE_LOADER')
if (response.fails()) {
return commit('NEW_MESSAGE', {message: responses.activation[response.code]})
}
return commit('SET_TOKEN')
})
}
But I would need to repeat these SHOW_LOADER/HIDE_LOADER commits in each API call.
What I would like to do is centralise this functionality somewhere so that whenever API calls are made the showing and hiding of the loader is implicitly bound to the calls and not have to include these additional lines each time.
For clarity; the instantiated API is a client layer that sits on top of Axios so that I can prepare the call before firing it off. I've found I can't directly import the store into the client layer or where the Axios events are fired (so that I could centralise the loader visibility there) because Im instantiating the client layer within the vuex module and therefore creates a circular reference when I tried to do so, meaning the store is returned as undefined.
Is what I am trying to do possible through some hook or event that I have yet to come across?
I actually took a different path with this "issue" after reading this GitHub thread and response from Evan You where he talks about decoupling.
Ultimately I decided that by forcing the API layer to have direct knowledge of the store I am tightly coupling the two things together. Therefore I now handle the SHOW and HIDE feature I was looking for in each of the components where the store commits are made, like so:
/**
* check the validity of the reset token
*
*/
checkToken () {
if (!this.token) {
return this.$store.commit('NEW_MESSAGE', {message: 'No activation token found. Unable to continue'})
}
this.showLoader()
this.$store.dispatch('CHECK_ACTIVATION_TOKEN', {token: this.token}).then(this.hideLoader)
},
Here I have defined methods that shortcut the Vuex commits in a Master vue component that each of my components will extend. I then call showLoader when needed and use the promise to determine when the process is complete and call hideLoader there.
This means I have removed presentation logic from both the store and the API layer and kept them where they, arguably, logically belong.
If anyone has any better thoughts on this I'm all ears.
#wostex - thanks for your response!

AngularJS ui-router save previous state

I want to save the current state, I don't mean saving url and params, I want to save the entire view with scopes.. In fact, I want to implement a facebook like search system, but differently. I want to display the results on the entire page, so I'll replace the current main State, and when the search bar will be cleared, then we will restore the previous state. I don't want to rebuild the state to restore the state instantly (no server requests) + to avoid complicated operations (eg. infinite scroll data..). I've found ui'router extras (sticky state) plugin, but we need to define what state to save with states definitions..
I apologize for my bad english..
You can save the date in the local storage using angular local storage. It will save you a trip to the server for data.
This is an architectural decision. The answer to this question will not be short and can be very subjective and different people will have different ways to implementing it.
What I would suggest is that you leverage local storage and do all actions on data present in local storage.
The way you would do this is to first understand that all server requests need to be done through A service that return what's present in localstorage is network is offline.
So all AJAX calls will be done from the controller like this
var getMeRequest = AuthService.getMe();
getMeRequest.promise.then(function(response){
if(response.cached) {
// $update views if need be
} else {
//Some code
}
});
And in the AuthService file, you'd do it like this:
// In AuthService
this.getMe() = function () {
if(network.online) {
//Make AJAX call
// Update localstorage with whatever response
return {response: locastorageData, cached:false};
} else {
return {response: localstorageData, cached:true};
}
};
This will not only let you make all the ajax calls as if you were online, but also allow the controller to update the view based on whether the response is cached or not.

Categories