Updating Setting Multiple Ramda lens once - javascript

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.

Related

Redux Toolkit caseReducers skip prepare callback in reducer function

I have a reducer for adding comment as follow
addComment: {
reducer: (state,action)=>{
const { newId, comment } = action.payload
console.log(newId)
// functions that create an new comment entry with the newID and update state
},
prepare: (input) =>{
return {
payload: {
...input,
newId: nanoid(),
}
}
}
If I call dispatch within my app as follow, the console.log shows the id generated by nanoid(). So far so good.
dispatch(addComment({comment: comment}))
However if I call the reducer within another reducer using the caseReducers, console.log returns undefined
commentSlice.caseReducers.addComment(state,{
payload: {
comment : comment
}
})
How do I get the prepare callback to run when using caseReducers.
Based on the docs it looks like if you are to invoke using the caseReducer way, you need to include the "type":
const slice = createSlice({
name: 'test',
initialState: 0,
reducers: {
increment: (state, action: PayloadAction<number>) => state + action.payload,
},
})
// now available:
slice.actions.increment(2)
// also available:
slice.caseReducers.increment(0, { type: 'increment', payload: 5 })

How to mutate complex object with nested array with SWR?

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.

Unwanted state changes in a class component with React

Long story short, I have a class component that constructs a poll. Before sending the data to the server I need to transform it a little so it fits the API request. I created a transformData method on my class component that transforms the data derived from the state. As a side effect it sets the data in separate this.state.data property so I can attach it with the API request. The problem is that the method mutates the other properties of the state.
transformData = () => {
const { title, sections } = this.state
const transformedSections = sections.map(section => {
delete section.isOpen
const transformedQuestions = section.questions.map(question => {
question.label = question.question
question.type = toUpper(question.type)
delete question.question
return question
})
section.questions = {
create: transformedQuestions,
}
return section
})
this.setState({
data: {
title,
sections: { create: transformedSections },
},
})
}
So I get this:
state: {
data: {...} //our transformed data
sections: {...} //transformed as well!!
}
instead of getting this:
state: {
data: {...} //our transformed data
sections: {...} //same before calling the method
I re-wrote the method with different approach — basically replaced all Array.map with Array.forEach and it worked as expected.
transformData = () => {
const { title, sections } = this.state
const transformedSections = []
sections.forEach(section => {
const transformedQuestions = []
section.questions.forEach(question => {
transformedQuestions.push({
label: question.question,
type: toUpper(question.type),
max: question.max,
min: question.min,
instruction: question.instruction,
isRequired: question.isRequired,
placeholder: question.placeholder,
})
})
transformedSections.push({
title: section.title,
questions: { create: transformedQuestions },
})
})
this.setState({
data: {
title,
sections: { create: transformedSections },
},
})
Can anyone explain what's going on here? How can I accidentally mutate a state property without explicitly calling this.setState on the aforementioned property? The thing is that the originally written method mutates the state even if I return the data object without calling this.setState whatsoever. Like so:
//This still mutates the state
return {
data: {
title,
sections: { create: transformedSections },
}
}
//without this!
//this.setState({
// data: {
// title,
// sections: { create: transformedSections },
// },
// })
Thanks!
javascript behave like this way,
its called variable referencing.
it works like pointer variable in C.
if your console those variable such as console.log(var1 == var2) it will show true cuz both references from same memory location
if you want to prevent mutate original variable then you have to create another brand new variable to mutate
like this way :
const { title, sections } = this.state
// create new variable following old one (spreading es6 way)
const tempSections = [...sections]
...
also
sections.forEach(section => {
const transformedQuestions = []
const tempQuestions = [...section.questions]
tempQuestions.forEach(question => {
...
always have to create a brand new variable of object/array/... to prevent auto mutation
for further info here
Issue here is of Shallow Copying :
console.log("---- before map -----" , this.state);
const { title, sections } = this.state
// sections is another object, and via map you are mutating inner objects
// beacuse of the shallow Copying
const transformedSections = sections.map(section => {
// any change on section object will direct mutate state
delete section.isOpen //<--- Here you are mutating state
return section
})
// state is muate already
console.log("---- After map -----" , this.state);
You can run the below code snippet and check both console.log, and check for "isOpen": true
Hope this will clear all your doubts :
const { useState , useEffect } = React;
class App extends React.Component {
state = {
title : "questions" ,
sections : [{
isOpen : true ,
questions : ["que1" , "que2" , "que3"]
}]
}
transfromData = () => {
console.log("---- before map -----" , this.state);
const { title, sections } = this.state
// sections is another object, and via map you are mutating inner objects
// beacuse of the shallow Copying
const transformedSections = sections.map(section => {
// any change on section object will direct mutate state
delete section.isOpen //<--- Here you are mutating state
return section
})
console.log("---- After map -----" , this.state);
}
render() {
return (
<div>
<button onClick={this.transfromData}>transfromData</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('react-root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>
You should never update the state without using the setState method. It is asyncronous, and if you don't set it properly you never know what might happen - and that's what you're seeing in the first part of your answer. See the docs
By doing
section.questions = {
create: transformedQuestions,
}
you are improperly altering the state, so you'll see this.state.sections transformed as well, because each element inside this.state.sections has now an attribute questions that contains create with the value transformedQuestions

Redux state updates two seperate objects

I have influencer data object. This object is beeing pulled from database with action FETCH_INFLUENCER and put inside two different objects: influencer and formInfluencer in redux store. And then I have action SET_INFLUENCER that is supposed to create new instance of the state and update influencer object in redux. For some reason though it updates both influencer and formInfluencer. I really struggle with finding answer here since I think I did everything to prevent pointing of two different variables to the same object and still it happens.
reducer:
case 'FETCH_INFLUENCER_FULFILLED':
return { ...state, fetching: false, fetched: true, influencer: action.payload.data, formInfluencer: Object.assign([], action.payload.data) }
case 'SET_INFLUENCER':
return { ...state, influencer: action.payload }
actions:
export function fetchInfluencer(id) {
return {
type: "FETCH_INFLUENCER",
payload: axios.get('/api/influencer/' + id, {headers: {Authorization: 'Bearer ' + localStorage.getItem('token')}})
}
}
export function setInfluencer(influencer) {
return {
type: "SET_INFLUENCER",
payload: influencer
}
}
dispatch:
handleUserChange(e) {
let influencer = [...this.props.influencer]
influencer[0].user[e.target.name] = e.target.value;
this.props.dispatch(setInfluencer(influencer))
}
mapping state to props:
const mapStateToProps = (state) => {
return {
influencer: state.influencers.influencer,
formInfluencer: state.influencers.formInfluencer
}
}
export default connect(mapStateToProps)(InfluencerDetails)
If You have any idea why this could be happening I would be happy to hear the answer.
You shouldn't mutate state (if you don't mutate it, then it is no problem that you have multiple variables pointing to the same object).
Instead of:
handleUserChange(e) {
let influencer = [...this.props.influencer]
influencer[0].user[e.target.name] = e.target.value;
this.props.dispatch(setInfluencer(influencer))
}
You should do a bit more work:
handleUserChange(e) {
const newUser = {
...this.props.influencer[0].user,
[e.target.name]: e.target.value
};
const newInfluencer = {
...this.props.influencer[0],
user: newUser
};
const newInfluencers = [...this.props.influencer];
newInfluencers[0] = newInfluencer;
this.props.dispatch(setInfluencer(newInfluencers));
}

How to modify object property in redux?

Trying to create a really simple redux todo, almost there but got stuck on one thing.
export const completeTodo = (todo) => ({
type: 'COMPLETE_TODO',
data: {
name: todo,
complete: !todo.complete
}
})
however, struggling to get the reducer working as I can't work out how to determine the exact object im working on
reducer:
case 'COMPLETE_TODO': {
const chore = { ...state.chores, complete: action.data.complete}
return { ...state.chores, chore };
}
and initialState is:
const initialState = {
chores: [{name: 'cleaning', complete: false}]
}
obviously when i click my button is should be wired up so it can change the complete boolean to the opposite but only for that one todo
Since you have an array you need to replace it with a new one
case 'COMPLETE_TODO': {
return {
...state,
chores: state.chores.map(chore =>
chore.name === action.data.name
? {...chore, complete: true /* or !chore.complete if you want toggle like behaviour*/}
: chore)
};
}
and in your action creator
export const completeTodo = (todo) => ({
type: 'COMPLETE_TODO',
data: {
name: todo.name // assumning names are unique
}
})

Categories