meaning of state in Delete items from state array - useReducer - react-redux

Hi all i am new to react hooks.Please explain what is the meaning of state.items in DELETE_ITEM case.Is this a single object if yes then how.
let initialState = {
items: [
{
name: 'A',
age: 23
},
{
name: 'B',
age: 20
},
{
name: 'C',
age: 29
}
]
}
const userReducer = (state = initialState, action) => {
switch(action.type){
case DELETE_ITEM:
return {
...state,
items: state.items.filter((item, index) => index !== action.payload)
}
}
}

state.items is the items object from your state. It starts off as described in initialState. That's the initial value. Then, in the reducer, subsequent actions such as DELETE_ITEM might alter that value.
The current state of that items value is what you have in state.items. Hence, the name. It's not a single object, it's the entire items array.

Related

Optimistic response not working when adding items to list

My data model is a list with items. Very simple:
{
_id: 1,
name: "List 1",
items: [
{ _id: 2, text: "Item text 1" },
{ _id: 3, text: "Item text 2" }
]
}
Adding a new list with optimistic response works perfectly:
const [addListMutation] = useAddListMutation({
update: (cache, { data }) => {
const cachedLists =
(cache.readQuery<GetAllListsQuery>({
query: GetAllListsDocument,
})?.lists as TList[]) ?? [];
if (data) {
cache.writeQuery({
query: GetAllListsDocument,
data: {
lists: [...cachedLists, data?.list as TList],
},
});
}
},
});
const addList = async (name: string) => {
const list = {
_id: ..new id here,
name,
items: [],
};
const variables: AddListMutationVariables = {
data: list,
};
await addListMutation({
variables,
optimisticResponse: {
list,
},
});
};
This gets reflected immediately in my component using const { loading, data } = useGetAllListsQuery();. data is updated twice; first with the optimistic response and then after the mutation is done. Just like expected.
Now I'm trying to add an item to the list this way:
const [updateListMutation] = useUpdateListMutation({
update: (cache, { data }) => {
const cachedLists =
(cache.readQuery<GetAllListsQuery>(
{
query: GetAllListsDocument,
},
)?.lists as TList[]) ?? [];
if (data?.list) {
// Find existing list to update
const updatedList = data?.list as TList;
const updatedListIndex = cachedLists.findIndex(
(list: TList) => list._id === updatedList._id,
);
// Create a copy of cached lists and replace entire list
// with new list from { data }.
const updatedLists = [...cachedLists];
updatedLists[updatedListIndex] = { ...updatedList };
cache.writeQuery({
query: GetAllListsDocument,
data: {
lists: updatedLists,
},
});
}
}
});
const updateList = async (updatedList: TList) => {
const variables: UpdateListMutationVariables = {
query: {
_id: updatedList._id,
},
set: updatedList,
};
await updateListMutation({
variables,
optimisticResponse: {
list: updatedList,
},
});
};
const addListItem = async (list: TList, text: string) => {
const updatedList = R.clone(list);
updatedList.items.push({
_id: ...new item id here,
text: 'My new list item',
});
await updateList(updatedList);
};
The problem is is in my component and the const { loading, data } = useGetAllListsQuery(); not returning what I expect. When data first changes with the optimistic response it contains an empty list item:
{
_id: 1,
name: "List 1",
items: [{}]
}
And only after the mutation response returns, it populates the items array with the item with text 'My new list item'. So my component first updates when the mutation is finished and not with the optimistic response because it can't figure out to update the array. Don't know why?
(and I have checked that the updatedLists array in writeQuery correctly contains the new item with text 'My new list item' so I'm trying to write the correct data).
Please let me know if you have any hints or solutions.
I've tried playing around with the cache (right now it's just initialized default like new InMemoryCache({})). I can see the cache is normalized with a bunch of List:1, List:2, ... and ListItem:3, ListItem:4, ...
Tried to disable normalization so I only have List:{id} entries. Didn't help. Also tried to add __typename: 'ListItem' to item added, but that only caused the { data } in the update: ... for the optimistic response to be undefined. I have used hours on this now. It should be a fairly simple and common use case what I'm trying to do :).
package.json
"#apollo/client": "^3.3.4",
"graphql": "^15.4.0",
"#graphql-codegen/typescript": "^1.19.0",

