I am very new to JS, React and am not able to pass state objects from React Component function to a external function.
This is my FormPage parent component method 'formSubmit ' from where i am making the call
import { formValidate } from './FormHelpers';
class FormPage extends React.Component {
constructor(props) {
super(props);
this.state = {
fields: {
userName: "", userAttributes: []
},
errors: {}
}
};
formSubmit = (e) => {
e.preventDefault();
const formErrors = formValidate(this.state.fields);
....
.....
}
Below is my function formValidate defined in FormHelpers.js file:
export const formValidate = ({fieldsIn}) => {
let errors = {};
let formIsValid = true;
let fields = fieldsIn;
if (!fields["userName"]) {
formIsValid = false;
errors["userName"] = "Please enter metadata";
}
if (typeof fields["userName"] !== "undefined") {
if (!fields["userName"].match(/^[a-zA-Z ]*$/)) {
formIsValid = false;
errors["userName"] = "Please enter alphabet char only";
}
}
let formErrors = {
errors: errors, formIsValid: formIsValid
};
return formErrors;
}
I would like to call formValidate() function with state.fields as argument and return formErrors as object back to React.
While executing, am getting below error in FormHelpers.js
Uncaught TypeError: Cannot read property 'userName' of undefined
I don't seem to understand my mistake here. Can you please guide me on this.
Thanks in advance
Related
I'm trying to set a variable equal to a return of a function but I don't understand how can i do.
In particular this is the code:
constructor() {
super();
this.manager = new BleManager()
this.state = {
info: "",
values: {}
}
this.deviceprefix = "FM_RAW";
this.devicesuffix_dx = "DX";
}
model_dx(model) {
return this.deviceprefix + model + this.devicesuffix_dx
}
if (device.name === "THERE i should use the return of model_dx") {
this.info(device.id)
this.manager.stopDeviceScan();
device.connect()
I should check device.name with the result of the model_dx function. How can I do?
Thank you
How about calling it? Create a instance of the object and call it:
// Assume the name is CustomObj
class CustomObj {
constructor() {
super();
this.manager = new BleManager()
this.state = {info: "", values: {}}
this.deviceprefix = "FM_RAW";
this.devicesuffix_dx = "DX";
}
model_dx(model) {
return this.deviceprefix + model + this.devicesuffix_dx
}
}
// I suppose this is outside of the object? Otherwise it would be out of scope anyways as you wrote your if in no function or whatsoever
CustomObj obj = new CustomObj(); //<-- Create instance
let alwaysdifferentParam = "model test";
if (device.name === obj.model_dx(alwaysdifferentParam )) { //<-- Call it
this.info(device.id)
this.manager.stopDeviceScan();
device.connect()
}
Try this:
if (device.name === this.model_dx('pass the desired value here')) {
this.info(device.id)
this.manager.stopDeviceScan();
device.connect()
}
I'm working on a React project and implementing email validation and setting the state to true when it doesn't pass and false when it does. Validation part works, but getting undefined state on second onSubmit.
A bit more detail: I'm checking the state onChange and onSubmit. onChange seems to work as expected. onSubmit does work on the first click/submit but the very next click/submit, it changes the state to 'undefined' and I have no idea why.
Best to view my codepen and start filling in the email field and checking the console as I'm logging the state.
Here's a snippet of the code:
this.state = {
inputs: {
name: '',
email: '',
message: '',
},
show: true,
errors: {
name: false,
email: false,
message: false,
},
};
validateEmail(email) {
const re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
handleOnChange = e => {
const { name, value } = e.target;
const emailInput = e.target.value;
const emailValid = this.validateEmail(emailInput);
if (name === 'email') {
this.setState({
inputs: {
email: emailInput,
},
errors: {
email: !emailValid,
},
});
} else {
this.setState({
inputs: {
...this.state.inputs,
[name]: value,
},
errors: {
...this.state.errors,
[name]: false,
},
});
}
console.log('errors.email onChange = ' + this.state.errors.email);
};
So, why is this happening? and how can I solve?
You have missed the else condition when the field is not empty. that will remove the error object key from state, that is the one gives you the undefined error.
rewrite the handleSubmit function like this.
handleSubmit = (e, slackPost) => {
e.preventDefault();
console.log('errors.email onClick = ' + this.state.errors.email);
let inputFields = document.getElementsByClassName('form-input');
let invalidEmailMessage = document.querySelector('#invalid-email-message');
let failMessage = document.querySelector('#fail-message');
let failMessageBox = document.querySelector('.alert-fail');
// empty array to house empty field names
const emptyFieldNames = [];
// empty object to house input state
let errors = {};
// loop through input fields...
for (var i = 0; i < inputFields.length; i++) {
if (inputFields[i].value === '') {
let inputName = inputFields[i].name;
// add name to new array
emptyFieldNames.push(inputFields[i].getAttribute('name'));
// add input name and value of true to new object
errors[inputName] = true;
failMessageBox.style.display = 'block';
} else {
let inputName = inputFields[i].name;
errors[inputName] = false;
}
}
debugger;
this.setState({ errors });
if (emptyFieldNames.length > 0) {
failMessage.innerHTML =
'Please complete the following field(s): ' + emptyFieldNames.join(', ');
} else if (this.state.errors.email === true) {
invalidEmailMessage.innerHTML = 'Please enter a valid email';
} else {
console.log('For Submitted!');
}
};
I don't understand what's going on
componentDidMount() {
console.log('componentDidMount');
//const self = this;
let _id = this.props.match.params.id.toUpperCase();
if (_id != this.state.id.toUpperCase()) {
axios.get('/data/pricemultifull?fsyms=' + _id + '&tsyms=USD')
.then(response => {
// let _currentcoin = { ...resp.data.RAW.BTC.USD, ticker: _id };
this.setState({ id: _id }); //this == undefined
});
}
}
I can get a response back but this is always undefined and I'm unable to setState. I'm using an arrow function which I thought was scope 'this' to the component level. I can fix it by making a new var and setting 'this' before I make the request. I know that this should be working though. What am I missing?
My entire component
import React, { Component } from 'react';
import axios from '../../axios';
class CoinViewer extends Component {
state = {
coin: {},
hasLoaded: false,
id: ''
}
componentDidMount() {
console.log('componentDidMount');
//const self = this;
let _id = this.props.match.params.id.toUpperCase();
if (_id != this.state.id.toUpperCase()) {
axios.get('/data/pricemultifull?fsyms=' + _id + '&tsyms=USD')
.then( resp => {
// let _currentcoin = { ...resp.data.RAW.BTC.USD, ticker: _id };
this.setState({ id: _id });
});
}
}
componentWillMount() {
}
componentWillUpdate() {
}
componentDidUpdate() {
}
getCompleteCoinData(_id) {
}
render() {
return (
<div>
CoinViewer Component: {this.state.id} sads
</div>
)
}
}
export default CoinViewer
Solution 1: arrow functions..
requestSuccess = (resp) => {
// let _currentcoin = { ...resp.data.RAW.BTC.USD, ticker: _id };
this.setState({ id: _id });
}
componentDidMount() {
console.log('componentDidMount');
//const self = this;
let _id = this.props.match.params.id.toUpperCase();
if (_id != this.state.id.toUpperCase()) {
axios.get('/data/pricemultifull?fsyms=' + _id + '&tsyms=USD')
.then(this.requestSuccess);
}
}
Solution 2: binding
componentDidMount() {
console.log('componentDidMount');
//const self = this;
let _id = this.props.match.params.id.toUpperCase();
if (_id != this.state.id.toUpperCase()) {
axios.get('/data/pricemultifull?fsyms=' + _id + '&tsyms=USD')
.then((resp) => {
// let _currentcoin = { ...resp.data.RAW.BTC.USD, ticker: _id };
this.setState({ id: _id });
}.bind(this));
}
}
:Edit
Wow, the below is kinda true, but the real issue is you didn't initialize state. https://reactjs.org/docs/react-component.html#constructor
constructor() {
super();
this.state = {
coin: {},
hasLoaded: false,
id: ''
}
}
You could use lexical scoping and fix like this, this is a popular pattern to protect this.
Basically, when you use promises or functions from other libraries/ APIs you do not know what they have set their context inside the callback functions to.
In order to use the context you want, you keep the context you need saved in a variable within scope and reference it there _this, rather than by pointing to the context this. I'd recommend reading 'you dont know js' to understand this concept further.
componentDidMount() {
console.log('componentDidMount');
const _this = this;
let _id = _this.props.match.params.id.toUpperCase();
if ( _id != _this.state.id.toUpperCase() ) {
axios.get('/data/pricemultifull?fsyms=' + _id + '&tsyms=USD')
.then(response => {
_this.setState({ id: _id }); //this == undefined
});
}
}
When working with React.js, chances are you have faced a problem how
to access this from inside the promise.There is more than one solution to resolve this reference inside the
promise. The old approach would be setting the self = this
reference While this would work, the recommended solution, which is
more inline with ES6, would be to use an arrow function here:
class Component extends React.Component {
componentDidMount() {
let component = this;
axios.get('http://…').then(function(data) {
component.setState( { name: data.blah } );
});
}
}
The arrow syntax, as stated above, is a much smarter way to allow use
of this to make reference to React.Component classes, as we can see
below:
class Component extends React.Component {
componentDidMount() {
axios.get('http://…').then(data => {
this.setState( { name: data.blah } );
});
}
}
Please note, instead of using
function(data) { //body },
we used data => { //body }, and in this case this reference won’t get the promise instance back.
I'm not sure if this is a bug so I'm going to ask for advice first since I'm very new to ReactJS
I'm trying to implement Google Distance Matrix to get the distance.
(If there are any pre-built reactjs alternative solution, please let me know)
My Code:
import React, {Component} from 'react';
import GoogleMap from 'google-distance-matrix';
//...
class View extends Component {
state = {
//...
address:'',
dest: '',
distanceText:'',
openModal: false,
foundDistance: false,
distanceText: "",
address: "New York NY",
dest: "Montreal"
};
constructor (props){
super(props)
this.searchUpdated = this.searchUpdated.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
handleFormSubmit = (event) => {
const component = this
// const { address, dest } = this.state
let address = "Toronto, ON, CA"
let dest = "Vancouver, ON, CA"
let origins = ['San Francisco CA', '40.7421,-73.9914'];
let destinations = ['New York NY', 'Montreal', '41.8337329,-87.7321554',
'Honolulu'];
event.preventDefault()
// console.log(event)
GoogleMap.matrix(address, dest, function (err, distances) {
distance.key('AIzaSyCFKLGuYz6ffYby7U-ODjFtV5TO4nDyevE');
distance.units('imperial');
console.log("address");
console.log(dest);
console.log(err);
console.log(distances);
if (err) {
return console.log(err);
}
if(!distances) {
return console.log('no distances');
}
if (distances.status == 'OK') {
if(distances.rows[0].elements[0]) {
var distance = distances.rows[0].elements[0].duration['text'];
console.log(distance);
component.setState({
foundDistance: true,
distanceText: distance
});
}
}
}).bind(this);
}
componentWillMount() {
//...
}
componentDidMount () {
// ...
}
render() {
//...
return (
<div>
<button onClick={this.handleFormSubmit}>Hello </button>
</div>
)
}
}
export default View;
I literally just want to console.log() the distance between two locations but I'm unable to do so... Right now, it's giving me this error:
Uncaught TypeError: locations.join is not a function
at formatLocations (index.js:45)
What the error gives me:
The error is emanating from your handleFormSubmit function when you call GoogleMap.matrix, it should look like this:
handleFormSubmit = (event) => {
const component = this
// const { address, dest } = this.state
let address = ["Toronto, ON, CA"];
let dest = ["Vancouver, ON, CA"];
event.preventDefault()
// console.log(event)
GoogleMap.matrix(address, dest, function (err, distances) {
Notice the brackets for Toronto and Vancouver; the package expects those two arguments to be arrays, not strings
I am having an issue resetting the errorText to it's original state. Every time the form is submitted with an error(s) it adds all of the errors to the end even if they are from a previous submit.
1st click on blank form // I expect this result every time.
"Errors: Email is invalid. Password is invalid."
2nd click on blank form
"Errors: Email is invalid. Password is invalid. Email is invalid. Password is invalid."
Code Snippet
class LoginForm extends Component {
constructor(props) {
super(props)
this.state = {
details: {
email: '',
password: '',
},
hasError: false,
errorText: 'Errors: \n',
}
}
render() {
let { hasError, errorText } = this.state
const { LogUserIn } = this.props
const onTapLogin = e => {
// broken?
if (hasError) {
this.setState({
hasError: false,
errorText: 'Errors: \n',
})
}
if (!check.emailValid(e.email)){
this.setState({
hasError: true,
errorText: errorText += "\n - Email address is invalid. "
})
}
if (!check.passwordValid(e.password)){
this.setState({
hasError: true,
errorText: errorText += "\n- Password is invalid. "
})
}
if (!hasError){
LogUserIn(e)
}
}
return (
<div {...cssLoginFormContainer}>
<div {...cssLoginFormHeader}>SIGN IN</div>
<div {...(hasError ? cssErrorText : cssErrorText_hide)}>
{errorText}
</div>
...
// the form.
I would take a different approach here for displaying errors, i.e. would implement error messages as separate values on your state:
this.state = {
...,
errorEmail: '',
errorPassword: ''
}
Set the state (for specific error you have):
this.setState({
hasError: true,
errorEmail: "\n - Email address is invalid. "
})
And your errorText can be extracted in separate function to return your errors' text:
function errorText() {
return `Errors: ${this.state.errorEmail} ${this.state.errorPassword}`
}
Note: you could also nest your errors under single errors object like so:
this.state = {
...,
errors = { email: '', password: '' }
}
In this case, watch out for the state update (since it is nested). More info: How to update a nested state in React
You have two problems here: first you're mutating the state using += to concatenate the strings. You probably want to use only +.
State should only be changed using setState. When you do errorText = erroText+="whatever" you're changing the string in place. Try the following code on your console:
x = "hello"
y = x+=" world"
x
y
You'll see you changed x while trying to set y to a new value
if you do
x = "hello"
y = x+ " world"
x
y
You'll see only y changes
Second, you're using setState multiple times in a single function, which can lead to race conditions
You should try setting state only once at the end, keep the string and the boolean in variables and use them to set the state
Render function should not include any side-effect, so it's better to wrtie onTapLogin function outside render function.
I don't see the point of setState multiple times, since setState may trigger React lifecycle and it will NOT execute immediately (It's an async function).
I rearranged your code to fix the issues, but the code you got didn't call onTapLogin function, so you might need change it to fit your actually code:
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
details: {
email: '',
password: '',
},
hasError: false,
errorText: null,
};
}
onTapLogin = e => {
let errorText = 'Errors: \n';
let hasError = false;
if (!check.emailValid(e.email)) {
errorText += 'Email address is invalid.\n';
hasError = true;
}
if (!check.passwordValid(e.password)) {
errorText += 'Password is invalid.\n';
hasError = true;
}
if (!hasError) {
this.logUserIn(e);
} else {
this.setState({
errorText,
hasError,
});
}
};
logUserIn = e => {}
render() {
const { hasError, errorText } = this.state;
const { LogUserIn } = this.props;
return <div>{hasError && <p>{errorText}</p>}</div>;
}
}