Is breaking up React global state into pieces a good idea? - javascript

I'm constructing a React App that is basically a photo sharing app.
Here's some use cases:
User can upload photos and videos
User can see the photos in a list view
User can reorder the photos in the list
User can select photos from list and performs actions based on their selection
Users can attribute properties to these photos, such as message, title, etc. Lets call an image + its properties a Post
Here's some major architectural components:
A CDN service to upload, host, and transform image and video creation
A backend application paired with a DB for persistent storage
I'm looking for a good way to organize all this data in state. One thought I had was to break up the state into separate, simple data structures.
Roughly this:
posts <Array> maps Post index to Post ID
media <Object> maps Post ID to Image Urls
selectedPosts <Object> maps Post ID to Boolean
loadingPosts <Object> maps Post ID to Boolean
So here we have four data structures.
posts: Determines what posts are in state and in what order
media: Attributes Post IDs to Image URLs
selectedPosts: Determines what posts are selected
loadingPosts: Determines if a given post is loading or not
I'm surfacing these via four React Contexts
Breaking up state into separate contexts makes it really easy for dependent components to subscribe to exactly the state they need. For example:
import React from 'react'
import useMedia from '/hooks/media'
export default ({ postId }) => {
const { media } = useMedia() // useMedia uses useContext under the hood
const imageForThisPost = media[postId]
return (
<Image src={imageForThisPost}/>
)
}
What I really like about this is that this component gets exactly the state it needs from global state and really only has one reason to re-render (pretend i'm using useMemo or something). I've worked with some tough React Redux web apps in the past where every component re-rendered on any state change because all the state was in one data structure (albeit memoized selectors could have fixed this).
Problems arise when it comes to use cases that impact multiple contexts. Take uploading an image as an example:
The sequence of events to upload a photo looks like this:
An empty post with ID, "ABC", is selected. Update selectedPosts context
User uploads a file and we wait for CDN to return image url
Update loading context of post ABC (loading == true)
(receives image url)
Update posts context at ABC
Update media context at ABC
Update loading context of post ABC (loading == false)
Deselect post ABC. Update selectedPosts context
Long, intricate, async sequences like this are tough to deal with, encapsulate, reuse, and test.
What's a better way to organize state for medium sized web applications like this with potentially long sequences of async actions and somewhat complex state?
Wishlist:
Easy to control re-renders
Easy to add extend/change app functionality (not a fan of huge deeply nested data structures)
Easy to test
Does not use Redux (but useReducer is fine) (I just don't like the huge overhead that comes with redux)
Anyone have any thoughts?
I know one way might be to emulate Redux using useReducer, actions, and selectors. And thankfully dispatch is a stable function identity in React. Idk, I just really don't like dealing with big, deeply nested objects. When product requirements change, those are such a pain to deal with because the entire application depends on a particular schema shape.

Old post, but am assuming that selectedPosts and loadingPosts are filtered posts objects.
Personally I would probably not have them as separate, but filter of posts which are marked loading / selected and upload those in code via some action?
I.e. state has list of posts, with loading / selected props and code does the filter on posts to upload - are you over complicating it? Presuming with some lookup one to many / many to many? on state for media to create the association? What did you end up doing? I would be using useReducer and dispatching updates to state, not sure context is needed unless its deeply nested.

Related

Nuxt data pre-loading with localStorage

I have a question about possibilities of data pre-loading with nuxt.js
I'm currently working on a project that is strongly coupled with back-end data and I stumbled on some difficulties.
I would like to save some data in localStorage to speed up the navigation within my website, the data consist of dictionaries containing (key: value) pairs that I use to resolve item properties names that come from api calls on query for specific item. (oh and they can possibly change in the future with more languages coming)
When the localStorage wasn't initiated (first visit/storage cleared/disabled) I would like to fetch that data to Vuex store so I can use store getters in Vue components.
Some problems emerge with my approach as localStorage is available from mounted() and not before that point in components life cycle. So I can't detect if there is any cached data before the whole page is already rendered.
I would like to hear any ideas, is there any workaround, maybe this way of cashing data is simply wrong. I'm just at the beginning of front-end journey.
Sidenote - I have wrote a custom plugin for async calls to api but Nuxt renders all components before I even commit the chages to Vuex store.

Implementing own localization engine in React.js - is it worth it?

I decided to write my own localisation engine using react hooks.
I don't need too much localisations, just want to display messages depending chosen locale.
I have some questions about performance and etc.
This is my translation component which simply returns message by given key:
import React from 'react';
import useLocale from 'store/LocaleContext';
export default ({ k }) => {
const { messages } = useLocale();
const msg = messages[k] ? messages[k] : k;
return <>{msg}</>;
};
As you can see it uses react hooks and i have messages asynchronously loaded in context provider.
My app is medium sized project and so I am planing to use 50-100 or more <Translate k="messagekey" /> component in it.
Does it affects my app performance if so many components are subscribed to one context which has provided about 5-6 KB data? (I mean messages will be about 5-10KB in size).
Is it good to use this engine instead of react-intl or react-i18n, if I ignore other localisation issues (date, time, currency, number ..., Just for messages)?
Edit:
https://codesandbox.io/s/test-localisations-8kynz This is sample codesandbox link.
You can see whole localisation files which I created.
Also I added translate function which doesn't returns component but only translated message. Is it good practice to do that?
The problem you might face using a Context is that every time the Provider updates, it will render. And every time a component renders its children render too (unless they are memoized).
In the case you describe I can think of two scenarios.
Are all messages loaded at once?
If this is the case you will have an initial render and another one after the data is fetched. From this point the data will not change and the Provider will not trigger new renders.
Are messages updated/added several times?
In this case every time the Provider updates all of its children will render. This can be an issue if there are many children and are not memoized.
If your case is the first one I would test if there is any issue managing the data inside a Context.
In the second case you might want to consider other options, like using Redux instead of a Context or possibly one of the libraries you suggest.
In any of the two cases, if you are managing a large amount of information in a single object you might consider using different Context and providing smaller sets of data to different parts of the app.

Passing values between components: Pass References vs Subjects

tl;dr: Why not pass variables by reference between components to have them work on the same data instead of using e.g. BehaviorSubjects?
I'm writing a sort of diary application in Angular 8. I have two components (Navbar and Dashboard) and a service (EntryService).
Navbar lists the entries, Dashboard provides the textarea, EntryService glues them together and communicates with the database.
While debugging the application I stumbled upon a way to communicate between the service and a component that i haven't thought of before.
By accident I passed a variable (entry: Entry) from the Dashboard by reference to the EntryService. The service saved to the database getting a unique ID back and saving this ID into the entry variable. This change immediately reflected to the Dashboard because of the 'passing by reference'.
Until now I was using Subjects to update the components on changes, but passing references around seems to be much simpler, because I want to work on the same data on both components and the service.
I've studied Angular for a while now and not read about this approach, so I'm wondering if it is a bad idea or design and if yes why?
Thanks for your answers!
Passing by reference can be handy. But as a general approach to keep the application components in sync it has some draw backs. With Subjects you can easily investigate in which places of the application the value of the Subject will be changed by checking where the Subject.next() function is being called. When you pass your object by reference to a hundred components/services it will be much more difficult to find out, which of them modify the object and more importantly when, becaue often you want to trigger other changes afterwards. When you subscribe to Subjects, you get notifications about changes and can react to them. Subjects and Subscribers are an example for an Observer/Observable pattern, it allows you to decouple your application logic. And they are much more flexible, for example you can have a Subject which can return the last x number of changes or you can debounce the changes when you track user input, you can apply filters to them etc.

Loading configurational data during page load in react app

I'm working on a React application that is connected to a few ASP.NET Core WebAPI microservices. Each of these services have different entities that are used throughout the application.
Within the complete application, there are enums and 'configurational data' that can be configured.
Imagine configurational data as just simple tables, with two fields (Id and Value).
Different entities have FK relationships to the configurational data, and/or have enum fields. I'm trying to understand how I would, in a performant way, can load the configurational data and all the used enums upfront upon page load, so that these can be used in dropdowns. I'm pretty new to React (1 month), so still learning day by day.
I've initially taken the approach of writing a custom DropDown component that accepts a WebAPI GET url, to fetch the possible values for a certain table or enum, but it's very impractical and will prove to be not so performant once there are 1000 users using the application, and all doing calls to these api's multiple times, just for some dropdowns.
So, what is the advised approach to have some sort of splash screen in React AND call APIs to cache values, that then can be used in other components?
"I've initially taken the approach of writing a custom DropDown component that accepts a WebAPI GET url"
You should not do this :)
Before I suggest a solution I want to go through a couple of important key concepts.
Firstly
The render method will always run once before you async stuff happens (like your GET).
Lifecycle methods order which will trigger the First Render : constructor => componentWillMount => render => componentDidMount.
This means that you will have to have all your data ready for render initially. Or have conditions which prevents certain jsx for being called.
Secondly
If you have dynamic content, which will be the options in your dropdown, you'll have to get it from somewhere. If it's static you can define a list locally.
If you want to save the response you could use localStorage or if you are using redux; the middleware redux-persist to persist the store.
I personally don't see the purpose though, because if the dynamic options updates you would want that to update the application state. And even 1000 simple calls like that is not expensive for the server.
If you are using redux, you should keep the options there, because then you won't have to make an GET every time you're mounting the component with the dropdown.
Suggestion:
Many ways you can do this but here is a simple solution).
keep a local state in component and initialize it for first render
this.state = {dropDownOptions: []}
Then in componentDidMount make api call:
fetch(url).then((response)=>this.setState({dropDownOptions: response}));
And lastly in your render method:
<MyDropDown options={this.state.dropDownOptions} .../>

