I am trying to normalize my state and can't quite figure out how to handle partial & complete resources, namely whether should they share the same slice of state or be relegated to their own slices.
A little more info:
When I make a request for a list of things, the server responds with a list of partials.
[ { id: 1, name: "thing1"},
{ id: 2, name: "thing2"},
...
{ id: N, name: "thingN"} ]
When I make a request for a single thing, the server responds with a more complete object.
{
id: 1,
name: "thing1",
img_url: "url1",
description: "description1",
... and more fields
}
Both the partial list and detailed information need to be present at the same time.
So should these partial and complete things all be in the same slice of state?
If so what is a best practice for managing whether a given thing has been partially or completely fetched?
Thanks for the help!
What's the purpose of fetching a single item? Probably showing it? Why not set up your state like:
this.state = {
allThings: [],
singleThing: {}
};
And fill it up accordingly.
You can go about this a few ways:
Have an incomplete object like so:
{
id: 1,
name: 'thing1',
detail: null //this would be a JS object when populated
}
How you would use this: in a component that uses the detail view, you check for null on the state. This means that you can lazy load details if you will need multiple in the future, as well as not have to retrieve them again if needed. However, this isn't great if information needs to be updated (you would have to resolve both null and the last retrieve date, can be risky for creating stale data).
Manage the two as completely different nodes on your state, where the detail view is either an array or just a singular object. This approach would work better if you want to be always updating the object on a lifecycle event (e.g. on a component mount you always want to refresh the data).
My personal preference is #1 - I use Firebase a lot and this works really well as the real time DB handles when the data should be updated, and then having them in the state means I can display data super fast to the user if it's pre-loaded.
Related
Creating an eCommerce type react application and part of it is merchants can create product listings(item title, item description, item price, etc). How do I allow them to add options to the listing like Size, Color, etc. It seems more confusing than just having input fields that can change a state variable.
An example of what a completed array should look like after a user enters options:
const options = [
{
optionName: 'Size',
values: ['small', 'medium', 'large']
},
{
optionName: 'Color',
values: ['blue', 'black', 'white', 'tan'],
}
]
Above is how it should be constructed to be sent to server.
Let me know if any more clarification is needed.
What you're describing is dynamic form construction, and is always much more complicated than a static form. This is not an easy feature to implement.
What you've sketched out there as dummy API request data is essentially the solution -- you just need to provide UI that enables that solution. Presumably you have an object that holds all your form data, so you can add a field like userDefinedOptions that gets initialized with an empty array.
Then, you'll need UI elements to allow adding a new user-defined option, which will probably include the display name of the option, as well as the list of choices for that option. This UI would then call an event handler that inserts the inputted values into your list of user-defined options.
Dynamic forms can get very messy very fast, so I'd recommend looking into a form library to handle some of the overhead. You'll also need to validate the dynamic user data before sending it off to the API.
Hope that helps!
I am new to Redux and RTKQ and believe that I have missed something. The use case I am going to describe just seems so standard that I cannot believe there isn't an inbuilt solution.
The High Level View:
Fetch data from the backend.
The user then updates parts of that data.
At some point the user clicks a button to persist the changes.
A bit more details
The fetched data is a list of profiles. These profiles have a name and a list of contacts.
Here is an example:
[
{
uuid: '<some id>',
name: '<SomeName>',
contacts: [
{
uuid: '<some id>',
label: 'Email',
type: 'email',
value: '<some email>',
},
],
},
{
uuid: '<some other id>',
name: '<SomeName>',
contacts: [
{
uuid: '<some other id>',
label: 'Email',
type: 'email',
value: '<some email>',
},
],
},
];
The user can add new profiles, alter existing profiles or contacts and can add new contacts to profiles.
As is typical in react the data structure is displayed with the help of various components.
Here is a tree of my current setup:
EditProfiles (handles saving/deletion of profiles)
-> ProfileList
-> ProfileListItem (transiently stores changes to the name)
-> EditContacts (transiently stores updates to channels or the creation of new channels)
-> EditContactList
-> EditContactListItem
The EditProfiles component fetches the data from the backend and sends any updates to the server (when the save or delete button is clicked).
But how do you correctly persist the updates before sending them?
After fetching the data RTKQ stores it. So far so good.
Now the user wants to change part of the fetched data. And here the trouble starts. Where do I store the changes before sending them?
I cannot store them as react state bc it's a complex object and there are all kinds of pitfalls if done incorrectly.
I could update them in RTKQ but there any changes would be overriden if the query is sent again for any reason.
Or, and this is what I ultimately went with: I can create a new slice and persist any changed data there. Because it creates a parallel structure the user's changes are save even if the query is sent again. Any changes to this state also cause a rerender (if listened to) no matter the complexity of the object.
But this has drawbacks of it's own:
I have to manually handle this slice, meaning I must make sure to add changes and also to delete them, after the user persisted them. I lose a lot of the neat automatisation of RTKQ.
I have to store duplicated data. Most of the time users only want to change part of a object, I still need to store all of it.
Because of 1. there is a lot of error potential that I do not have when just using RTKQ.
So the question basically is: Is there a more RTKQ way of solving this I am just unaware of?
You didn't miss anything - the job of RTKQ is to mirror the data you have on the server, and to keep that as sync as possible. It does only that one thing.
I would recommend using some kind of form library (formik, react hook form, react final form, ...) to handle that state while the user is editing it - or, if local state is really not an option, to go for a separate slice.
I'm writing an sort of project management app that'll have a large number of "tasks" with a lot of properties within them that'll be rendered throughout the app. I am looking at using React Query to prefetch, cache locally, and update this data from the server.
One key architectural thing I want to get right is that when I query or mutate Tasks[123] that I affect a single underlying object in the state and not get stuck with duplicate data everywhere. On first glance React Query seems to be perfect for this job if the Query Keys are setup right. However in their examples they don't seem to do this (or I'm failing to understand).
In their Basic Example they fetch some Posts on start and query using queryClient.getQueryData(["post", post.id]). As far as I can tell this is causing the data to be duplicated if I look at the provided ReactQueryDevtools window in the example.
Am I correct in thinking the example should be rewritten to use something like queryClient.getQueryData(["posts", {id: post.id} ])?
That is indeed the way I am setting up my query keys, so that I can do: queryClient.invalidateQueries(['posts']) and it invalidates all of them. But sometimes, you need more fine granular control. If that's the case, I'd do:
["posts", "list", { filter: "all" }]
["posts", "list", { filter: "published" }]
["posts", "detail", 1]
["posts", "detail", 2]
that way, I can still tackle everything with ["posts"], all lists with ["posts", "list"], all details with ["posts", "detail"] and a specific detail with ["posts", "detail", id] etc.
It is also good practice to have a queryKeyFactory to create those keys, something like:
const postKeys = {
prefix: "posts",
lists: [postKeys.prefix, "list"],
list: (filter) => [...postKeys.lists, { filter }],
details: [postKeys.prefix, "detail"],
detail: (id) => [...postKeys.details, id]
}
Of course, I'm talking about "at scale" here. None of this is really needed for a todo app :)
I have the following store in my Sencha Touch 2.4.1 application:
Ext.define('NativeApp.store.Item', {
extend: 'Ext.data.Store',
config: {
model: 'NativeApp.model.Item',
storeId: 'item-store',
fields: ['id', 'description'],
proxy: {
type: 'ajax',
url: 'resources/json/item.json',
reader: {
type: 'json',
rootProperty: 'items'
}
},
autoLoad: true
}
});
It's being populated with everything in the JSON file, which let's say looks like this:
{
"items": [
{
"id": 's1',
'description': 'desc'
},
{
"id": 's2',
'description': 'desc'
}
]
}
Let's say that in my view I have two buttons, "s1" and "s2".
If I click on "s1", I am taken to a page with a list that contains the data with id "s1". (In this case, there is only one, but there could be more).
How would this be accomplished?
Idea 1:
The autoload attribute can be set to an Object. From here - "If the value of autoLoad is an Object, this Object will be passed to the store's load() method."
So if in my controller, in the handler that's executed when the button is tapped, I can generate this object (parse the JSON file) and somehow pass it to the store - mission accomplished. Sort of.
Besides the question of how to pass it to the store, the immediate problem is when is the data loaded into the store?
According to the docs, "this store's load method is automatically called after creation". Is creation when the app launches, or when I create the view object that uses the store:
var view = {xtype: 'myview'};
Idea 2:
Maybe I could dynamically switch out the url from the proxy and have separate JSON files for each id (there won't be too many). But that seems unlikely to work.
Idea 3:
Pass in a data object to the store (again parsing the JSON) from that controller.
Are any of these feasible? And if not, what is an alternative solution?Perhaps I'm overthinking this.
Edit:
I have a list of items, and each item has a list of sub-items. So when I select one item, I want to be taken to page of the sub-items. Right now, all of these sub-items are in one JSON file, so after clicking on one item, I need a way of telling the store to load only part of the json data.
Is Ext.data.model.load still the best approach?
Idea 1:
Yes, you could do that... probably more trouble than it's worth IMO. The store will load immediately after it is created -- so if you create the store at application start, then it loads then. If you create a store randomly during runtime, it loads then. If the store is defined inline for a view, it's loaded when the view is created.
Idea 2:
Arguably a better solution, but still a lot of trouble to swap the URL for the proxy, then forcibly load it, parse the data, etc.
Depending on your view, you could just directly load the data for a single model using NativeApp.model.Item.load(id) (see docs). This assumes you have an API setup for that, but it's easier IMO and certainly more RESTful.
Idea 3:
Meh. It really sounds to me like Ext.data.Store is the wrong construct for what you're trying to accomplish.
Are any of your ideas feasible? Yes.
Are you over-thinking this? Slightly, but then again you wouldn't be a programmer if you didn't.
It's hard to tell you exactly what I'd do without seeing the rest of your application and understanding the WHY you're doing X, Y, or Z... but if you're only planning on displaying a single model's data on a given "Detail" screen (and you need that data loaded on-demand), then the static Ext.data.Model.load() method is probably what you need.
Say I am building an API with a call that returns a collection of cities, each of which has a relationship to a state. A state has many cities, but a city has only one state.
I can imagine flattening the relationship and obscuring the underlying structure of the data like this,
{ cities : [
{ id: 1,
name: "Los Angeles",
state: "CA" }
]}
Or I can imagine structuring the JSON such that the relationship between cities and states is apparent,
{ cities : [
{ id: 1,
name: "Los Angeles",
state: { id: 1,
name: "CA" }
]}
The consumer of the API, as of now, only ever needs to know the name of the state. He does not need to know its ID or a way to get more information about the state. What are the pros and cons in structuring the JSON in either way?
In my opinion you should not add useless information to you api, but as #kgb said if your api is prone to be expanded you should design it that way. You asked about the relationship between the cities and states, and in my opinion this relationship is already defined in both of them.
So if you are 100% sure your api will not expand the state functionality, you should go with option 1. If there is only a slight chance, i propose you do this:
{ cities : [{
id: 1,
name: "Los Angeles",
state: { name: "CA" }
}]
}
That depends on the other consumers. Do you have any? Are you planning to?
An API is a machine interface, it is equally easy for the consumer's developer to use both structures. If the "state" entity is not a compound entity(no usable properties except name), it might me a good idea to show just the name, not a structure with the id.
If there is a possibility that state id might be useful in future, or a possibility that a new property is going to be added to state entity, then you should use the second approach from the start. Changing API in any way breaks the software already written, so changing it will make you support two different versions. Change between approaches 1 and 2 is not backward-compatible.
I would go with approach 2. It is not much more complex than 1 and leaves a possibility to extend state entity.
There is also a third approach. But it is noticeably more complex(and more extendable). Return state id only and create a method for state entity retrieval.