I am struggling to find a good way to organise my state in Redux with React. It is a simple Blog with posts. I am getting an array from the api that is already ordered from oldest to newest. Since the app is a bit more complicated, I'd like to store the state in an object with the uuid as keys, so I can access it easily.
{
uuid_post1: { ...post1 },
uuid_post2: { ...post2 },
...
}
With the format above it is easy for me to sync the state between API and React without refetching data all the time. But I do need to display the output in an ordered form from newest to oldest.
Is there an easy way to solve this and keep the date info in the object? Is there another good way to organise state for this use case?
Thanks.
In your reducer you'll want to index the posts by ID and also save the sorted IDs. This will let you look up the posts efficiently, and also maintain a list of them in the order that you received them (oldest to newest). You can get them in the reverse order using a selector.
switch (action.type) {
case 'POSTS_RECEIVED':
return {
...state,
orderedPostIDs: posts.map(p => p.id),
postsById: posts.reduce((acc, post) => {
acc[post.id] = post;
}, {});
}
}
With this orderedPostIDs is an array of Post IDs and postsById is an object where the keys are Post IDs and the values are the posts.
function getPostByID(state, postId) {
return state.posts.postsById[id];
}
// Should use reselect here because it's returning a new array with every call
// oldest to newest - post are received from API in this order
function getPostsSortedByDateAscending(state) {
return state.posts.orderedPostIDs.map(id => getPostByID(state, id));
}
// Should use reselect here because it's returning a new array with every call
// newest to oldest
function getPostsSortedByDateDescending(state) {
// copy to new array, because Array.reverse mutates the value
return [].concat(getPostsSortedByDateAscending(state)).reverse();
}
Another approach which makes your state simpler is to only store postsById, as Max commented below. To get the sorted posts, you'll use Object.values() and then sort them as needed.
// oldest to newest - need to use reselect here
function getPostsSortedByDateAscending(state) {
return _.sortBy(Object.values(state.posts.postsById), p => p.date)
}
// oldest to newest - need to use reselect here
function getPostsSortedByDateDescending(state) {
// copy to new array, because Array.reverse mutates the value
return [].concat(getPostsSortedByDateAscending(state)).reverse();
}
Related
I'm bumbling my way through adding a back-end to my site and have decided to get acquainted with graphQL. I may be structuring things totally the wrong way, however from following some tutorials I have a React front-end (hosted on Vercel), so I have created an api folder in my app to make use of Vercel's serverless functions. I'm using Apollo server and I decided to go with Fauna as my database.
I've successfully been able to return an entire collection via my API. Now I wish to be able to return the collection sorted by my id field.
To do this I created an index which looks like this:
{
name: "sort_by_id",
unique: false,
serialized: true,
source: "my_first_collection",
values: [
{
field: ["data", "id"]
},
{
field: ["ref"]
}
]
}
I then was able to call this via my api and get back and array, which simply contained the ID + ref, rather than the associated documents. I also could only console log it, I assume because the resolver was expecting to be passed an array of objects with the same fields as my typedefs. I understand I need to use the ref in order to look up the documents, and here is where I'm stuck. An index record looks as follows:
[1, Ref(Collection("my_first_collection"), "352434683448919125")]
In my resolvers.js script, I am attempting to receive the documents of my sorted index list. I've tried this:
async users() {
const response = await client.query(
q.Map(
q.Paginate(
q.Match(
q.Index('sort_by_id')
)
),
q.Lambda((ref) => q.Get(ref))
)
)
const res = response.data.map(item => item.data);
return [... res]
}
I'm unsure if the problem is with how I've structured my index, or if it is with my code, I'd appreciate any advice.
It looks like you also asked this question on the Fauna discourse forums and got an answer there: https://forums.fauna.com/t/unable-to-return-a-list-of-documents-via-an-index/3511/2
Your index returns a tuple (just an array in Javascript) of the data.id field and the ref. You confirmed that with your example result
[
/* data.id */ 1,
/* ref */ Ref(Collection("my_first_collection"), "352434683448919125")
]
When you map over those results, you need to Get the Ref. Your query uses q.Lambda((ref) => q.Get(ref)) which passes the whole tuple to Get
Instead, use:
q.Lambda(["id", "ref"], q.Get(q.Var("ref")))
// or with JS arrow function
q.Lambda((id, ref) => q.Get(ref))
or this will work, too
q.Lambda("index_entry", q.Get(q.Select(1, q.Var("index_entry"))))
// or with JS arrow function
q.Lambda((index_entry) => q.Get(q.Select(1, index_entry)))
The point is, only pass the Ref to the Get function.
EventEmitter in service
toshoppinglist = new EventEmitter<Ingredients[]>()
Emitting method
toshoppinglist() {
this.slservice.toshoppinglist.emit(this.item.ingredients);
}
ingredients : Ingredient []
Subscribing to emit and pushing emitted values
this.slservice.toshoppinglist.subscribe(
(ingredients: Ingredients[]) => {
for (let item of ingredients) {
this.ingredients.push(item);
}
}
)
Now, when pushing new values into the array,it's getting duplicated.It's work fine for first pushing,but getting duplicated after that.
Ok, first in my opinion you use wrongly EventEmitter. Eventemitters used only inside DUMB components to raise an event to Smart component. And not inside services. Second, yes it will be duplicated. Imagine you have button which will raise this eventemitter and we emit the same ingredient every time. Inside the subscribe you didnt check that new ingredient are different. And because you use list, it doesnt care if you have duplicates. So a solution is to add to the subscribe a check that it will push only - new- ingredients.
You need to add a check in your subscription function.
currentIngredients: Ingredients[];
this.slservice.toshoppinglist.subscribe(
(ingredients: Ingredients[]) => {
> //here you need the if condition comparing it to a current
> ingredients array to see if it exists or not
for(let item of ingredients) {
if(this.currentIngredients.includes(item) {
// in this case you should increment the count of ingredient
currentIngredients[item].count++;
}
else {
// this should only add non-existing items ingredients list
this.currentIngredients.push(item)
}
}
}
I have an objectdailyData: {}, and when I'm making ajax request, this object contains some data like this:
So, how to put uniqe id (for example using library shortid) to every object in dailyData.daily ?
Just in case, my reducer's code:
case GET_DAILY_DATA: {
return {
...state,
dailyData: action.data,
}
}
You will have to iterate through the whole array and add the unique id to each of the objects in daily.
So, before saving the data to your state you can do something like this:
dailyData.daily.map(data => {...data, uniqueId: id }))
Don't use the index as unique id, just in case you have to append more data to this in the future, it won't remain unique anymore.
You can use the index of each object, I don't know the exact pourpose of doing that but if you going to iterate the array you have access to the index, like that:
this.props.daily.map((item, index) => }
//use the index like an unique ID
})
You need to be careful of how to use it. This way works pretty well in some cases and don't in others.
I'm having a hard time figuring out how to update a list of items (in the cache). When a new item is created with react-apollo.
The <CreateItemButton /> component is (in my case) not nested within <ListItems /> component. From what I can figure out, I need to update the cache via the update function in my createItemButton <Mutation /> component.
The problem is when I try to store.readQuery({query: GET_LIST_ITEMS, variables: ???}) to get the current list of items (to append my recently created item), this can have variables/filters (for pagination, sorting etc.).
How do I know what variable to pass to the store.readQuery function, is there a sort of "last used variables" around this, or do you have any other suggestion on how to solve this issue?
This is a known Apollo issue and the team is working on it. The current options you have can be found in this medium post.
It is also an issue with deleting/updating an item when you have it in a list with filters or pagination.
Here is a reference of an opened issue on Github about it
This seems to work, but a bit hacky to access the cache.watches Set? :S
The trick is to access the cache's variables for a given query, save it and perform a refetch with the given query variables. In my case after creation of an item:
export const getGraphqlCacheVariables = (queryName, cache) => {
if (cache.watches) {
const watch = [...cache.watches].find(w => !!w.query[queryName]);
if (watch) {
return watch.variables;
}
}
};
The mutation update function:
let variables;
const update = (cache, { data }) => {
if (data && data.createTask && data.createTask.task) {
variables = getGraphqlCacheVariables('GetTasks', cache);
}
}
And the refetchQueries function:
const refetchQueries = () => {
if (variables && Object.keys(variables).length > 0) {
return [{ query: GET_TASKS, variables }];
}
return [];
}
Bot the update and refetchQueries functions should have access to the variables variable
The benefit compared to the solution in the Medium article, is that you're not deleting the cached data for a given query (GET_TASKS) with different variables.
Apollo now provides a new directive that allows ignoring of some variables when querying the cache. Check it here. I've used it to ignore my changing pagination params.
I have a list of objects that are selectable, eg.
let Tasks = [Task A, Task B, Task C, Task D]. I'm using FlatList to render the tasks. Upon clicking a task, user is taken to an "Edit Task" for the selected task. I can use redux to update selected element to the array index or alternatively I can store the selected task key.
case SELECT_TASK:
return {
...state,
selected_habit: action.array_index
};
case SELECT_TASK:
return {
...state,
selected_habit: action.key
};
On the task specific page, the user can edit the task. Then in redux, I could do either of the following:
case EDIT_TASK: {
let tasks = { ...state.tasks};
tasks[state.selected_task] = edited_task; //not showing edit code
return {
...state
tasks: tasks
}
}
//don't use array index, instead use task key
case EDIT_TASK:
return {
...state
tasks: state.tasks.map(task => {
if (task.key === action.key) {
//update task code
return task;
} else return task;
})
}
Within each task, I may have an array of subtasks requiring a flatlist or similar. Accessing these nested arrays using Redux seems messy. It would be seem preferable to hold an object and access specific keys. However, FlatList requires an array.
1) Should I consider storing tasks in redux as an object, and building an array from the object before rendering so I can use FlatList? This would allow be to more easily update specific attributes of tasks (without storing selected states or mapping for each update)?
2) If the current pattern, is there significant performance upside to storing array element instead of object key?