0
I wrote a framework called React Server, that mimics React on the serverside. It features a few familiar concepts from React, like components, states, hooks, effects but on the serverside.
A serverside component looks the following
export const HelloWorldExample2 = () => {
// The useState hook looks familiar?
const [count, setState] = useState(0, {
key: "count",
scope: Scopes.Global,
});
// A simple function that can be executed from the client side.
const increase = () => {
setState(count + 1);
};
return (
// Simply pass down props to the client side.
<ServerSideProps
key="hello-world-2-props"
count={count}
increase={increase}
/>
);
};
See https://state-less.cloud for more information
The concept is similar to react on the frontend. You have a useState
hook and calling the setter re-renders the component on the server. Components that are rendered on the server can be consumed by a clientside hook useComponent
which receives all the props passed from the server side. If a component re-renders on the server, all connected clients receive the updated state.
Now suppose you have a modestly complex server-side application, illustrated in the below code.
const Item = (props) => {
const [item, setItem] = useState(props);
const toggle = () => {
setItem({
...item,
completed: !item.completed,
});
};
return <ServerSideProps toggle={toggle} {...item} />;
};
const List = () => {
const [items, setItems] = useState([]);
const createItem = () => {
const newItem = {
id: uuid(),
title: "New Item",
completed: false,
};
setItems([...items, newItem]);
};
return (
<ServerSideProps createItem={createItem}>
{items.map((item) => (
<Item {...item} />
))}
</ServerSideProps>
);
};
const ListContainer = () => {
const [lists, setLists] = useState([]);
const createList = () => {
const newList = {
id: Math.random(),
title: "New List",
items: [],
};
setLists([...lists, newList]);
};
return (
<ServerSideProps createList={createList}>
{lists.map((list) => (
<List key={list.id} {...list} />
))}
</ServerSideProps>
);
};
This is only pseudo-code as I can’t post huge real-world code.
Now suppose a connected client calls the toggle
function. This would re-render the Item
component on the server and send the updated component to the client. This is ok, as there’s not much changed data that needs to be sent to the client.
However, suppose you call the createList
function. This would re-render the ListContainer
component which keeps all the lists. Re-rendering the component is not costly, but sending the data is. Imagine you have hundreds of lists with hundreds of items.
Sending the whole updated lists array every time a list is created and another item is added to an array seems unneccesary.
Most of the data hasn’t changed so I don’t need to send it again.
I suppose instead of sending the whole state, I figure out what changed on the server (. e.g. inserting an object into the array), I then generate an action e.g. {action: 'insert', 'prop': 'lists', data: {...}}
.
On the client side I somehow mutate the GraphQL cache inserting the data at the right place.
You can reproduce the problem in a current app. If you head over to https://lists.state-less.cloud and add a lot of lists each with a lot of items, you can see that adding a list becomes increasingly slow.
It’s still working as supposed, but I figure this is a point worth optimizing as the optimization can be done in the framework and everything built with it would benefit from the optimization.
I’m not sure if there’s not a better solution. It seems like there are a lot of pitfalls implemting incremental synchronization. What if one request get’s lost and the states grow out of sync, then you would need to reload the page.
Are there some practical guides on how to properly implement such a process? I fear that it’s a lot of effort and that I may introduce hard to find bugs.
1
1 Answer
Reset to default
0
Honestly, your solution feels like an overkill, feels like you’re recreating Next.Js
with added complexity, but maybe I’m misunderstanding something.
I don’t understand why you need to store whole react components on server.
I’ve done synchronized, enterprise grade, react apps in the past and the way I handled it is this:
Shared Global state was stored in-memory on server. This contained only the relevant shared parts of the state. This state was stored in Redis
.
When website initially loaded that state was fetched and loaded into Redux
, or other state management solution. This kept the components synchronized.
Every time one of the actions would update Redux state, it would also dispatch a server event. Server would update server state and then propagate the actions to other connected clients.
When connected clients received the event, they would also dispatch local Redux action, that kept the state updated and in-sync.
Why this approach works nice is that on each update you just send relevant action, with relevant data.
There are papers written on data synchronization, conflict resolution, etc. and it's complex. (But almost all apps are easier than games which have additional considerations.) It'll boil down to how consistent things need to be and when, and how likely collisions with other actors' data are likely to be.
1 hour ago