How to handle IDs when optimistically creating an entity in Redux? - javascript

I have a To-Do List app that creates a TodoItem by dispatching a CREATE_TODO_REQUEST action, which causes a middleware to make aPOST request to an API and respond with CREATE_TODO_SUCCESS with the newly created TodoItem returned by the API. This ToDoItem has a messy hexadecimal ID (like 59e52a5ec8dae14f2420a9ef) assigned to it by our database.
The problem is, sometimes the API could take a few seconds to respond (especially if the user is on a weak connection), so I'd want to optimistically update our application state with the new ToDoItem before the server is done processing it.
This pattern gets messy because all my TodoItems are indexed by ID in my Redux store, and their order is stored in a list of IDs. These IDs are generated by the API after a ToDoItem gets created.
{
byId: {
59e52a5ec8dae14f2420a9ef: {...},
59e52a5ec8dae14f2420a434: {...}
},
ids: [
'59e52a5ec8dae14f2420a9ef',
'59e52a5ec8dae14f2420a434'
]
}
My question is, what ID should I assign my eagerly-created ToDoItem while I wait for the API to return the newly created ToDoItem with a proper ID? Is there an established pattern for handling this type of situation?
I could use a random number generator to create a provisional ID and replace it with the real ID when the CREATE_TODO_SUCCESS action is dispatched (see sample app state below).
{
byId: {
59e52a5ec8dae14f2420a9ef: {...},
59e52a5ec8dae14f2420a434: {...},
"provisional-todo-1": {...} // this is being created on the API rn
},
ids: [
'59e52a5ec8dae14f2420a9ef',
'59e52a5ec8dae14f2420a434',
'provisional-todo-1'
]
}
But this might require some complex logic keeping track of which provisional ToDoItem is associated with actual ToDoItems that are later returned from the server. Additionally, there is the complexity associated with making sure actions dispatched against provisional ToDoItems (marking as complete, editing, deleting) are applied to the correct "real" ToDoItems after they are created.

The easiest answer is to create an local object with a mapping to the remote id.
For example, it might look something like this:
class Todo {
constructor() {
this.id = 'local' + Todo.globalId;
Todo.globalId += 1;
this.remoteId = null;
}
resolve(remoteId) {
this.remoteId = remoteId;
}
}
Todo.globalId = 0;
In redux, you could store these Todo objects, and use those internally to track your state. Then, when the API finally comes back with a value, you can set the remoteId. If there is some failure you could remove the local object or perhaps set a flag.

Related

How make secondary HTTP request based upon results of first

