ReactJS: State variable not updating as expected - reactjs

So I'm learning react and as a practise exercise, I made a basic add user app. The data should only be inserted in case in both input fields are valid. The problem is even if validations fails, the app still enters the data into the field. I'm not sure why because I have explicitly added the condition:
const onSubmit = (event) => {
event.preventDefault();
validateUser(newUser);
if (!showModal.display) {
console.log(showModal.display);
props.onAddingNewUser(newUser);
setNewUser({
username: "",
age: "",
});
}
};
Here is the link to sandbox for my app: https://codesandbox.io/s/beautiful-wilbur-j35mmi
Try clicking submit button without entering any data and you'll see it still populated the list.
I'm still learning React so it would be great if someone can elaborate what I'm doing wrong here.
Thanks!

the reason for that is every time you close the error box you convert display to false again, so no matter what in the end the if statmet:
validateUser(newUser);
if (!showModal.display){
...more code
}
will always be true because even if the user is not valid when you close the error box display will be false again and the if statement will run.
if you want a way around you can return false or true from the validateUser there are more ways to solve this, this is just one way.

Alternatively, you can check for validation before submitting a new user. Here is example below.
if(user.username !== "") and if(user.username) essentially same thing if statement will evaluate as true. Also you don't need to pass newUser from params you can access directly from your state.
const validateUser = () => {
let bool = true;
if (newUser.username && newUser.age && !isNaN(parseInt(newUser.age))) {
setShowModal({
display: false,
text: "",
});
} else {
bool = false;
console.log("Both are empty");
setShowModal({
display: true,
text: "Please enter a valid username and age (non-empty values).",
});
}
if (!newUser.username) {
bool = false;
// Username is empty
console.log("Username is empty");
setShowModal({
display: true,
text: "Please enter a valid age (> 0).",
});
}
if (!newUser.age) {
// Age is empty
bool = false;
console.log("Age is empty");
setShowModal({
display: true,
text: "Please enter a valid username (non-empty values).",
});
}
console.log("Validation End:", showModal);
return bool;
};
const onSubmit = (event) => {
event.preventDefault();
if (!showModal.display && validateUser()) {
console.log(showModal.display);
props.onAddingNewUser(newUser);
setNewUser({
username: "",
age: "",
});
}
};

Related

Required doesn't work after adding test Yup

This is what i've done
subject: Yup.string().test('checkForManualText', 'Please, add hand-written subject as well',
function(inputValue) {
let newMessage = inputValue.replace(/\{{(.+?)\}}/gmi, '').replace('/\s/gmi', '').trim()
return newMessage.length !== 0
}).required()
Now the test validation works fine, but the required stopped working. Before adding test, all was good.
You don't need to use required when using .test. Returning false will show the error message. Just add null/undefined check to the string.
subject: Yup.string().test('checkForManualText', 'Please, add hand-written subject as well',
function(inputValue) {
if(!inputValue) return false;
let newMessage = inputValue.replace(/\{{(.+?)\}}/gmi, '').replace('/\s/gmi', '').trim()
return newMessage.length !== 0
})
Working Example

ReactJS / Firebase: Check collection map field if code exists and then update field within

I have the following map within a firebase collection:
{
"1234567890": {
"Redeemed": false
},
"2234567890": {
"Redeemed": false
},
"id": "4ced9690-4925-11ed-b8ce-cd7059f0665a"
}
What I am trying to do, is match the number to the user entering this via a textbox and then setting Redeemed to true if it matches ... it is basically for some promo codes.
How can I lookup the mapping number and then set Redeemed to true once submitted?
Is there a better way to set up this mapping/array?
After you have read the document and stored the data in a variable
const data = {
"1234567890": {
"Redeemed": false
},
"2234567890": {
"Redeemed": false
},
}
// Take the user input as you want to
const input = "2234567890";
// Checking if the key exist
try {
const isRedeemed = data[input].Redeemed;
console.log(isRedeemed)
} catch (err) {
console.log(err)
}