React reducer question on changing a returned payload

I am trying to either add a new field into a returned payload or add a new field copying the contents of another field in the returned payload object. Here is my reducer code...
[actionTypes.GET_PAYTYPE_CONTRIBUTORS]: (state, action) => {
return {...state, paytypeContributors: { ...action.payload }, loadingPaytypeContributors: false, }
},
For each entry in action.payload.Items, I need to either change the field name ID to Id or add Id to the payload Items array with the same contents as the ID field has.
Here is where I tried to do this...
[actionTypes.GET_PAYTYPE_CONTRIBUTORS]: (state, action) => ({...state, paytypeContributors: { ...action.payload, Id: action.payload.Items.ID }, loadingPaytypeContributors: false}),
The payload returns an object and then Items inside of the object is an array and ID is a field in the array. Any ideas on how to do this?
If I understood correctly, that would be my take on this:
const actionFunc = (state, action) => ({ ...state,
paytypeContributors: { ...action.payload,
Items: action.payload.Items.map(Item => {
const newItem = { ...Item,
Id: Item.ID
};
delete newItem['ID'];
return newItem;
})
},
loadingPaytypeContributors: false
});
const state = {};
const action = {
payload: {
Items: [{
ID: 1
},
{
ID: 2
},
{
ID: 3
},
{
ID: 4
}
]
}
}
console.log(actionFunc(state, action));

How can I get which object changed in a BehaviourSubject in RxJs?

I am listening to an observable an after the first emit with all the objects, I would to get only the object that changed. So if I have:
[{name: 'Mark'},{name: 'Joe'}]
and then a name change I only get the object that changed. So if the object becomes:
[{name: 'Jean Mark'},{name: 'Joe'}]
I only get
[{name: 'Jean Mark'}]
Your Observable emits arrays and you want to know the difference between the currently emitted array and the previous one. Tracking array state changes has more to do with how to compare arrays or objects than with Observables.
If you want to track changes within an Observable it really comes down to comparing a previous with a current value. The logic you want to use here is up to you. e.g. you have to think about how to distinguish between a 'modified' value and newly 'added' value in an array?
Check out these questions to get you started:
How to get the difference between two arrays in JavaScript?
Comparing Arrays of Objects in JavaScript
How to determine equality for two JavaScript objects?
You can compare the current value cv to the previous one pv in an Observable by using pairwise. Here is a how it could look like.
const source = of(
[{ name: "Mark", p: 2 }, { name: "Joe", p: 3 }],
[{ name: "Jean Mark", p: 2 }, { name: "Joe", p: 3 }],
[{ name: "Jean Mark", p: 1 }, { name: "Joe", p: 3 }, { name: 'Alice' }],
[{ name: "Jean Mark", p: 1 }, { name: "Joe", p: 3 }],
[{ name: "Jean Mark", p: 1 }, { name: "Joe", p: 4 }],
[{ name: "Jean Mark", p: 1 }, { name: "Joe", p: 4 }]
);
// compare two objects
const objectsEqual = (o1, o2) =>
typeof o1 === "object" && Object.keys(o1).length > 0
? Object.keys(o1).length === Object.keys(o2).length &&
Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
: o1 === o2;
// compare two arrays
// REPLACE this function with YOUR OWN LOGIC to get your desired output !!!
const difference = (prev, curr) => ({
added: curr.filter(o1 => !prev.some(o2 => objectsEqual(o1, o2))),
removed: prev.filter(o1 => !curr.some(o2 => objectsEqual(o1, o2)))
})
source.pipe(
startWith([]), // used so that pairwise emits the first value immediately
pairwise(), // emit previous and current value
map(([pv, cv]) => difference(pv, cv)) // map to difference between pv and cv
).subscribe(console.log);
https://stackblitz.com/edit/rxjs-m9ngjy?file=index.ts
You can watch an array (index value/add/remove) with javascript proxy, but that doesn't watch for object change in the array.
const handler = {
set: function(target, property, value, receiver){
console.log('setting ' + property + ' for ' + target + ' with value ' + value);
target[property] = value;
return true;
}
}
const arr=[{name: 'Mark'},{name: 'Joe'}];
const proxy = new Proxy(arr, handler);
// will log
proxy[0]="hello"
// won't log
proxy[0].name="ben"
if you also want to watch for object change then you need to either use proxy for every object added, or create your to be added object with Object.defineProperty()
and add your setter
There is also an existing library that watch for both object and array change, and it also use proxy
https://github.com/ElliotNB/observable-slim/

