Shorten multiple lines of setState code in react - reactjs

This question is just for the purpose of improving my codes in React which currently looks very dirty and unreadable. I have 10-15 forms field which updated once I get values from APIs.
Here is the code from which I did this,
this.setState({smtpServer:res.data.data[0].smtpServer});
this.setState({senderName:res.data.data[0].senderName});
this.setState({senderEmail:res.data.data[0].senderEmail});
this.setState({smtpPorts:res.data.data[0].smtpPort});
this.setState({username:res.data.data[0].username});
this.setState({password:res.data.data[0].password});
this.setState({authType:res.data.data[0].authType});
There is also lots more of setState method which I didn't add. This makes my component code complex. Is there any method to shorten these codes.

You can destructure the data and use a single setState with shorthand object assignment (i.e. keys are named the same as the variables)
const {
smtpServer,
senderName,
senderEmail,
smtpPort,
username,
password,
authType
} = res.data.data[0];
this.setState({
smtpServer,
senderName,
senderEmail,
smtpPort,
username,
password,
authType
});
Or, if res.data.data[0] has all the properties you want to store in state, more succinctly
this.setState({...res.data.data[0]});

You can shorten them by fitting it all into a single object state using setState. First try to destructure your res.data.data[0] object in order to handle the individual attributes better:
const { smptServer,
senderName,
senderEmail,
smtpPort,
username,
password,
authType} = res.data.data[0];
Next you can set state in one GIANT OBJECT with each attributes. ES6 allows javascript to use key only instead of key-value pairs when the key and value have the same name.
Thus: const objName = {username:username, password:password} can also be typed as
const objName = {username,password}
By that logic you can just setState as:
this.setState({ smptServer,
senderName,
senderEmail,
smtpPort,
username,
password,
authType});
Hope this helps.

Assuming these calls are happening in sequence, it could be written as
const data = res.data.data[0];
this.setState({
smtpServer: data.smtpServer,
senderName: data.senderName,
senderEmail: data.senderEmail,
smtpPorts: data.smtpPort,
username: data.username,
password: data.password,
authType: data.authType
});

Or if there's nothing else in that object, you could just simply do
this.setState(res.data.data[0]);
or
this.setState({ data: res.data.data[0] });

It can be achieved in a very simple way as below:
You can take that data in one variable and then you can assign it in the state.
const data = res.data.data[0];
this.setState({...data});
You can assign it directly without the use of an extra variable.
this.setState({...res.data.data[0]});

Related

Cannot assign to read only property of Object in TypeScript

can anyone explain to me please why and how this might happen:
I have a typescript app with Zustand state management.
Somewhere during the app I am updating certain elements by extracting them from the state and cloning via simple Object.Assign :
let elemToUpdate = Object.assign({},story?.content?.elementsData[nodeId]);
console.log(elemToUpdate);
if(elemToUpdate) {
if(elemToUpdate.title) elemToUpdate.title[editorLang] = newName;
else elemToUpdate.title = {[editorLang]:newName} as TextDictionary;
updateElement(nodeId,elemToUpdate);
}
Now the interesting part is on my first try the update goes through without fail, but the next object I am trying to update fails with the following message:
Tree.tsx:39 Uncaught TypeError: Cannot assign to read only property 'en' of object '#<Object>'
I can't understand WHY the first one comes through, but the second gets blocked.
(I know HOW to fix it, need to do deep clone, I just want to understand WHY)
Thanks
First, let's start from why some objects in your code are readonly. Based on what you described in the question, you use a Zustand state manager. Such managers traditionally wraps you stored data to readonly objects to prevent it's manual mutation (expecting, you will change the state only via built-in mechanisms), to guarantee data stability. So, if the story?.content?.elementsData[nodeId] is the Zustand state object, it self and all it's nested objects are converted to readonly.
Second, let's define, which objects will be blocked. I see at least two objects here: elemToUpdate: { ..., title: { [lang]: string }} (elemToUpdate and it's title). Both will be converted to readonly.
Third, you use Object.assign({}, ...) which creates a new object (new reference) and clones all properties of the source object. It happens only for first level of properties, no deep clone. So, as the title is a reference to another object, it will be cloned as is and in the new object it still leads to the existing { [lang]: string } object. There are several way to solve that: 1) deep clone as you mentioned; 2) manually clone title property, for instance {..., title: { ... elemToUpdate.title }} or via Object.assign
But I would suggest don't mutate you object this way. Probably, your entire algorithm has some architectural issues in general.
That is expected because in the first case you are not assigning value to the title you are only changing the value of the title property. In the second case, you are reassigning the value of the title property,
it's the read-only value you cant change it. Let's understand with a simple example
Javascript: Only for example not related to problem
const user = {
name: 'John',
}
user.name = "Pete"; // This works
const user = {
name: 'John',
}
user = { name: 'Pete'} // This doesn't work
Typescript:
const user: Readonly<{
a: {
name: string
}
}> = {
a:{ name: 'John',}
}
user.a.name = "Pete"; // This works
user.a = { name: 'John',} // not work
The same is happening there, Typescript does not check deep Readonly prop. check here

