How to mutate complex object with nested array with SWR? - javascript

TL;DR:
How can I spread a new object inside a nested array e.g. data.posts.votes and also keep all the existing state from data
Full Info:
I have data I fetch that looks like that:
I want to have an upvote functionality that adds a new vote object into the votes array in the screenshot above.
​
I really have no plan right now how to do this in the mutate function from swr:
Right now I have it like this but that won't work:
mutate(
`/api/subreddit/findSubreddit?name=${fullSub.name}`,
(data) => {
console.log(data);
return {
...data,
posts: (data.posts[postid].votes = [
...data.posts[postid].votes,
{ voteType: "UPVOTE" },
]),
};
},
false
);
How can I optimistically update this?

I think your approach should be like this:
mutate(
`/api/subreddit/findSubreddit?name=${fullSub.name}`,
async (data) => {
console.log(data);
return {
...data,
posts: data.posts.map((post) => {
if (post.id === postid) {
return {
...post,
votes: [...post.votes, { voteType: "UPVOTE" }],
};
} else {
return post;
}
}),
};
},
false
);

Try using immer
produce(data, draft => {
const post = draft.posts.find( p => p.id === postId);
post.votes.push(newVote);
})
All immutable without the clunky spreads.

Related

How to solve "Expected to return a value in arrow function" error in eslint

I am using eslint and getting this error.
Expected to return a value in arrow function
The error is showing on the third line of the code.
useEffect(() => {
let initialPrices = {};
data.map(({ category, options }) => {
initialPrices = {
...initialPrices,
[category]: options[0].price,
};
});
setSelectedPrice(initialPrices);
}, []);
The map function must return a value. If you want to create a new object based on an array you should use the reduce function instead.
const reducer = (accumulator, { category, options }) => (
{...accumulator, [category]:options[0].price}
)
const modifiedData = data.reduce(reducer)
More information https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
The map function is intended to be used when you want to apply some function over every element of the calling array. I think here it's better to use a forEach:
useEffect(() => {
let initialPrices = {};
data.forEach(({ category, options }) => {
initialPrices = {
...initialPrices,
[category]: options[0].price,
};
});
setSelectedPrice(initialPrices);
}, []);
Your map function should return something. Here it's not the case so the error happens. Maybe a reduce function will be more appropriate than map?
From what I can see in your case, is that you want to populate initialPrices, and after that to pass it setSelectedPrice. The map method is not a solution, for you in this case, because this method returns an array.
A safe bet in your case would a for in loop, a forEach, or a reduce function.
const data = [
{
category: "ball",
options: [
{
price: "120.45"
}
]
},
{
category: "t-shirt",
options: [
{
price: "12.45"
}
]
}
];
The forEach example:
let initialPrices = {};
// category and options are destructured from the first parameter of the method
data.forEach(({ category, options}) => {
initialPrices[category] = options[0].price;
});
// in this process I'm using the Clojure concept to add dynamically the properties
setSelectedPrice(initialPrices);
The reduce example:
const initialPrices = Object.values(data).reduce((accumulatorObj, { category, options}) => {
accumulatorObj[category] = options[0].price
return accumulatorObj;
}, {});
setSelectedPrice(initialPrices);

React - Adding a property to an object inside of array in component's state

I am fetching an array of objects in the state, and I am trying to add a property to each of these objects.
The problem is I can see my property being added to each of the objects but when I mapping through all these objects and trying to console.log it I'm getting undefined
Adding a property
addFavourites() {
let data = this.state.data.map((e) => {
e.favourite = false;
return e;
});
return data;
}
state = {
data: []
}
addFavourites's called here:
getItem = () => {
this.service
.mergeData()
.then((body) => {
this.setState({
data: body
},
() => {
this.addFavourites();
},
() => {
this.dataToFilter();
});
});
}
componentDidMount(){
this.getItem();
}
In render function I see all my objects including favourite
console.log(data.map((e) => e));
But this gives an array of undefined
console.log(data.map((e) => e.favourite));
How can I solve this?
First, welcome! You should have probably made it more clear that this question is about React.
Now to the answer, every state modification would be made using setState, so your addFavourites function should be like this:
addFavourites() {
let data = this.state.data.map((e) => {
e.favourite = false;
return e;
});
this.setState({ data: data });
}

Vue, reassigning and reusing response values for different objects