I have two async requests I am trying to fulfill, the second based upon the results of the first. The way I am trying to do this is by:
Listen for success of first action: actions.GetAllItems
Select out from the store the relevant items based on ID: this.store.select(selectors.getItemsById)
Map over the returned IDs so I can make the second call for each item in the array of IDs returned by the first call
Put results in redux store, render to view.
The way I have now does successfully put it in my redux store. However since it's just vanilla Array.map it doesn't return an observable. Which means the observable isn't stored in this.details$, which means it does not render in my template with {{ details$ | async | json }}
How can I achieve this secondary XHR call based upon the results of the first?
ngOnInit() {
this.store.dispatch(new actions.GetAllItems())
this.details$ = this.actions$.pipe(
ofType(actions.types.GetAllItemsSuccess),
mergeMap(() => {
return this.store.select(selectors.getItemsById); // filter to multiple items based on item ID
}),
map((items: models.IItemGeneralResponse[]) => {
items.map(item => { // sync map does not seem like it belongs in rxjs
this.store.dispatch(
new actions.GetItemDetail(item.id)
);
});
})
);
}
You are trying to do ngrx effects stuff in your angular component. Use effects to handle side effects (calls to the backend/fetching data from local storage etc...) and make your component to watch for a piece of your state via a selector. Let's summarize like this -
Your component [or your guard or resolver] will just dispatch an action to the store.
If you set up a reducer for that action then your reducer will be called first otherwise it will go to step 3
In your effect, you are watching for the dispatched action. Your effect will make the first call and then from the response of the first call, it will make the second call and then it will update the state in your store [or piece of the state] which is being watched by your component by dispatching the respective actions to the store.
This is a typical workflow [It may vary as per the need of the app but the basic idea remains the same]. So keeping the basic idea lets modify your code like this -
In your component
sliceOfState$: Observable<any>; //change the type of observabe as per your app
ngOnInit() {
this.store.dispatch(new actions.GetAllItems())
//this observable will be used to render your data on UI
//you can use various rxjs operators to transform your data before shoing to UI
this.sliceOfState$ = this.store.select(//your selector which gives you sliceOfState);
}
Now In your effect -
#Effect()
this.details$ = this.actions$.pipe(
ofType(actions.types.GetAllItems),
switchMap(() => {
//here you call API which makes the call to backend which return allItems
return this.yourServiceWhichGetAllItems.getAllItems();
}),
switchMap(allItems => {
//now for each item you need to get its detail
//so forkJoin all the observables which calls the backedn for each item
const obs$ = allItems.map(item => this.yourServiceWhichGetDetails.getItemDetail(item));
return forkJoin(obs$);
})
map(allItemsWithDetails => {
//here you should call your action which will update the state in your store
return new actions.SetAllItemsDetails(allItemsWithDetails);
})
);
I have provided pseudo code which will give you an idea of how to achieve what you want to do. For more info, you can visit the official site of ngrx - https://ngrx.io/guide/effects

vuex- dynamic modules don't have unique data

I have an async api call where I get an array of objects and then I map that to dynamically registered modules in my store. Something like this:
dispatch
// before this dispatch some api call happens and inside the promise
// iterate over the array of data and dispatch this action
dispatch(`list/${doctor.id}/availabilities/load`, doctor.availabilities);
The list/${doctor.id} is the dynamic module
action in availabilities module
load({ commit }, availabilities) {
const payload = {
id: availabilities.id,
firstAvailable: availabilities.firstAvailable,
timeslots: [],
};
// then a bunch of code that maps the availabilities to a specific format changing the value of payload.timeslots
commit('SET_AVAILABILITIES', payload)
}
mutation
[types.SET_TIMESLOTS](state, payload) {
console.log(payload);
state.firstAvailable = payload.firstAvailable;
state.id = payload.id;
state.timeslots = payload.timeslots;
}
When I check my logs for the console.log above each doctor has different arrays of time slots Exactly the data I want. However, in the vue developer tools and what is being rendered is just the last doctor's timeslots for all of the doctors. All of my business logic is happening in the load action and the payload in the mutation is the correct data post business logic. Anyone have any ideas why I'm seeing the last doctor's availabilities for every doctor?
It looks like you are assigning the same array (timeslots) to all doctors.
When you add an element to the array for one doctor, you mutate the array that all doctors are sharing.
However with the little code you show, it's difficult to know where is the exact problem.

How much of this business logic belongs in Vuex?

I have a simple app which pulls products from an API and displays them on-page, like this:
I've added Vuex to the app so that the search results as well as the product search array doesn't disappear when the router moves the user to a specific product page.
The search itself consists of the following steps:
show loading spinner (update the store object)
dispatch an action to access the API
update the store object with products, spinner
decide if the product list is exhausted
hide loading spinner
You get the idea.
With all of the variables stored in Vuex, it stands to reason all of the business logic should belong there as well, but should it really?
I'm talking specifically about accessing store params such as productsExhausted (when there are no more products to display) or productPage (which increments every time the infinite scroller module is triggered) etc.
How much logic - and what kind - belongs in Vuex? How much does not?
I was under the impression that Vuex is used for storage only but since all of the data is located there, fetching it all back to the Vue app only to send it all back seems like an overly verbose way to address the problem.
Vuex allows you to share data !
For everything that concerns the state of the app its pretty straightforward.
All the data that can be used by multiple components should be added
to the store.
Now concerning the business logic, even though I find its not really clear in the official documentation, it should follow the same principle.
What I mean is that logic that can be used by multiple components should be stored in actions.
Moreover actions allows you to deal with async operations. Knowing this, your code that pulls the data should definitely be stored in vuex's actions.
What I think you should do is to put the request inside an action, then mutate the state of your variables and automatically your UI will reflect the changes.
Moreover, a good pattern to apply is to convert most of the logic to a state logic. For instance consider this demo of a jumping snowman. In here the click action results on updating a value from the store. Although the interesting part is that one component uses the watch functionnality to be notified when the store changes. This way we keep the logic inside the component but use the store as an event emitter.
var store = new Vuex.Store({
state: {
isJumping: 0
},
mutations: {
jump: function(state){
state.isJumping++;
}
}
})
Vue.component('snowman', {
template: '<div id="snowman" :class="color">⛄</div>',
computed: {
isJumping: function(){
return this.$store.state.isJumping;
}
},
watch: {
isJumping: function(){
var tl = new TimelineMax();
tl.set(this.$el,{'top':'100px'})
tl.to(this.$el, 0.2, {'top':'50px'});
tl.to(this.$el, 0.5, {'top':'100px', ease: Bounce.easeOut});
}
}
})

React and Redux architecture issues

Before reading:
This isnt a matter of non working code but a question on architecture. Also i am not currently using the ReactRedux library as im first trying to understand how the parts work on their own in this test app. Its as short as i could cut it but unfortunately still lengthy, please bear with me
Short Intro
I've got an array of Bottle models. Using pseudocode,a bottle is defined like so:
class Bottle{
//members
filledLiters
filledLitersCapacity
otherMember1
otherMember2
//functions
toPostableObject(){
//removes functions by running JSON.
var cloneObj = JSON.parse(JSON.stringify(this));
//removes all members we dont want to post
delete cloneObj["otherMember1"];
}
//other functions
}
I've also got a React component that displays all Bottle items.The component needs to store the previous state of all Bottle items as well ( its for animating, disregard this ).
Redux usage
There are complex operations i need to perform on some of the Bottle items using a helper class like so:
var updated_bottles = BottleHandler.performOperationsOnBottles(bottleIds)
mainStore.dispatch({type:"UPDATED_BOTTLES",updated_bottles:updated_bottles})
I dont want to update the store for every operation as i would like the store to be updated all together at the end in one go. Therefore my BottleReducer looks something like this :
var nextState = Object.assign({}, currentState);
nextState.bottles = action.updated_bottles
Where action.updated_bottles is the final state of bottles after having performed the operations.
The issue
Even though everything works, im suspicious that this is the "wrong mindset" for approaching my architecture. One of the reasons is that to avoid keeping the reference to the bottle objects and mutating the state as im performing the operations, i have to do this ugly thing:
var bottlesCloneArray = mainStore.getState().
bottleReducer.bottles.map(
a => {
var l = Object.assign({}, a);
Object.setPrototypeOf( l, Character.prototype );
return l
}
);
This is because i need a cloned array of objects that still retain their original functions ( meaning they're actual instance clones of the class )
If you can point out the flaw/flaws in my logic i'd be grateful.
P.S: The reason i need to keep "deep clones" of the class instances is so that i can keep the previous state of bottles in my React component for the reason of animating between the two states when an update in render happens.
When dealing with redux architecture it can be extremely useful to keep serialisation and immutability at the forefront of every decision, this can be difficult at first especially when you are very used to OOP
As the store's state is just a JS object it can be tempting to use it to keep track of JS instances of more complex model classes, but instead should be treated more like a DB, where you can serialise a representation of your model to and from it in an immutable manner.
Storing the data representations of your bottles in its most primitive form makes things like persistance to localStorage and rehydration of the store possible for more advanced applications that can then allow server side rendering and maybe offline use, but more importantly it makes it much more predictable and obvious what is happening and changing in your application.
Most redux apps i've seen (mine included) go down the functional route of doing away with model classes altogether and simply performing operations in the reducers directly upon the data - potentially using helpers along the way. A downside to this is that it makes for large complex reducers that lack some context.
However there is a middle ground that is perfectly reasonable if you prefer to have such helpers encapsulated into a Bottle class, but you need to think in terms of a case class, which can be created from and serialised back to the data form, and acts immutably if operated upon
Lets look at how this might work for your Bottle (typescript annotated to help show whats happening)
Bottle case class
interface IBottle {
name: string,
filledLitres: number
capacity: number
}
class Bottle implements IBottle {
// deserialisable
static fromJSON(json: IBottle): Bottle {
return new Bottle(json.name, json.filledLitres, json.capacity)
}
constructor(public readonly name: string,
public readonly filledLitres: number,
public readonly capacity: number) {}
// can still encapuslate computed properties so that is not needed to be done done manually in the views
get nameAndSize() {
return `${this.name}: ${this.capacity} Litres`
}
// note that operations are immutable, they return a new instance with the new state
fill(litres: number): Bottle {
return new Bottle(this.name, Math.min(this.filledLitres + litres, this.capacity), this.capacity)
}
drink(litres: number): Bottle {
return new Bottle(this.name, Math.max(this.filledLitres - litres, 0), this.capacity)
}
// serialisable
toJSON(): IBottle {
return {
name: this.name,
filledLitres: this.filledLitres,
capacity: this.capacity
}
}
// instances can be considered equal if properties are the same, as all are immutable
equals(bottle: Bottle): boolean {
return bottle.name === this.name &&
bottle.filledLitres === this.filledLitres &&
bottle.capacity === this.capacity
}
// cloning is easy as it is immutable
copy(): Bottle {
return new Bottle(this.name, this.filledLitres, this.capacity)
}
}
Store state
Notice it contains an array of the data representation rather than the class instance
interface IBottleStore {
bottles: Array<IBottle>
}
Bottles selector
Here we use a selector to extract data from the store and perform transformation into class instances that you can pass to your React component as a prop.
If using a lib like reselect this result will be memoized, so your instance references will remain the same until their underlying data in the store has changed.
This is important for optimising React using PureComponent, which only compares props by reference.
const bottlesSelector = (state: IBottleStore): Array<Bottle> => state.bottles.map(v => Bottle.fromJSON(v))
Bottles reducer
In your reducers you can use the Bottle class as a helper to perform operations, rather than doing everything right here in the reducer directly on the data itself
interface IDrinkAction {
type: 'drink'
name: string
litres: number
}
const bottlesReducer = (state: Array<IBottle>, action: IDrinkAction): Array<IBottle> => {
switch(action.type) {
case 'drink':
// immutably create an array of class instances from current state
return state.map(v => Bottle.fromJSON(v))
// find the correct bottle and drink from it (drink returns a new instance of Bottle so is immutable)
.map((b: Bottle): Bottle => b.name === action.name ? b.drink(action.litres) : b)
// serialise back to date form to put back in the store
.map((b: Bottle): IBottle => b.toJSON())
default:
return state
}
}
While this drink/fill example is fairly simplistic, and could be just as easily done in as many lines directly on the data in the reducer, it illustrate's that using case class's to represent the data in more real world terms can still be done, and can make it easier to understand and keep code more organised than having a giant reducer and manually computing properties in views, and as a bonus the Bottle class is also easily testable.
By acting immutably throughout, if designed correctly your React class's previous state will continue to hold a reference to your previous bottles (in their own previous state), so there is no need to somehow track that yourself for doing animations etc
If Bottle class is a react component (or inside a react component) I think you could play with componentWillUpdate(nextProps, nextState) so you can check the previous state (do not unmount your component of course).
https://reactjs.org/docs/react-component.html#componentwillupdate
Deep cloning your class doesn't seem a good idea to me.
Edit:
"I've also got a React component that displays all Bottle items."
That's where you should keep and look for your previous state. Keep all your bottle in a bottles store. And get it in your components when you need to display bottles.
Inside componentWillUpdate you can check you this.state (which is your state just before being updated, ie your previous state) and nextState passed as a parameter which is the current state
Edit2:
why would you keep an complete class in your state ? Just keep data in state. I mean just keep an object that will be updated by your reducer. If you need to have some utils functions (parser...) do not keep them in your state, treat your data in reducers before updating your state or keep your utils/parser functions in some utils file
Also your state should stay immutable. So it means you reducer should return a copy of the updated state anyway.
I've got an array of Bottle models.
I think It makes more sense to have a model of BottleCollection.
Or maybe you have one Bottle model and multiple usages of it?
class Bottle{
//members
filledLiters
filledLitersCapacity
otherMember1
otherMember2
//functions
toPostableObject(){}
}
Hm, it looks like your model represents multiple things:
a cache of persistent data (retrieved via AJAX?)
data object (dumb fields)
a temporary state for user input (data to be POSTed?)
I wouldn't call it a model. It's 3 things: API wrapper/cache, data and pending changes.
I would call it REST API wrapper, data object and application state.
There are complex operations i need to perform on some of the Bottle items using a helper class like so:
var updated_bottles =
BottleHandler.performOperationsOnBottles(bottleIds)
It looks to be the domain logic. I wouldn't place the core logic of the application under the name "helper class". I would call it "the model" or "business rules".
mainStore.dispatch({type:"UPDATED_BOTTLES", updated_bottles:updated_bottles})
That looks to be a change in application state. But I don't see the reason for it. I.e. who requested this change and why?
I dont want to update the store for every operation as i would like the store to be updated all together at the end in one go.
That's a good reasoning.
So you'll have a single action type:
mainStore.dispatch({type:"UPDATED_DATA", { updated_bottles })
However, in this case you might need to clean up old state like this:
mainStore.dispatch({type:"UPDATED_DATA", { updated_bottles: null })
The reason i need to keep "deep clones" of the class instances is so that i can keep the previous state of bottles
I think the reason is that you keep REST API cache and pending changes in a single object. If you keep cache and pending changes in separate objects you don't need clones.
Another thing to note is that your state should be a plain JavaScript object, not an instance of a class. There's no reason to keep references to functions (instance methods) in a state if you know which type of data your state contains. You can just use temporary class instances:
const newBottlesState = new BottleCollection(state.bottlesCache, state.bottlesUserChanges).performOperationsOnBottles()

How to update Baobab leaf data before React component render?

ReactJS, Baobab, Material-UI app displays some items, identified by their numeric id. To display those, title and image url's are retrieved from a remote service via ajax. Tree branch stores that data:
data: {
12345: {title:'ABC', image:'https://...'}, // id is 12345
12346: {...
}
Upon item component creation and first rendering, its extended data may, or may not be already available in the tree. If its not, ajax call is enqueued to receive that data. It might happen that multiple items are created with the same item id.
To avoid extra requests for the same id, I want to put a dummy info {title:'loading', image:'spinner.gif'} into the tree upon the first request to that id's info. Thus this data will be used for the very first render(). Successive components would get that dummy info, and will not initiate any extra requests.
Question: how, and where can I place the code to test if the tree has no info yet and place the dummy there to indicate its "penging" state and enqueue the request?
Tried so far:
component's constructor – props are not set there yet;
componentWillMount() – the first render started with the old state of the tree, despite the tree.commit() after setting the dummy value;
in the branch function that dynamically creates components cursor pointing to its data. Got warning:
setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
This can be solved one level up – once the list of ids is available. But it feels right that a component should be able to handle its data within itself.
Please advice a correct way to immediately update Baobab tree data before the first render of a React Component, from within that Component?
In my case (i am use same stack) wrap branch work fine.
import BaobabPropTypes from 'baobab-react/prop-types';
class Actions {
/**
* #param {Baobab} tree
*/
static prefetchTree = (tree) => {
tree.select(somePath).set(defaultValue);
tree.commit();
};
}
class Page extends React.Component {
static contextTypes = {
tree: BaobabPropTypes.baobab
};
componentWillMount() {
Actions.prefetchTree(this.context.tree);
}
render() {
return <Branch {...this.props}/>;
}
}
Baobab has a get event, use it to detect requests that return values that are not fetched yet:
tree.on('get', function(e) {
if (e.data.data === undefined) {
const path = e.data.path; // requested cursor path like ['data',12345]
const id = path[1];
FETCH_DATA(id)
.then( data => tree.set(path , data) );
tree.set(path, PLACEHOLDER_DATA);
}

Categories