React-Redux mapStateToProps with dynamic (uid based) path

I am new to React and React-Redux. I'm trying to pass "mapStateToProps" using a dynamic pathway depending on the users id. The basic code being used follows:
const mapStateToProps = (state) => {
console.log(state);
console.log(state.firebase.auth.uid); <===== THIS DISPLAYS THE USERS ID, PROVING THE PATH EXISTS
return {
courses: `state.firestore.ordered.${state.firebase.auth.uid}`, <======= THIS LINE IS AN ERROR EVEN THOUGH THE PATH EXISTS
}
}
The code works fine if I manually type in the users ID. Lets say the users ID is "12345", then replacing that line with
courses: state.firestore.ordered.12345, <======= THIS LINE WOULD WORK
Any clarification as to why this doesn't work and an alternative method of making this work would be greatly appreciated!
If you want to access dynamic property in JS object, you need to use square braces
courses: state.firestore.ordered[state.firebase.auth.uid]

How to push an object in array field using Firebase DB

I want to push an object in array field of Firebase database.
I've tried,
const friendObj = {
first_name: item.first_name,
last_name: item.last_name,
username: item.username,
phoneNumber: item.phoneNumber,
email: item.email,
}
db.collection('users')
.doc('documentId')
.set({ friends: friendObj })
I want to add an object in friends array of below table.
My firebase table looks like below
Can anyone please help me, I'm having difficulty while add value in DB.
Firebase doesn't actually have a data type as arrays, but you have few alternatives to store data as an array using an alternative (like sets) and there are methods specifically to work with those.
Check this link for an implementation
https://stackoverflow.com/a/40055996/11945277

Handle null username in React Native