Redux/sagas: Approach for larger API's

I've worked with Redux/sagas workflows on small projects based off of this real-world example, but the logic of those is not nearly as complex. How should I be approach working with a more comprehensive api (i.e., Reddit's API), without making things overly verbose?
Do I make a const for every endpoint? i.e.,
export const fetchUser = login => callApi(`users/${login}`, userSchema)
Should I be worried about managing the entity cache?
Is there a way how to further reduce complexity/boilerplate (i.e. further grouping request types with get/put/post/delete for the same endpoint)?
Are there any examples out there that deal with bigger/more complex than the real-world?
I think the answer depends on how fluid you want your components to be.
I'm working on a large codebase using sagas, our pages are separated into "types", for example a "list" type, "form" type etc.
We have one saga responsible for fetching content, while each pageComponent when being rendered is responsible for supplying the endpoints.
this allows a very modular approach, to add a component you need to deal with one subsection of your file system.
Our pages are mostly a configuration file that contains all this information, and we use this configuration to render a "generic" component with the correct data.
Saga reusability
I see Sagas as sequential processes, they can be for async fetching data, but they're also useful for anything that needs to be dealt with in sequence.
These "Flows" are sometimes very similar in a codebase, and those are the ones you want to generalize.
Like you said, the most common operations are CRUD for any endpoint, that can be easily grouped together.
Login is extremely different than loadUserList and different things need to happen afterwards, however loadUserList and loadRepoList is extremely similar.
Things that impact reusability
Your ability to control your API's, if you can dictate the shape of the API you consume, you can get away with even more generalizations in front end.
The shape of the application(Front-wise) - are your pages strangely dependent on one another's state? for example it's not uncommon for insurance programs to have forms that link to one another, you can fill the first 3 forms in any order you want, but once all three are complete the 4th unlocks.
Each of these dependancies will normally have their own saga that controls the flow of your use story.
Does your application require syncing? You can easily create sagas that automatically sync data with your different endpoints and update your Redux State, there's much to consider here, including if we decide to interrupt the user with new data(we might want to let him know that the form he's editing has out dated data) - syncs require a distinct saga as there are usually various business rules when to sync what data - if the rules are very different this can force you to create multiple sagas)
Common Sagas that can be unified
UserSagas - login, logout.
FetchData - fetch a single record, or a collection.
DeleteData - Delete a single record, or a collection of IDs
Data Syncing - Updating your local data from a remote periodically.
Regarding the entity cache
Entity cache is just a name they picked, but this goes back the points mentioned before.
Does your application run on stale data, or do you fetch from the server every time your component is loaded?
If the data is only fetched once and you display stale data, you'll store it in a type of cache(that's basically the redux store).
If you show stale data, this is the way to go.

Categories

Resources