I have a working vue function with an axios call but I need to define some of my response values differently and reuse them for an object without changing the overall structure. I currently have:
data () {
return {
dateEvents: [],
events: [
],
},
created() {
this.fetchItems();
},
methods: {
fetchItems() {
axios.get('/home/items')
.then(response => {
// handle success
console.log(response.data)
this.dateEvents = response.data
this.events = response.data
})
The problem is, I need to keep my current format for dateEvents but map these values to different names for the events object.
So right now my response.data is item_id, item_title, item_available which I still need for dateEvents. But for events I need to map it so that events.title = item_title, events.start = item_available
I tried this but it gives me undefined
axios.get('/home/items')
.then(response => {
// handle success
console.log(response.data)
this.dateEvents = response.data
this.events.title = response.data.item_title
this.events.start = response.data.item_available
})
How can I keep the general structure but only change the assignments for my events object?
You should map your response.data array to make new format array like this
this.events = response.data.map(({ item_title, item_available, ...item }) => ({
...item,
title: item_title,
start: item_available
}))

Updating Setting Multiple Ramda lens once

Noobie to Ramda. So, I was facing some deep state update issues. Somebody recommended Ramda. Now I need some help with it.
Here is my react state
steps: {
currentStep: 1,
step1: {
data: {},
apiData: null,
comments:[],
reviewStatus: '',
reviewFeedback: ''
},
step2: {
data: {},
apiData: null,
comments:[],
reviewStatus: '',
reviewFeedback: ''
}
}
I made lenses for each step data ,apiData,comments ,reviewStatus, reviewFeedback.
const step1ApiDataLens = lensPath(['steps', 'step1', 'apiData'])
const step1DataLens = lensPath(['steps', 'step1', 'data'])
const step1Status = lensPath(['steps','step1','reviewStatus'])
const step1Feedback = lensPath(['steps','step1','reviewFeedback'])
Sometimes I need to update the apiData alone sometimes together like reviewStatus,reviewFeedback.Currently I'am handling it through setState callback.It works but having 3 to 4 callbacks looks odd. Are there any other ways to set multiple lens at same time?.
this.setState((state) => {
return set(step1ApiDataLens, response.data, state)
}, () => {
if (push) {
this.setState({
currentStatus: view(step1Status, this.state),
currentFeedback: view(step1Feedback, this.state)
}, () => {
this.setState((state)=>{
return set(currentStepLens,currentStep,state)
},()=>{
this.setState({
stepReady: true
})
})
})
}
});
You should not use async-after-set-state-callback for a single update.
this.setState(state => {
const upd1 = set(step1ApiDataLens, response.data, state);
if (push) {
const upd2 = {
...upd1,
currentStatus: view(step1Status, upd1),
currentFeedback: view(step1Feedback, upd1),
stepReady: true,
};
return set(currentStepLens, currentStep, upd2);
}
return upd1;
});
You probably don't need lenses with ramda, if you don't use over on them, unless you want typechecking/selector-abstraction. Ramda.path and Ramda.assocPath can work well enough.

Updating Apollo store when mutating list filter

I have a graphql schema that looks like this :
type User {
entries(status: EntryStatus): [Entry]
}
type Mutation {
updateEntry(status: EntryStatus): Entry
}
I can filter the list of entries by status (which is an enum) and I can update the status of an entry. I'd like to update the store when the status is updated so that the entry appears in the right list (entries(status: FOO) to entries(status: BAR)).
I know I can update the store with the update method.
const withMutation = graphql(updateEntryMutation, {
props: ({ mutate }) => ({
updateEntry: updateEntryInput =>
mutate({
variables: { updateEntryInput },
update: (proxy, newData) => {
// update data here
// Which would involve removing the entry from its previous filtered "list", and adding it to the one with the new status
})
})
});
But how can I know from which list to remove the entry since I don't have access to the old data (previous entry.status) from update ?
(apart from enumerating all lists by status and removing the updated entry if I find it...)
You need to store.readQuery() first, then write the new data to the store.
Kinda like this:
const EntriesQuery = 'yourQueryHere';
const withMutation = graphql(updateEntryMutation, {
props: ({ mutate }) => ({
updateEntry: updateEntryInput =>
mutate({
variables: { updateEntryInput },
update: (store, { data: { updateEntry }}) => {
const data = store.readQuery({ query: EntriesQuery });
data.entries.push(updateEntry)
store.writeQuery({ query: EntriesQuery, data })
})
})
});

Categories