I have a review section in my app which uses the user's username as the heading along with their review. Everything works well unless the user does not have a username set up yet. I am trying to create a condition where if the user does not have a username set, it will be Anonymous.
I am using Redux to add the reviews.
Here is my code where I dispatch my action along with the username, uid, and review to the action creator:
const review = this.props.review.review;
//const username = this.props.userData.username.username;
const uid = this.props.string.uid;
const username = () => {
if (!this.props.userData.username.username) {
return 'Anonymous';
}
return this.props.userData.username.username;
};
//dispatch action with my values
this.props.submitUserReview({ review, username, uid });
I appreciate any help and guidance with this issue. Cheers!
SOLVED:
I solved this by using lodash thanks to Noah Allen:
const username = _.get(userData, 'username.username', 'Anonymous')
Edit: Lodash one-liner:
const username = _.get(userData, 'username.username', 'Anonymous')
To fix your example, you really only need to add a pair of parenthesis (explanation below):
this.props.submitUserReview({ review, username: username(), uid });
Taking it a step further you should do this to help improve code readability, re-use, and abstraction:
function checkUsername (userData) {
// Checks for undefined, null, and a string with no characters:
if (userData && userData.username && userData.username.username && username.length > 0) {
return userData.username.username
}
// Default case:
return 'Anonymous'
}
Then in your component:
this.props.submitUserReview({ review, username: checkUsername(this.props.userData), uid })
Basically you were passing the function username to your submission, rather than running the function and passing the result of the function to submission. In this example, you define the check username function elsewhere to help with abstraction. And then in your submission you run the function and pass the result of it to your submitUserReview function.
Note that in JS, when you have a function:const x = () => 1, the type of x will be a function. As soon as you use the parenthesis, the function gets called: x(), and the value is returned. So in your original example, you were passing x as a parameter - which means that it was never evaluated and you passed a function into your store. You need to get the value out of it and pass that to redux.
This is good to know when binding a function to a component too. For example:
<Button onPress={this.doSomething()} />. In this case, the function doSomething will get evaluated when the Button is loaded, when you really want to evaluate it when it is tapped, like so: Button onPress={this.doSomething} />. In this case, onPress evaluates the function, so you don't need to do it yourself.
Another way to solve your problem is in the component where you render the username:
<Text>{review.username ? review.username : 'Anonymous'}</Text>
That conditionally shows username if it exists in a review object (or however it looks in your component), and shows Anonymous otherwise. This might be helpful if you need to tell whether or not the review actually has a username somewhere else.
You do not need to create funcion for this small thing. You can write it in below fashion:
this.props.submitUserReview({ review, username: (this.props.userData.username.username || "Anonymous"), uid });
Here if value of this.props.userData.username.username is falsy i.e. null OR undefined OR empty string then it will pass Anonymous as a parameter. Hence it will solve your usecase.

How to set the key name on an array in Angularfire

I am creating arrays using Angularfire but I am unable to set the key name. Firebase is automatically giving me a cryptic key (eg Jm9vLy8Lye-KS35KsmL) but I would like to set it myself to something more meaningful. It is unclear how I do this in Angularfire. I am using the $add method on $firebaseArray:
var firebaseRef = new Firebase("https://firebase_location");
$scope.messages = $firebaseArray(firebaseRef);
$scope.messages.$add(
{
FirstName: patient.FirstName,
LastName: patient.LastName
}
).then(function(firebaseRef) {
var id = firebaseRef.key();
});
The data is stored fine and I can see it on my dashboard. However, id is always a cryptic firebase value. I would love to be able to set it myself to something meaningful. In my case the individual patient's ID would be meaningful...
Any thoughts?
Thank you!
Thanks for the response.
I found that the child function will do what I need. I first specify the child and then set it.
var patient_child = firebaseScreenRef.child(patient.PatientId);
patient_child.set(
{
FirstName: patient.FirstName,
LastName: patient.LastName,
});
Adding an item to a firebase array make firebase define a unique id for you. If this is not what you want, I think you can try getting the "messages" object with $firebaseObject, instead of getting just the list with $firebaseArray. Then you will be able to edit your object in js, in order to add your items to the messages collection. In this way you can use the id that best suits your needs. Finally, you have to $save() your entire object. Look here: https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-firebaseobject
Hope this helps.
For your question, I found an explanation. You don't need to use firebaseObject, you should use ref directly:
var ref = new Firebase(FURL);
createProfile: function(id ,user) {
var profile = {`enter code here`
name: user.name,
email: user.email,
gravatar: get_gravatar(user.email, 40)
};
var profileRef = $firebaseArray(ref.child('profile').child(id));
return ref.child('profile').child(id).set(profile);
},
In the code, I use ref to reference my URL. With profileRef, I created a child profile and I added id for the profile. Afterwards, I use ref directly to set the value profile for the id that I want. You see, it is very easy.

Resources