Combine the all custom array value into one string observable

I am trying to return single string once map the whole array object.
Here it's below code that combine my custom array input to string but it's emit each value alone instead map into single block of string(concatenation of string).
const exampleInfo: GithubInfo = {
name: "Hello",
login: "Hello1",
description: "TypeScript dev",
repos: [{ project: "ts", star: 5 }, { project: "js", star: 5 }]
};
const repos = from(gitHubInfo["repos"]);
const reposeDetial = repos.pipe(
map(val => `${val.project},${val.star}`))
.subscribe(val => {
console.log(val); // emit `ts,5` ,`js,5` instead in single block `ts,5,js,5`
});
If you want to concat your emitted data at the end of your operators chain, you can use reduce. It will accumulate all your stream into one single value:
const exampleInfo: GithubInfo = {
name: "Hello",
login: "Hello1",
description: "TypeScript dev",
repos: [{ project: "ts", star: 5 }, { project: "js", star: 5 }]
};
const repos = from(gitHubInfo["repos"]);
const reposeDetial = repos.pipe(
map(val => `${val.project},${val.star}`)),
reduce((acc, value) => acc.concat(value), '')
).subscribe(val => {
console.log(val); // emit `ts,5,js,5`
});

Reduce returns empty array, however scan does not

Code:
const Rx = require('rxjs')
const data = [
{ name: 'Zachary', age: 21 },
{ name: 'John', age: 20 },
{ name: 'Louise', age: 14 },
{ name: 'Borg', age: 15 }
]
const dataSubj$ = new Rx.Subject()
function getDataStream() {
return dataSubj$.asObservable().startWith(data);
}
getDataStream()
.mergeMap(Rx.Observable.from)
.scan((arr, person) => {
arr.push(person)
return arr
}, [])
.subscribe(val => console.log('val: ', val));
Using .reduce(...) instead of .scan(...) returns an empty array and nothing is printed. The observer of dataSub$ should receive an array.
Why does scan allow elements of data to pass through, but reduce does not?
Note: I am using mergeMap because I will filter the elements of the array before reducing them back into a single array.
scan emits the accumulated value on every source item.
reduce emits only the last accumulated value. It waits until the source Observable is completed and only then emits the accumulated value.
In your case the source Observable, which relies on a subject, never completes. Thus, the reduce would never emit any value.
You may want to apply the reduce on the inner Observable of the mergeMap. For each array, the inner Observable would complete when all the array items are emitted:
const data = [
{ name: 'Zachary', age: 21 },
{ name: 'John', age: 20 },
{ name: 'Louise', age: 14 },
{ name: 'Borg', age: 15 }
]
const dataSubj$ = new Rx.Subject()
function getDataStream() {
return dataSubj$.asObservable().startWith(data);
}
getDataStream()
.mergeMap(arr => Rx.Observable.from(arr)
.reduce((agg, person) => {
agg.push(person)
return agg
}, [])
)
.subscribe(val => console.log('val: ', val));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>

Resources