Appsync and Apollo Client, how to handle caching of lists with dataIdFromObject? - apollo-client

There are currently cache items that are accessed fine that are simple objects but struggling with what should happen when the object passed in to dataIdFromObject is an object with a key of items which is an array of objects. For example:
const dataIdFromObject = (obj) => {
switch(obj.__typename) {
case 'Post':
return `${obj.__typename}.${obj.categoryId}.${obj.postId}
case 'PostConnection':
return obj.items ? obj.items[0].categoryId : defaultDataIdFromObject(obj)
default:
return defaultDataIdFromObject(obj)
}
}
obj.items is an array of Post objects.
The problem is that data seems to be cached from other categories from time to time using the first item in the array of the nested item. Is there any issue with doing it this way?
Could there ever be a potential race condition if a request is not cancelled that could cause data from another category to be store under the wrong cache key as when looking in localStorage you would be able to see posts under other lets say category a under category b.
PostConnection.a {
items:
cat a item
cat b item
cat b item
}

Related

Check if session variable is defined

I need to count the number of views. I am using a session variable to avoid duplicate count. I need to check if session view_count isset and then it is set to false and increment the view count
$currentPost = Post::where('slug',$slug)->first();
$comments = \App\Comment::where('post_id',$currentPost->id)->get();
if(\Session::get('view_count')) {
\Session::put('view_count', false);
$currentPost->view_count = $currentPost->view_count + 1;
$currentPost->save();
}
Can't you just do
if (Session::has('your_key'))
{
//your code here
}
Update answer
Determining If An Item Exists In The Session
To determine if an item is present in the session, you may use the has method. The has method returns true if the item is present and is not null:
if ($request->session()->has('users')) {
//
}
To determine if an item is present in the session, even if its value is null, you may use the exists method. The exists method returns true if the item is present:
if ($request->session()->exists('users')) {
//
}
I am assuming you want to check whether a visitor has viewed a certain blog post, in which case, I'd probably do something like this.
$currentPost = Post::where('slug', $slug)->first();
// You should also probably set up your relationship with Comments
$comments = \App\Comment::where('post_id', $currentPost->id)->get();
if(! in_array($currentPost->id, session()->get('posts_viewed', []))) {
session()->push('posts_viewed', $currentPost->id);
// Your increment could also be simplified as follows
$currentPost->increment('view_count');
}
In your particular case, you will only be able to track whether the user has viewed one particular blog post. However, if you use an array and keep pushing viewed blog posts into it, you would be able to track views across many blog posts.

Apollo client: Making optimistic updates while creation is still in progress

I want to be able to do updates on an object while it is still being created.
For example: Say I have a to-do list where I can add items with names. I also want to be able to edit names of items.
Now say a user with a slow connection creates an item. In that case I fire off a create item mutation and optimistically update my UI. That works great. So far no problem
Now let's say the create item mutation is taking a bit of time due to a slow network. In that time, the user decides to edit the name of the item they just created. For an ideal experience:
The UI should immediately update with the new name
The new name should eventually be persisted in the server
I can achieve #2 by waiting for the create mutation to finish (so that I can get the item ID), then making an update name mutation. But that means parts of my UI will remain unchanged until the create item mutation returns and the optimistic response of the update name mutation kicks in. This means #1 won't be achieved.
So I'm wondering how can I achieve both #1 and #2 using Apollo client.
Note: I don't want to add spinners or disable editing. I want the app to feel responsive even with a slow connection.
If you have access to the server you can implement upsert operations, and you can reduce all queries to the such one:
mutation {
upsertTodoItem(
where: {
key: $itemKey # Some unique key generated on client
}
update: {
listId: $listId
text: $itemText
}
create: {
key: $itemKey
listId: $listId
text: $itemText
}
) {
id
key
}
}
So you will have a sequence of identical mutations differing only in variables. An optimistic response accordingly, can be configured to this one mutation. On the server you need to check if an item with such a key already exists and create or update an item respectively.
Additionally you might want to use apollo-link-debounce to reduce number of requests when user is typing.
I think the easiest way to achieve your desired effect is to actually drop optimistic updates in favor of managing the component state yourself. I don't have the bandwidth at the moment to write out a complete example, but your basic component structure would look like this:
<ApolloConsumer>
{(client) => (
<Mutation mutation={CREATE_MUTATION}>
{(create) => (
<Mutation mutation={EDIT_MUTATION}>
{(edit) => (
<Form />
)}
</Mutation>
)}
</Mutation>
)}
</ApolloConsumer>
Let's assume we're dealing with just a single field -- name. Your Form component would start out with an initial state of
{ name: '', created: null, updates: null }
Upon submitting, the Form would do something like:
onCreate () {
this.props.create({ variables: { name: this.state.name } })
.then(({ data, errors }) => {
// handle errors whichever way
this.setState({ created: data.created })
if (this.state.updates) {
const id = data.created.id
this.props.update({ variables: { ...this.state.updates, id } })
}
})
.catch(errorHandler)
}
Then the edit logic looks something like this:
onEdit () {
if (this.state.created) {
const id = this.state.created.id
this.props.update({ variables: { name: this.state.name, id } })
.then(({ data, errors }) => {
this.setState({ updates: null })
})
.catch(errorHandler)
} else {
this.setState({ updates: { name: this.state.name } })
}
}
In effect, your edit mutation is either triggered immediately when the user submits (since we got a response back from our create mutation already)... or the changes the user makes are persisted and then sent once the create mutation completes.
That's a very rough example, but should give you some idea on how to handle this sort of scenario. The biggest downside is that there's potential for your component state to get out of sync with the cache -- you'll need to ensure you handle errors properly to prevent that.
That also means if you want to use this form for just edits, you'll need to fetch the data out of the cache and then use that to populate your initial state (i.e. this.state.created in the example above). You can use the Query component for that, just make sure you don't render the actual Form component until you have the data prop provided by the Query component.

ReactiveUI - ReactiveCommand to get more data for objects in ReactiveList

Currently I am learning ReactiveUI and I am not sure how to approach this problem. What I want to achieve is once a reactive list has been loaded (in this case I am using a command to load it from a local store) I want to be able to trigger that each of the items in the reactive list then go off and fetch data from an api endpoint and update the view model.
What I currently have to load and create the view models using this logic:
LoadSavedLocations = ReactiveCommand.CreateAsyncTask(async o => {
var savedLocations = await _savedLocationService.GetUserSavedLocations();
return savedLocations;
});
LoadSavedLocations.Subscribe(savedLocations =>
{
foreach (var savedZone in savedLocations)
{
Zones.Add(new ZoneDetailViewModel() {
ZoneId = savedZone.ZoneId,
SavedName = savedZone.SavedName,
});
}
});
I want to then be able to have a command that I can kick off (one first load of the screen and then when the user prompts for an update - pull for reload).
There are two ways I think I can do this but struggling with the approach and the code to achieve this.
Option 1
A command which loops through the items in the ReactiveList fetches data from the Api and then updates that viewmodel something along the lines of
UpdateZones = ReactiveCommand.CreateAsyncTask(async o =>
{
foreach (var zone in Zones)
{
// Fetch
// Await
// Update view model
}
return null;
});
With this I am confused around what the return type of the command would be just a new object()? Is there a better way than just looping like this?
Option 2
On the view model ZoneDetailViewModel have a command called FetchExtraData which will then return the data from the API and I can subscribe to that command in the view model and populate the extra properties. With this approach how does the parent viewmodel trigger all the items in the ReactiveList to fire their commands.
For both approaches I don't know how to get each of the items in the ReactiveList to do logic which involves going to an Api and update.

Attaching new relation and returning the model

I have a User that has many Positions. I want to update the User (the model and the relation Position), and then return the updated result.
The input will be an array of the format
{
first_name,
last_name,
email,
... (other user information),
positions : [
{
id,
name,
descriton
},
...
]
}
My update function currently is
public function update($id)
{
// This is a validation that works fine
if ( ! User::isValid(Input::all())) return $this->withValidation(User::$errors);
$user = User::with('positions')->find($id);
$new_ids = array_pluck(Input::get('positions'), 'id');
$user->positions()->sync($new_ids);
$user->update(Input::all());
return $user;
}
My User and its permissions are updated, but I still get the old $user's relationship back (i.e. the basic information is updated, and the new positions are updated in the DB, but the returned result is the NEW basic information with the OLD positions).
Right now to fix this, I recall the User::with('positions')->find($id) at the end and return that. But why isn't my code above working?
Correct, sync doesn't update related collection on the parent model.
However you should use $user->load('positions') to reload the relation, it will call only 1 query, without fetching the user again.
Also, you can call load on the Collection:
$user->positions->load('anotherRelation');
because here positions is a Collection, which have load method to lazy load all the related models for each item in the collection.
This would not work, since positions() returns relation object, not collection:
$user->positions()->load('anotherRelation');

MVC 3 Details View

I am new to MVC frame work. And i am making one page where we can see details of department by clicking on details link button.
While User click link button it fetch the all the records of the particular department in List Collection and redirect to Details View.Data has been fetched in List but while going to Details view it Generates following error:
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[DocPageSys.Models.Interfaces.DepartmentInfo]', but this dictionary requires a model item of type 'DocPageSys.Models.Interfaces.DepartmentInfo`'.
I understood the error but confusion to solve it.And stuck with this problem...
Since your Details view is strongly typed to DepartmentInfo:
#model DocPageSys.Models.Interfaces.DepartmentInfo
you need to pass a single instance of it from the controller action instead of a list:
public ActionResult Details(int id)
{
DepartmentInfo depInfo = db.Departments.FirstOrDefault(x => x.Id == id);
return View(depInfo);
}
So make sure that when you are calling the return View() method from your controller action you are passing a single DepartmentInfo instance that you have fetched from your data store.
To make it run fine initially you could simply hardcode some value in it:
public ActionResult Details(int id)
{
var depInfo = new DepartmentInfo
{
Id = 1,
Name = "Sales",
Manager = "John Smith"
}
return View(depInfo);
}
Oh, and you will notice that I didn't use any ViewData/ViewBag. You don't need it. Due to their weakly typed nature it makes things look really ugly. I would recommend you to always use view models.
Passing a list instead of a single item
This error tells you, that you're passing a list to your view but should be passing a single entity object instance.
If you did fetch a single item but is in a list you can easily just do:
return View(result[0]);
or a more robust code:
if (result != null && result.Count == 1)
{
return View(result[0]);
}
return RedirectToAction("Error", "Home");
This error will typically occur when there is a mismatch between the data that the controller action passes to the view and the type of data the view is expecting.
In this instance it looks as if you're passing a list of DepartmentInfo items when your view is expecting a single item.

Resources