Handling UI State in Remix.run - javascript

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.

Related

calling setState only once inside of useEffect--is there a better method?

In my react app I use the following pattern quite a bit:
export default function Profile() {
const [username, setUsername] = React.useState<string | null>(null);
React.useEffect(()=>{
fetch(`/api/userprofiles?username=myuser`)
.then(res=>res.json())
.then(data => setUsername(data.username))
},[])
return(
<div>
{username}'s profile
</div>
)
}
When the page loads, some user data is fetched from the server, and then the page updates with that user data.
One thing I notice is that I only really need to call setUsername() once on load, which makes using state seem kinda excessive. I can't shake the feeling that there must be a better way to do this in react, but I couldn't really find an alternative when googling. Is there a more efficient way to do this without using state? Or is this the generally agreed upon way to load data when it only needs to be done once on page load
Without using any external libraries, no - that is the way to do it.
It would be possible to remove the state in Profile and have it render the username from a prop, but that would require adding the state into the parent component and making the asynchronous request there. State will be needed somewhere in the app pertaining to this data.
The logic can be abstracted behind a custom hook. For example, one library has useFetch where you could do
export default function Profile() {
const { data, error } = useFetch('/api/userprofiles?username=myuser');
// you can check for errors if desired...
return(
<div>
{data.username}'s profile
</div>
)
}
Now the state is inside useFetch instead of in your components, but it's still there.

React useEffect() hook highly affects SEO

I have a static website made with react that requests data from the backend in the useEffect() hook:
export default const App = () => {
const [data, setData] = useState("");
useEffect(() => {
server.get().then(data => {
setData(data)
})
})
return(
<title>{data}</title>
<h1>{data}</h1>
)
}
However, when Bing crawls the webpage, the following problem occurs:
Bing Screenshot:
<title></title>
<h1></h1>
How can I solve this issue?
React isn't used for static sites. If you'd like to have better SEO and server-side rendering you can use nextjs.
The way your app is setup currently will only return some HTML with and empty body to a GET request to / (which is what I suppose crawlers like the one you mentioned use) and starts rendering components after the JavaScript is loaded.
But if you decide on a server-side rendering approach, whenever a request is made to your app the server will first render the app on it's side and the return an HTML string with the rendered components.
Did you check if your server.get() is returning some data? I can't see any url here, so maybe it's actually returning nothing.
Even so, maybe you forgot to pass the second argument of useEffect, which is an array of arguments, which this hooks uses to trigger itself. For example, if you want to trigger only once, when component is mounted, you need to pass [] as second argument of useEffect.

How to persist UI component state data in Sapper?

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.

Retrieve the status from multiple axios requests called from multiple components

I have a sort of Dashboard in my application. In this dashboard I let the user put many widgets (each widget is a class component). Each widget renders different stuff, such as, charts, images, and text. When I display it, each widget make an axios call to retrieve data from backend. I need a way to be able to tell when all the requests have finished so I can get the HTML completely rendered (I'm going to export it using HiqPdf later).
I need each widget to be independent so I can use in other components. That's why each widget make its own axios call. Otherwise I think I could make many axios calls in a single component that is above my widgets and then I would pass all the data as props to each widget. However, no... the axios calls must stay inside each widget.
I've found many places talking about promises, but every example talks show how to do it in a single component.
The reason I'm working on it is because I have the need to export it using a library call HiqPdf. This library receives a HTML as string and exports to PDF. Therefore, I need to know when the dashboard has been completely loaded to let the application export it.
Think about an event-driven framework that persists the global state of your single page app and notify subscribers whenever there is a change in the state.
One of the famous frameworks is redux.
Another simple framework is mufa. These are some similar questions that leverages mufa:
https://stackoverflow.com/a/42124013/747579
Stop the communication between react components using mufa after a condition
For your case, it might be something like this:
const all_calls = [];
const {on, one, fire, unsub} = mufa;
axios.get('call1').then((data) => {
fire('call1_received', data);
})
axios.get('call2').then((data) => {
fire('call2_received', data);
});
one('call1_received', () => {
all_calls.push('call1_received');
if (all_calls.length === 2) {
alert('!!!! All calls have been received')
}
})
one('call2_received', () => {
all_calls.push('call2_received');
if (all_calls.length === 2) {
alert('!!!! All calls have been received')
}
})
Note, one will subscribe once only.. while on subscribe forever.

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.

Categories