I'm building a real-time "lobby" type web app that hosts multiple users (2-8 at a time), where the state of the lobby is shared among the users. The UI is built with React. Each user establishes a websocket connection to the backend upon joining the lobby. At this time they receive the full global state of the app as a JSON object (its size should not exceed a few kilobytes).
I'm having difficulties conceptualizing the precise state maintenance scheme, and would like to hear your views about it, once I've described the situation in more detail.
The lobby presents to the users a number of finite resource pools, access to which is shared by everyone. The users will move these resources between each other as well as to and from the pools. My current thinking is that the full state of the lobby and all of its resource pools is stored and maintained exclusively in the backend. When a user wants to move a resource e.g. from a pool to themselves or vice versa, or to change the visible state of a resource, this is done with JSON messages sent over their respective websocket connections.
Each action they perform causes a message like this to be sent over the socket (simplified):
{
"action": "MOVE",
"source": "POOL1",
"target": "user_id_here",
...metadata...
}
The users send these messages concurrently at arbitrary times and intervals, and the backend (using a Python asyncio-based server and a data store still to be determined) receives them serially, reconciles each one with the global state in the order they arrived, and then sends the full updated state of the app to every user over their websocket connections, for every single message received. The user who performed the action that triggered the state update additionally gets a status object informing them of a successful transaction, which the UI can then indicate to them.
When a user sends an action message that is impossible to reconcile (e.g. another user has exhausted a resource pool just before their message requesting a resource from that same pool came in), the app still sends them the full up-to-date state of the app, but a status object is included, containing information that the UI uses to inform them that their action could not be performed.
So far, so good. Given the types of actions, types of resource pools, number of users and size of state objects that are to be expected, the frequency of updates should not become a problem, neither in terms of resources nor bandwidth use.
To clarify: none of the actions that the users perform in the React UI mutate their local state in any way. Each and every action they perform is translated into a JSON message like the example above, and the result of that action will be receiving the updated full state of the app, which fully replaces the previous state that React used to render the UI with. The React-level app state is ephemeral, only used for rendering it once. All renders exclusively happen in response to state updates over websockets.
The one area that I'm having difficulties with is how to structure that ephemeral state on the React side so that rendering the updated state object is as quick and efficient as possible. I'm a backend guy and have no prior experience in building a React app of this nature (I last used it four years ago in a really ad-hoc manner, passing props to deeply nested child components, with state stored all over the place). I'm not quite sure what facilities and tools to use.
For example, I could use a top-level context provider with the useReducer hook, touted by many as a "Redux replacement" (which it technically isn't). Or, I could use Redux, but does it actually add any value in this case? Or something else?
Given that the whole state is replaced as a result of every action of every user, what is the best, most efficient, least render time-requiring way of structuring the React side of things?
I would like to suggest that you do not send in the entire state of each and every user over the network instead just send in the modification and let the individual users apps perform the change handling. Once you make this change you could make use.of redux and store the states in a reducer. Also doing this will help you avoid a lot of re-renders as the object references will not change for a lot of your components,
Another thing to add here is that you can store the redux state in the localStorage when the session is terminated
FurtherMore, the one problem that you could have here is that when the user re-connects, he might not get the changes that happened while he was online.
To solve this, you can maintain a transaction id for each user so that the user is sent all the data post that transactionId till the current state by the server and then the app can process and update the transactions
Or the other approach if to completely fetch the data when the user connects for first time or reconnects.
As far as using useReducer or Redux is concerned, you need to decide that based on the complexity of your App.
Cases where the app is small might easily be covered with useReducer and useContext but if you states are complex and you need to maintain multiple reducers, you should go ahead with Redux as it provides moree flexibility with data storage
EDIT:
If the only solution for you is to send the data totally to frontend and let the frontend render it, then you need to divide your frontend code into various simpler modules as much as possible so that no component is using a a complex state.
Once you do that you can make use of shouldComponentUpdate or areEqual parameter to a class component or functional component respectively.
The idea here is to compare the previous and current value that you get from props and let go ahead with the rendering logic or not.
You can store the state as it comes to a reducer inside the redux state, that way, you would be able to implement selectors that are memoized and are able to return data which doesn't change if the actual value hasn't change.
Also while you are using connect for your React app component, its actually a functional component, so unless mapStateToProps returns a value whose reference changes, it will prevent the re-render itself since its a PureComponent
I would strongly suggest, you go through the documentation of shouldComponentUpdate, React.memo and redux. Also look into reselect library that helps you implement memoized selectors
Related
I'm working with a react app and currently working with a feature. The main task is showing some charts by getting data from API. And these charts will show the last 30 minutes' data.
I have questions,
In this situation, is it necessary to store these data in the state by Redux, though it can be handled at components very easily? And every time I refresh or request, I get new data (log base data).
When do we make the mind to store data in state and when not?
A redux store is a singleton, thus a single source of truth that can be made available to all components in the whole react application. If your state is intended only for one react component then you don't need a redux store. A useReducer react hook allows to well reproduce the redux pattern in a single component. Stick with a useReducer hook for a single component and use redux library for a store available to an app composed of several components.
Redux is not designed for the specif role of a special type of data.
You can use still store your temporary (30 min) data into redux, and use it to cross your feeling the same as the rest of your data.
But in this case, you might need to reset data after 30 minutes or invalidate your cache, keep your eye in react-query and RTK-query handling these types of actions more easily for you.
If data is being used for many states or those data are being used by many components then you should use redux. You can still go without redux, it is up to you after all.
If you have various components and routes then redux will help you to reduce the codes and also make the codes simpler.
Redux will give the one store for all the components in the project to store and access the data which is better then context or props tricks.
Also if you want to achive something like if user opened two different tabs. Let it be same page or two different pages of your website and if user done an action on page A and you want that page A or page B opened in another tab should get that update then redux can let you achieve that. Context and props passing are not useful in this case.
https://redux.js.org/faq/general#when-should-i-use-redux
Redux is most useful when in cases when:
You have large amounts of application state that are needed in many places in the app
The app state is updated frequently
The logic to update that state may be complex
The app has a medium or large-sized codebase, and might be worked on by many people
You need to see how that state is being updated over time
Let's figure it out,
An user performs a login submission, so app shows instead a
Submit button a Spinner, a self contained state whose help us (isLoading).
Okay, when application send to saga login action we can pass a callback
for set false loading state when login submission has successful or failure.
Some experts will say, manage loading state in reducers, but carry to all whole application
loading state, for some specific action not sounds good.
The problem with callbacks is that the architecture doesn't guarantee that the callback gets called or that it won't get called multiple times. That is because redux actions are essential events - where each event can be handled by 0-n handlers (or sagas in our case).
Of course at the time of writing you know that that particular code is handled exactly once, but for anyone else this might be hard to grasp unless there are strict rules in the project how to handle this.
At the same time, you are right that putting local state to redux store isn't great. I usually deal with this by moving the data logic to its own structure. So e.g. loading collections of items from server is no longer local state of some component bur rather global data state that can be used and reused by multiple parts of the applications. This will also make it easier to have custom caching logic for the data cross whole application etc. However, some local component state in redux is still unavoidable for some specific backend calls.
In terms of future, I saw some attempts at useSaga hook, which would work on top of local useReducer hook and therefore local state, however the implementation for such logic is still limited because the current react hook api lacks certain functionality that is necessary to make sure this works well with react commit phase, render bail outs, reducer reruns etc.
I’m using Redux to manage the logic of a music player. When it’s playing something and suddenly fails, I want to be able to intercept that action and make the player start again, but only n times, as I don't want to keep automatically retrying forever if the problem isn't recoverable.
I think a middleware is a good option to implement this, but I’m wondering if I should store the number of retries already done for a certain item either in the global state, or locally in the middleware.
A more general question would be if a Redux middleware should contain any local state at all, I mean, state that only it cares about.
In my opinion (and based on limited information about your specific project):
you should configure the n value via the middleware constructor, and
you should store both n and totalRetries in private variables of the middleware.
Why?
Choosing where to store your middleware state shares similarities with deciding between component state and redux state. You can use the same questions as a guide:
Do other parts of the application care about this data?
Do you need to be able to create further derived data based on this original data?
Is the same data being used to drive multiple components?
Is there value to you in being able to restore this state to a given point in time (ie, time travel debugging)?
Do you want to cache the data (ie, use what's in state if it's already there instead of re-requesting it)?
Do you want to keep this data consistent while hot-reloading UI components (which may lose their internal state when swapped)?
As Dan Abramov said:
The way I classify it is when ever state needs to be shared by multiple components or multiple pages and we need to persist some data over route changes, all that data should go inside the redux store.
You can map this idea from components to middleware by rephrasing "…persist some data over route changes…" to "…persist some data over [insert relevant boundary here]…", for example closing and relaunching the app.
The totalRetries doesn't seem to represent a meaningful state of the application. It has to do with some "behind-the-scenes" I/O operation that wont persist across closing the app or sharing app state with a debugger. One might even argue that you should not expose it to other components via redux state, lest they rely on (potentially shifting) internal workings of your middleware.
redux-saga, etc allow us to write this type of functionality in a very neat and testable way without having "exposed wires" in the application state or module namespace. You could use a saga to encapsulate your entire "play audio" behavior.
Exporting a reducer and then accessing its compartmentalized "public" state from your middleware introduces quite a bit of unnecessary complication.
If you want to expose totalRetries and n to other components, you may do so via an action, such as PLAY_AUDIO_RETRY with the action containing both variables in its payload.
I am working on react app + redux.
I have some HTTP requests that I don't think that should be in the state of my app. (for example : in case of autocomplete, I fetch only relevant data, I don't have to use this data anywhere but in the specific input).
Is it a bad practice to have folder of "statless services" inside an react-redux app?
Do I have to keep every request result in the state?
What about POST and PUT requests?
Thank you.
Speaking broadly is not bad to keep something out of the state if it's not relevant or shouldn't influence render or state.
Take for example a live graph that updates every second. You don't want it to trigger a re render every time it updates.
Or some external service for example Google Maps Auto Complete. It takes control of the dom, does external calls and so on but at the end of the day what you want for your app is the location when the user perform some action.
All the several state changes that it does on the map and on the info until the user decide that there is his location is not relevant and shouldn't be a part of your state.
This could be opinion based... It's a bad practice, that's transient state no one else is interested in.
I suggest you to use https://redux-saga.js.org/ to avoid pollute your logic with side effects and take care of this common transient states.
So I'm coding in React,
and as far as I understand, every time a user make a local update in a realtime distributed system(such as Firebase, which I'm using) there needs to be a call to update first Firebase, and then if successful, a new object (the old object + the new data we pushed locally) can be sent back from Firebase to the React's Stores and then app will use that object to re-render starting with the top level component of the app.
Yet it feels like my understanding is off because it seems like a complete re-rendering be slow. I think of ajax situations where only the piece that needs to be updated is updated so have smooth updates.
So do I return a whole object and have it re-rendered entirely or just use a success event to go ahead and add that piece of data to the store it belongs to?(which I'm assuming would only cause a re-rending of only the component that is effected by the change?)
In Reactjs, the way to handle this is return the whole object and pass it in to the app the same as for the first response. However, each of your components should implement shouldComponentUpdate to determine whether the new data requires that it update itself. If there's no need, it returns false and that part of the UI does not re-render.
This keeps the app's code simpler since it always does the same thing and you don't have to worry about piecemeal data responses. However the implementations of shouldComponentUpdate can become complex.