React Native: state is null sometimes - setState is working asynchronously

I think I do have some problems with the state of my application . I already figured out that this.setState({ ... }) is a function which is working asynchronously.. So, I think this has something to do with my problem.
My problem is that I want to show a dialog popup to my user when I am sending a push notification via OneSignal. This push notification gets received by both iOS and Android. Even when the app is running in the
background, foreground or got killed and isn't even running in the background. For the popup dialog I am using this package: react-native-popup-dialog
This popup is only visible if I send certain key/value pairs with the push notification. These keys are:
showPopup:true - Displaying the popup when true. If it isn't set or not equals true, it isn't displayed!
openLink:mydomain.de- Adds a button with a link to the popup
buttonText:Open in Browser - Sets the button text to the link
Note, the extra URL button is only added to the popup if key openLink and buttonText is set. Of none of them or only one of the key is set, it isn't displaying this button.
However, the popup dialog only shows up sometimes in some cases. I will list them for you below:
Case 1: The application is opened. In this case the popup shows up on iOS and Android. This gets handled by the
onReceived function!
Case 2: The app is completely close (swiped off the screen/killed). In this case, the popup shows up on Android
devices but not on iOS devices! This gets handled by the onOpened function!
Case 3: The app has been opened and is now running in the background. In this case, the popup shows up on iOS
devices but not on Android devices. This gets handled by the onOpened function too!
So, cause I am not getting and error messages or something else, I guess I am right with my guess that this issue is
due the asynchronous this.setState({ ... }) function.
My question now is how can I make sure that the state of notification and visible is always set before rendering the getPopup(...) method.. I already was thinking about implementing it so that I call the getPopup(...) function with parameters. So, I can be sure the parameters are always set before calling the method. However, sadly this is not possible. Cause the class you see below, the SuperScreen class, is just a class which gets extended by some subclasses to bundle my code like the push notification code or some functions I need in every of these subclasses.
Also, I already tried out to add a variable to my SuperClass state e.g. called stateSet which gets set after the setState({ ... }) function of either onReceived or onOpened has finished and verify it with
if(this.state.stateSet) in the first line of the getPopup(...) function. However, this is also not possible. The reason for that is because then my popup is not closing anymore when I am pressing either Ok or the link button.
If you guys have any ideas on how to solve this problem I really would appreciate it!
Here is my code:
export default class SuperScreen extends Component {
constructor(props) {
super(props);
this.state = {
pushNotification: null,
visible: false
};
OneSignal.init("00000000", {
kOSSettingsKeyAutoPrompt: true
});
OneSignal.inFocusDisplaying(0);
OneSignal.enableVibrate(true);
OneSignal.enableSound(true);
OneSignal.addEventListener("received", this.onReceived);
OneSignal.addEventListener("opened", this.onOpened);
OneSignal.addEventListener("ids", this.onIds);
}
componentWillUnmount() {
OneSignal.removeEventListener("received", this.onReceived);
OneSignal.removeEventListener("opened", this.onOpened);
OneSignal.removeEventListener("ids", this.onIds);
}
onReceived = notification => {
//App is opened!
console.log("Notification received: ", notification);
this.setState({
pushNotification: notification,
visible: true
});
if (notification.payload.notificationID != null) {
firebase.analytics().logEvent("Popup_Link_Button", {
notificationID: notification.payload.notificationID,
clicked: true
});
}
};
onOpened = openResult => {
//App either is closed or running in background
//Android: Closed: Showing Background: Not Showing
//iOS: Closed: Not Showing Background: Showing)
console.log("openResult: ", openResult);
this.setState({
pushNotification: openResult.notification,
visible: true
});
if (openResult.notification.payload.notificationID != null) {
firebase.analytics().logEvent("Popup_Link_Button", {
notificationID: openResult.notification.payload.notificationID,
clicked: true
});
}
};
onIds = device => {
console.log("Device info: ", device);
};
getPopup() {
if (
this.state.pushNotification != null &&
this.state.pushNotification.payload.additionalData != null &&
this.state.pushNotification.payload.additionalData.showPopup != null &&
this.state.pushNotification.payload.additionalData.showPopup == "true"
) {
var actionButtons = null;
if (
this.state.pushNotification.payload.additionalData.openLink != null &&
this.state.pushNotification.payload.additionalData.buttonText != null
) {
actionButtons = [
<DialogButton
text="Ok"
key={0}
onPress={() => {
this.setState({ visible: false });
firebase.analytics().logEvent("Popup_Link_Button", {
notificationID: this.state.pushNotification.payload
.notificationID,
opened: false
});
}}
/>
];
actionButtons.push(
<DialogButton
text={this.state.pushNotification.payload.additionalData.buttonText}
key={1}
onPress={() => {
this.openLink(
this.state.pushNotification.payload.additionalData.openLink
);
this.setState({ visible: false });
firebase.analytics().logEvent("Popup_Link_Button", {
notificationID: this.state.pushNotification.payload
.notificationID,
link: this.state.pushNotification.payload.additionalData
.openLink,
opened: true
});
}}
/>
);
} else {
actionButtons = [
<DialogButton
text="Ok"
key={0}
onPress={() => {
this.setState({ visible: false });
firebase.analytics().logEvent("Popup_Link_Button", {
popupID: this.state.pushNotification.payload.notificationID,
opened: false
});
}}
/>
];
}
return (
<Dialog
visible={this.state.visible}
dialogTitle={
<DialogTitle
title={
this.state.pushNotification == null
? ""
: this.state.pushNotification.payload.title
}
/>
}
dialogAnimation={
new SlideAnimation({
slideFrom: "bottom"
})
}
dialogStyle={{ marginLeft: 20, marginRight: 20 }}
actions={actionButtons}
>
<DialogContent>
<Text />
<Text>
{this.state.pushNotification == null
? ""
: this.state.pushNotification.payload.body}
</Text>
</DialogContent>
</Dialog>
);
}
}
you can add a callback to setState to ensure the code runs after setting the state:
this.setState().then({ //Do something here. })
Use async, await for asynchronously .
onReceived = async (notification) => {
//App is opened!
console.log("Notification received: ", notification);
await this.setState({ // It will wait until finish setState.
pushNotification: notification,
visible: true
});
if (notification.payload.notificationID != null) {
firebase.analytics().logEvent("Popup_Link_Button", {
notificationID: notification.payload.notificationID,
clicked: true
});
}
};
You can use a callback in setState!, i learned from it a month ago and it's been useful since . Check this article , you can pass a function as a callback ;)
this.setState(
{ pushNotification: notification,
visible: true }, () => {this.getPopup()}) //this.getPopup it's the second parameter, a callback
Check the article, it's short and will help

Intercept checkbox change event in VueJS

I have a long set of checkboxes. I would like two groups of three of them to behave as radio buttons. Now, leaving aside my UX choices, how can I make this work?
The checkboxes are implemented as properties on a single object, layer:
data() {
return {
layer: {},
}
},
watch: {
layer: {
handler(val, oldval) {
mapping.updateLayers(val)
},
deep: true,
},
},
That works fine. But intercepting val and updating this.layer inside handler() doesn't:
handler: function(val, oldval) {
if (val.FutureYear) { this.layer.NextYear = false; this.layer.ExistingYear = false; }
if (val.ExistingYear) { this.layer.NextYear = false; this.layer.FutureYear = false; }
if (val.NextYear) { this.layer.ExistingYear = false; this.layer.FutureYear = false; }
mapping.updateFoiLayers(val);
},
How can I achieve this result? (I'd prefer not to have to implement actual radio buttons because that makes managing all the layers and UI more complex.)
Example: https://codepen.io/jacobgoh101/pen/NyRJLW?editors=0010
The main problem is the logic in watch.
If FutureYear is selected already, other field becomes unchangeable. Because if (val.FutureYear) is always the first one being triggered and the other 2 field will always be set to false immediately.
Another thing about watch is that it will be triggered when
user changed the value
program changed the value (this is unnecessary and make things harder to handle)
Therefore, handling #change event is more appropriate in this scenario.
JS
methods: {
handleChange: function(e) {
const name = e.target.name;
if (this.layer[name]) {
Object.keys(this.layer).map((key)=>{
if(key != name) {
this.layer[key] = false;
}
})
}
}
}
html
<div id="app">
<input type="checkbox"/ v-model="layer.FutureYear" name="FutureYear" #change="handleChange($event)">FutureYear<br/>
<input type="checkbox"/ v-model="layer.NextYear" name="NextYear" #change="handleChange($event)">NextYear<br/>
<input type="checkbox"/ v-model="layer.ExistingYear" name="ExistingYear" #change="handleChange($event)">ExistingYear<br/>
</div>

React form validation still adds values

So I have a little bit of form validation going on and I am running into an issue. When I first load the web app up and try adding a value and submitting with my button it doesn't allow me and gives me the error I want to see. However, when I add a value setState occurs and then my value is pushed to UI and I try to add another blank value it works and my conditional logic of checking for an empty string before doesn't not go through what am I doing wrong?
addItem() {
let todo = this.state.input;
let todos = this.state.todos;
let id = this.state.id;
if (this.state.input == '') {
alert("enter a value");
document.getElementById('error').style.color = 'red';
document.getElementById('error').innerHTML = 'Please enter something first';
}
else {
this.setState({
todos: todos.concat(todo),
id: id + 1,
}, () => {
document.getElementById('test').value = '';
})
console.log(this.state.id);
}
}
You are checking this.state.input but no where in that code are you setting the input value on the state.
Try adding this where it makes sense in your application:
this.setState({ input: 'some value' });
Also, I recommend you use the state to define the application UI. So instead of using document.getElementById('error') or document.getElementById('test').value, have the UI reflect what you have in your state.
See here for more info: https://reactjs.org/docs/forms.html
Instead of manipulating the DOM directly:
document.getElementById('test').value = '';
you'll want to use React:
this.setState({ input: '' });
A good ground rule for React is to not manipulate the DOM directly through calls like element.value = value or element.style.color = 'red'. This is what React (& setState) is for. Read more about this on reactjs.org.
Before you look for the solution of your issue, I noticed that you are directly updating the DOM
Examples
document.getElementById('error').style.color = 'red';
document.getElementById('error').innerHTML = 'Please enter something first';
document.getElementById('test').value = '';
Unless you have special use case or dealing with external plugins this isn't recommended, when dealing with React you should update using the virtual DOM. https://www.codecademy.com/articles/react-virtual-dom
Pseudo code sample
constructor(props) {
this.state = {
// retain previous states in here removed for example simplicity
errorString: ''
}
}
addItem() {
let todo = this.state.input;
let todos = this.state.todos;
let id = this.state.id;
if (this.state.input == '') {
alert("enter a value");
this.setState({
errorString: 'Please enter something first'
});
}
else {
this.setState({
todos: todos.concat(todo),
id: id + 1,
input: '',
});
}
}
// notice the "error" and "test" id this could be omitted I just added this for your reference since you mentioned those in your example.
render() {
return (
<div>
{(this.state.errorString !== '') ? <div id="error" style={{color: 'red'}}>{this.state.errorString}</div> : null}
<input id="test" value={this.state.input} />
</div>
}
Every time you invoke setState React will call render with the updated state this is the summary of what is happening but there are lot of things going behind setState including the involvement of Virtual DOM.

Resources