How to persist UI component state data in Sapper? - javascript

In a Sapper app, I want to be able to persist the state of some UI components so I can navigate the app without losing state when the user returns to the pages using those components.
In a Svelte-only app, this is usually done with a custom store that uses the sessionStorage or localStorage API. A good example of that can be found in R. Mark Volkmann's book Svelte and Sapper in Action, ยง6.24:
store-util.js
import {writable} from 'svelte/store';
function persist(key, value) {
sessionStorage.setItem(key, JSON.stringify(value));
}
export function writableSession(key, initialValue) {
const sessionValue = JSON.parse(sessionStorage.getItem(key));
if (!sessionValue) persist(key, initialValue);
const store = writable(sessionValue || initialValue);
store.subscribe(value => persist(key, value));
return store;
}
Unfortunately, using stores that way breaks immediately in Sapper because the scripts run on the server first, where sessionStorage is not defined. There are ways to prevent some parts of code from running on the server (using the onMount lifecycle function in a component, or checking process.browser === true), but that doesn't seem possible here.
Persisting some state locally looks like a very common use case so I'm wondering what's the right way to do it in a Sapper app (considering that I haven't even found the wrong way).

Provide a dummy store for SSR.
It is always possible to do feature detection with something like typeof localStorage !== 'undefined'.
Your component code will re-run in the browser, even if the page was SSR'd. This means that if it is fed a different store, the browser-only values will take over and update existing state (inherited from the server).
See this answer for an example.

Related

Handling UI State in Remix.run

I am trying out remix and ohh boy.. I am stuck on creating a simple counter (clicking button increase count)
I guess I am not supposed to use the useState hook so I tried my luck with loader and action as I believe that this is where it should be handled in Remix
What I have on my component is:
export default function Game() {
const counter = useLoaderData();
return (
<>
<div>{counter}</div>
<div>
<Form method="post">
<button type="submit">click</button>
</Form>
</div>
</>
);
}
Server:
import { ActionFunction, LoaderFunction } from 'remix';
let counter: number = 0;
export const loader: LoaderFunction = async () => {
console.log('game object!', counter);
return counter;
};
export let action: ActionFunction = async ({ request, params }) => {
console.log('[action] game object!', ++counter);
return counter;
};
The code above will have counter always resetting to 0 on every click
Looked around some repositories and what I can find are those storing in Cookie/DB/SessionStorage etc, however what if I just want a simple state for my UI?
You are supposed to use useState for your client-side state in Remix.
If you clone the remix repository and do a grep -r useState in the remix/examples/ folder, you will find many occurrences of it.
For example you have a simple one in the Route modal example (in app/routes/invoices/$id/edit.tsx), being used for a form with controlled inputs.
What Remix does is make it easier to communicate between the client and the server by colocating their codes for the same functionnality, and providing simple ways to perform that communication. This is useful if you need to communicate the data to your server. If it's not the case, then it's totally ok to keep that information only on the client.
About server side rendering
Remix also server-side renders the components by default. Which means that it executes your React code on the server to generate the HTML, and sends that with the JavaScript code. That way the browser can display it even before it executes the JavaScript to run the React code on the browser.
This means that in case your code (or a third party library code you use) uses some browser API in you component, you may need to indicate not to server-side render that component.
There is a Client only components example that demonstrates how to do that. It includes an example with a counter storing its value in the local storage of the browser.

Can Relay work properly with Next.js SSR?

I have started learning Relay by writing a new Next.js application. I have so far been following the with-relay-modern example in the Next.js repo, and it has been working just fine for fetching data from the server. However, I have now moved beyond that example by adding a mutation, and things immediately stopped working.
The mutation updater looks like this:
function updateStore(
store: RecordSourceSelectorProxy,
formInstanceUuid: string,
) {
const mutation = store.getRootField("updateFormValue");
const newFormValue = mutation?.getLinkedRecord("formValue");
if (!newFormValue) {
throw "Expected new form value from server";
}
const localFormInstance = store.get(formInstanceUuid)
const localValueRecords = localFormInstance?.getLinkedRecords("values") || [];
for (const record of localValueRecords) {
if (record.getValue("partUuid") == newFormValue.getValue("partUuid")) {
console.log("Copying fields from server provided value to local value");
record.copyFieldsFrom(newFormValue);
}
}
console.debug("New state of store:", initEnvironment().getStore().getSource())
}
All it does is inject a "value" object returned from the server into a list of value objects in a local "form" object. As you can see I am dumping the state of the store on the last line, so I can confirm that the mutation worked as expected, and the local value was modified as expected.
However, the UI doesn't refresh. I have to reload the window to see the new state.
I can't for the life of me figure out what I've done wrong, so I'm starting to wonder if the example I was following only works for fetching data. I assume it's the QueryRenderer object that is responsible for refreshing the UI when the underlying store changes, and the example doesn't use one. I also can't imagine how a QueryRenderer could be added to the example without ruining SSR.
TL;DR
Does the "with-relay-modern" example work when adding mutations, or is my issue somewhere else?
I also started a Discussion on the Next.js GitHub page about this

Is the ngrx store persistent?

Is the ngrx store persistent? In other words can we close the browser reopen it, and retrieve state that was added to the ngrx store?
Currently ngrx/store doesn't support such functionality. But you can maintain state by using a library like ngrx-store-persist to save data to the browsers indexedDB, localStorage or WebStorage. This library automatically saves and restores ngrx/store's data. You just need to add redux keys that you want to store in the config (see "Usage" section).
The nrxg/store is in memory only. To manage state via the browser with something like pouchdb ngrx/effects can be used. More here.
You can maintain state by using a library like idb.js to save data to the browsers indexDB, then by using the ngrx effect you can have an init effect to reload the state back when webapp loads. here is a example code let say I want to reload selected language. the effect would be:
#Effect()
init$: Observable<Action> = defer(() => {
return from(storageDb.readData('app', Lang.ActionType.LANG_KEY)).pipe(
map((data: any) => {
const tmpData = data ? data.state : {dir: 'rtl', selected: 'ar'};
this.translate.setDefaultLang(tmpData.selected);
return new Lang.LanguageInit(tmpData);
})
);
});
the storageDb.readData is a wrapper for idb to load data by key, the effect will kick in when effects are getting loaded it will get the data from the indexdb and if it does not find any sets default value then dispatchs an Action. ngrx best practices is to use promises in effects not in reducers since loading the data is async operation, then in your state reducer you can catch the Action like so:
case lang.ActionType.LANGUAGE_INIT: {
return {...state, ...action.payload};
}
Using it in this way you can slice your states save them and loaded them even when lazyloading.

Is window.__INITIAL_STATE__ still the preferred way to pass initial state to the client in React Universal apps?

I'm currently reading a book about React and Universal apps in which the author claims that the following is best practice to pass initial state from server to client:
server.js
import React from 'react';
import {renderToStaticMarkup} from 'react-dom/server';
import Myapp from '../MyApp';
import api from '../services';
function renderPage(html, initialData) {
return `
<html>
<body>
${html}
</body>
<script>
window.__INITIAL_STATE__ = ${JSON.stringify(initialData)};
</script>
<script src="bundle.js"></script>
</html>
`;
}
export default function(request, reply) {
const initialData = api.getData();
const html = renderToStaticMarkup(<MyApp />);
reply(renderPage(html, initialData);
}
And then, in the client you would read out the data like this:
bundle.js
const initialData = window.__INITIAL_STATE__ || {};
const mountNode = document.getElementById('root');
ReactDOM.render(<MyApp />, mountNode);
From what I understand is that the initial state first gets converted to a string and then attached as a global object literal to the window object.
This solution looks very rough to me. The book was released in mid 2016. Is usage of window.__INITIAL_STATE__ still the way how to do this or are there better solutions?
For example, I could imagine that it would be possible to offer the initial state in a separate micro service call which then could also be cached better than if the data is embedded directly into the document because then the initial state data has to be transferred every time the page refreshes, even if the data hasn't changed.
Simple answer: Yes.
But I'm not sure why no one has pointed out that you have a very common XSS vulnerability using JSON.stringify(initialData) what you want to do instead is to:
import serialize from 'serialize-javascript';
window.__INITIAL_STATE__ = ${serialize(initialData)};
HTTP works by caching responses, in your case, if the initial state will always be the same, you can also cache this in server side and display it in the page, it will work faster, because react will have access immediately to this value so it will not have to wait. Also you can also force the browser to cache the page, so the response for the page will be the same with the initial state not changing.
With the extra call request, you rely on the browser to cache that call, but you'll have to build an extra step, make react re-render when the information arrives or block react to render until the information is ready.
So I'll go with number 1, gives you more flexibility and some other nice to have, like server rendering, which can be easily achieved after having the state loaded in the server.

Structuring a Vue + Vuex project

I am kind of confused here on where to place my global functions. In a lot of examples a main.js file points to an app component and this is placed somewhere within the html. This workflow would be fine for me If I were to simply contain all my logic within this app component. But I am combining components with Laravel functionality so this does not work for me.
Currently my main.js file contains a bunch of methods that I need to have access from anywhere in my app. These methods don't contain any broadcasting events so they can effectively be placed anywhere as long as they get a vue-resource instance.
My main.js file:
https://github.com/stephan-v/BeerQuest/blob/develop/resources/assets/js/main.js
Hopefully somebody can tell me where I could place my friendship methods if I were to use vuex or in general since this does not seem like best practice at all.
Thank you.
Vuex manages all of the data in your application. It's a "single source of truth" for data on your front-end. Therefore, anything that changes the state of your application, such as adding a friend, or denying a friend, needs to flow through Vuex. This happens through three main function types, getters, actions, and mutations.
Check out: https://github.com/vuejs/vuex/tree/master/examples/shopping-cart/vuex
Getters are used to fetch data from storage in Vuex. They are reactive to changes, meaning if Vuex data changes, the information in your component is updated as well. You can put these in something like getters.js so that you can import them in any module you need them in.
Actions are functions that you call directly, ie. acceptFriendRequest when a user clicks the button. They interact with your database, and then dispatch mutations. In this app, all of the actions are in actions.js.
So you'd call this.acceptFriendRequest(recipient) in your component. This would tell your database to update the friend status, then you get a confirmation back that this happened. That's when you dispatch a mutation that updates the current users' list of friends within Vuex.
A mutation updates the data in Vuex to reflect the new state. When this happens, any data you are retrieving in a getter is updated as well. Here is an example of the entire flow:
import {addFriend} from './actions.js';
import {friends} from './getters.js';
new Vue({
vuex:{
getters:{
friends
}
},
methods:{
addFriend
}
}
store.js:
export default {
state:{
friends: []
},
mutations:{
ADD_FRIEND(state, friend) {
state.friends.push(friend);
}
}
}
actions.js:
export default {
addFriend(friend){
Vue.http.post('/users/1/friends',friend)
.then((response)=>{
dispatch("ADD_FRIEND", response) //response is the new friend
})
}
}
getters.js
export default {
friends(state) {
return state.friends;
}
}
So all of these are organized into their own files, and you can import them in any component you need. You can call this.addFriend(friend) from any component, and then your getter which is accessed from this.friends will automatically update with the new friend when the mutation happens. You can always use the same data in any view in your app and know that it is current with your database.
Some misc stuff:
getters automatically receive state as a variable, so you can always reference the state of your Vuex store
mutations should never be asynchronous. Do fetching/updating in actions and then dispatch mutations just to update your data
creating services (or resources) using Vue Resource will make fetching/updating/deleting resources even easier. you can put these in separate files and import them in your actions.js to keep the database retrieval logic separated. Then you'd be writing something like FriendService.get({id: 1}) instead of Vue.http.get('/users/1'). see https://github.com/vuejs/vue-resource/blob/master/docs/resource.md
Vuex works with vue devtools for "time-traveling". You can see a list of every mutation that has taken place and rewind them/redo them. It's great for debugging and seeing where data is being changed.

Categories