Here is my code, I am using React:
I am using Local Storage in order to persist the state even after page refresh and to remain on the same page.
class App extends React.Component{
constructor(props){
super(props);
this.state = localStorage.getItem("app_state") ? localStorage.getItem("app_state") : {
account: {
email: "",
password: ""
},
errorMessage: "",
token: "",
authenticated: false
};
}
notLoggedInhomePage = () =>{
if(!this.state.authenticated) {
return (
<form onSubmit={this.handleSubmit}>
<br/>
<label>Email Address</label>
<input type="text" value={this.state.account.email} onChange={this.handleChange} name="email"/>
<label>Password</label>
<input type="password" value={this.state.account.password} onChange={this.handleChange} name="password" />
<div>
{this.state.errorMessage}</div>
<br/>
<Button type="submit" onClick={() => {
this.someFunction(String(this.state.account.email));
}}>Sign In</Button>
</form>
);
}else{
return(<Redirect to="/items/somelink" />);
}
}
componentDidUpdate(prevProps, prevState){
if(this.state.token !== prevState.token){ //set a new state if token changes
localStorage.setItem("app_state", this.state);
}
}
}
export default App;
Here is the error that I am getting:
It is saying that the email is undefined, what is the reason behind such error message, and why/how is the email undefined, even though it's defined as an empty string in the state.
What is a possible fix to the above ?
local storage will only return String. not object or array.
you need to parse it before assigning it to state
JSON.parse(localStorage.getItem(app_state))
let appState = localStorage.getItem(app_state)
if(appState) {
appState = JSON.parse(appState)
}
In you code
state = getState()
const getState =() => {
let appState = localStorage.getItem(app_state)
if(appState) {
return JSON.parse(appState)
} else {
return {
account: {
email: "",
password: ""
},
errorMessage: "",
token: "",
authenticated: false
};
}
}
Using setItem inserts a DOMstring value into local storage, getItem returns said DOM string (MDN), when I run your code snippet it returns "[object Object]", you can get around this by using
localStorage.setItem("app_state", JSON.stringify(this.state));
And then retrieving the data as:
this.state = localStorage.getItem("app_state") ? JSON.parse(localStorage.getItem("app_state")) : {
account: {
email: "",
password: ""
},
errorMessage: "",
token: "",
authenticated: false
};
Related
I'm trying to use setState to update a state array in React using a basic HTML input.
The state I'm trying to update looks like:
"businessName": "",
"grossAnnualSales": 0,
"contactEmail": "",
"numEmployees": 0,
"annualPayroll": 0,
"locations": [{"zip": ""}],
"industryId": "",
I need to add a zip code inputted by the user in a React component to this object in the array.
So far I've tried this and it's not working, it just updates to a string and not an array:
updateZip(){
return e => this.setState({"locations": [{"zip": e.currentTarget.value}]})
}
The React component:
<input onChange={this.updateZip()} type="text" className="zip-input"/>
How can I update the state succesfully?
Try replacing your updateZip function with an arrow function.
updateZip = (e) => {
return e => this.setState({"locations": [{"zip": e.currentTarget.value}]}) }
Also
<input onChange={(e) => this.updateZip(e)} type="text" className="zip-input"/>
use e.target.value and pass onChange={this.updateZip}
class App extends Component {
state = {
locations: [{ zip: "" }]
};
updateZip = (e) => {
this.setState({ locations: [{ zip: e.target.value }] });
};
render() {
return (
<div>
<input onChange={this.updateZip} type="text" className="zip-input" />
<p>{this.state.locations[0].zip}</p>
</div>
);
}
}
export default App;
CodeSandBox
I have the following React Component, which holds a form with two inputs and a button.
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: null,
password: null
}
}
emailInputChanged = (e) => {
this.setState({
email: e.target.value.trim()
});
};
passwordInputChanged = (e) => {
this.setState({
password: e.target.value.trim()
});
};
loginButtonClicked = (e) => {
e.preventDefault();
if (!this.isFormValid()) {
//Perform some API request to validate data
}
};
signupButtonClicked = (e) => {
e.preventDefault();
this.props.history.push('/signup');
};
forgotPasswordButtonClicked = (e) => {
e.preventDefault();
this.props.history.push('/forgot-password');
};
isFormValid = () => {
const {email, password} = this.state;
if (email === null || password === null) {
return false;
}
return isValidEmail(email) && password.length > 0;
};
render() {
const {email, password} = this.state;
return (
<div id="login">
<h1 className="title">Login</h1>
<form action="">
<div className={(!isValidEmail(email) ? 'has-error' : '') + ' input-holder'}>
<Label htmlFor={'loginEmail'} hidden={email !== null && email.length > 0}>email</Label>
<input type="text" className="input" id="loginEmail" value={email !== null ? email : ''}
onChange={this.emailInputChanged}/>
</div>
<div className={(password !== null && password.length === 0 ? 'has-error' : '') + ' input-holder'}>
<Label htmlFor={'loginPassword'}
hidden={password !== null && password.length > 0}>password</Label>
<input type="password" className="input" id="loginPassword"
value={password !== null ? password : ''}
onChange={this.passwordInputChanged}/>
</div>
<button type="submit" className="btn btn-default" id="loginButton"
onClick={this.loginButtonClicked}>
login
</button>
</form>
<div className="utilities">
<a href={'/signup'} onClick={this.signupButtonClicked}>don't have an account?</a>
<a href={'/forgot-password'} onClick={this.forgotPasswordButtonClicked}>forgot your password?</a>
</div>
</div>
)
}
}
export function isValidEmail(email) {
const expression = /\S+#\S+/;
return expression.test(String(email).toLowerCase());
}
The values of the inputs are stored in the component's state. I have given them initial value of null and update them using setState() in the onChange event.
In the render() method I use the state to colour inputs with invalid values. Currently I am only checking the email value against a simple regex and the password to be at least 1 character in length.
The reason I have set the initial values of the state variables to null so I can check in the layout and the initial style on the input to not be marked as "has-errors". However I need to extend the check to:
this.state.email !== null && this.state.email.length === 0
in order to work, because null has no length property.
Is there a cleaner and "React-ish" way to achieve this:
- initial state of the div holding the inputs has no class has-errors
- less checks when setting the value attribute on the input (because React doesn't accept null as value of the attribute)
Edit:
If the initial value of this.state.email and this.state.password are empty strings, I get has-error applied to the div holding the inputs. In my opinion this is bad UX, because the user hasn't done anything and he is already wrong.
The hidden attribute is used by a custom component I made, which acts like "placeholder" (gets erased if something is typed in the input).
Video below showing how my form looks like when this.state.email and this.state.password are empty strings as well how my <Label> component works:
You could create a validate function which return an errors object to know which fields are in error, and use empty strings as initial values. I don't understand what you are trying to do with the hidden attribute.
Edit: add a touched property in the state, to know which field have been touched.
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
touched: {},
};
}
emailInputChanged = e => {
this.setState({
email: e.target.value.trim(),
touched: {
...this.state.touched,
email: true,
},
});
};
passwordInputChanged = e => {
this.setState({
password: e.target.value.trim(),
touched: {
...this.state.touched,
password: true,
},
});
};
loginButtonClicked = e => {
e.preventDefault();
if (!this.isFormValid()) {
//Perform some API request to validate data
}
};
isFormValid = () => {
const errors = this.validate();
return Object.keys(errors).length === 0;
};
validate = () => {
const errors = {};
const { email, password } = this.state;
if (!isValidEmail(email)) {
errors.email = true;
}
if (password.length === 0) {
errors.password = true;
}
return errors;
};
render() {
const { email, password, touched } = this.state;
const errors = this.validate();
return (
<div id="login">
<h1 className="title">Login</h1>
<form action="">
<div
className={
(errors.email && touched.email ? 'has-error' : '') +
' input-holder'
}
>
<Label htmlFor={'loginEmail'}>email</Label>
<input
type="text"
className="input"
id="loginEmail"
value={email}
onChange={this.emailInputChanged}
/>
</div>
<div
className={
(errors.password && touched.password ? 'has-error' : '') +
' input-holder'
}
>
<Label htmlFor={'loginPassword'}>password</Label>
<input
type="password"
className="input"
id="loginPassword"
value={password}
onChange={this.passwordInputChanged}
/>
</div>
<button
type="submit"
className="btn btn-default"
id="loginButton"
onClick={this.loginButtonClicked}
>
login
</button>
</form>
</div>
);
}
}
export function isValidEmail(email) {
const expression = /\S+#\S+/;
return expression.test(String(email).toLowerCase());
}
Normally in HTML you do something like this:
<form>
<input type="text"/>
<input type="text"/>
<input type="submit"/>
</form>
I believe this is not the React way to do it.
Another way to do like i did in my app, is not the best way to do as well i believe.
Like this:
buttonclickRequest(){
var reasonn = document.getElementById("testControl").value;
}
<div>
<FormControl id="testControl"/>
<Button id="btnRequest" onClick={this.buttonclickRequest}/>
</div>
In other stackoverflow topics I saw examples like this:
constructor(props) {
super(props);
this.state = {
firstName: '',
lastName: '',
place: '',
address: '',
email: '',
phoneNumber: ''
};
}
handleClick() {
//do something
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
<div>
<input type="text" onChange={e => this.handleChange(e)}/>
<button type="submit" onClick={this.handleClick}/>
</div>
But i have my questions at this point as well,
I don't know how to do this properly with multiple text inputs:
You can make multiple specific changehandlers which is inefficiƫnt,
You can make a changehandler with a switch to set the properties
Is it even efficient to do a handle change on the inputfields? Because I just want the inputfield values when the button is clicked..
This is the form I'm talking about.
So how to properly get the multiple input data with React, when the button is clicked?
Thanks for your help in advance!
I think first you should add name attribute to your input field and use the name to set the state and then use the state on handleClick:
constructor(props) {
super(props);
this.state = {
firstName: '',
lastName: '',
place: '',
address: '',
email: '',
phoneNumber: ''
};
}
handleClick = () => {
//do something
console.log(this.state);
// should be something like this {
// firstName: '',
// lastName: '',
// place: '',
// address: '',
// email: '',
// phoneNumber: ''
//}
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return(
<div>
<input type="text" name="firstName" onChange={this.handleChange}/>
<input type="text" name="lastName" onChange={this.handleChange}/>
<button type="submit" onClick={this.handleClick}/>
</div>
)
}
Note that the name should match the state key.
Assuming you may be looking for state values
constructor(props) {
super(props);
this.state = {
firstName: '',
lastName: '',
place: '',
address: '',
email: '',
phoneNumber: ''
};
}
handleClick() {
console.log("State ==>", this.state);
}
setFirstName = (e) => {
this.setState({
firstName: e.target.value
})
}
setPhoneNumber = (e) => {
this.setState({
phoneNumber: e.target.value
})
}
render(){
return('
<div>
<label> First Name </label>
<input type="text" name="firstName" onChange={e => this.setFirstName(e)}/>
<label> Phone Number </label>
<input type="text" name="phoneNumber" onChange={e => this.setPhoneNumber(e)}/>
<button type="submit" onClick={this.handleClick}/>
</div>
')
}
and yes... you are right creating change handlers for each input its not efficient on your case, what you need is a react form that gives you the old and submit options,you cant use old form because it needs to update the page to retrieve the values.
I Suggest you to use Antd form witch gives you all in components, i even suggest you to use their Input components witch look very nice and handles pretty well.
Antd Design (User interface components for react) - Antd
some sample code.
give it a try !!! CodeSandbox
I am attempting to create an edit form in React. I can successfully query my database and can see the data is what is expected after my get request. I then want to set the state of various properties on my component so it fills in my form, which I can then edit.
Here is my react component
constructor(props) {
super(props);
this.state = {
name: '',
teamName: '',
bio: '',
teamId: '',
uploadedFileCloudinaryUrl: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.findPlayerById(this.props.params.id)
}
findPlayerById(playerId) {
axios.get("/api/player/" + playerId)
.then(res => {
const player = res.data;
console.log(player);
this.setState({
name: player.name,
teamName: player.teamName,
bio: player.bio,
teamId: player.teamId,
uploadedFileCloudinaryUrl: player.profileImageUrl
});
});
}
And here is my rendered HTML
<div className="form-group">
<label className="control-label">Name</label>
<input type="text" className="form-control" ref="name"
defaultValue={this.state.name}
onChange={this.handleChange.bind(this, 'name')}/>
</div>
I don't get any errors but I don't think the state is being correctly set within my promise. Am I doing something wrong?
I'm trying to experiment with React, I have a issue of updating state values of a particular key.
Here is my state
this.state = {
connections : {
facebook : "http://facebook.com",
flickr : null,
foursquare : null,
googleplus : null,
id : 0,
instagram : "http://instagram.com/",
openstreetmap : null,
pinterest : null,
place_id : 1,
tomtom : null,
tripadvisor : null,
twitter : "http://twitter.com",
vimeo : null,
wikipedia : null,
yelp : null,
youtube : null
},
contact : {
}
}
I'm calling a external component and sending parameters to it.
<Connection type="Facebook" icon={facebookIcon} placeholder="Add Facebook Link" name="facebook" value={this.state.connections.facebook} onChange={this.handleChange.bind(this)}/>
<Connection type="Twitter" icon={twitterIcon} placeholder="Add Twitter Link" name="twitter" value={this.state.connections.twitter} onChange={this.handleChange.bind(this)}/>
<Connection type="Instagram" icon={instagramIcon} placeholder="Add Instagram Link" name="instagram" value={this.state.connections.instagram} onChange={this.handleChange.bind(this)}/>
Component in external file:
<input type="text" name={this.props.name} placeholder={this.state.placeholder} value={this.props.value} onChange={this.props.onChange} />
On change of value in textbox,
handleChange(e) {
this.setState({
connections : {[e.target.name]: e.target.value}
})
}
While I try to change any field values, it sets the rest 2 with empty. For example, if I try to edit textbox of Facebook, it sets Twitter and Instagram values to empty.
May I know what I'm doing wrong in setting handleChange? I'm sure its something wrong with this.setState, but not sure how to target particular key value.
In this case you need get previous state and create new (for merge states you can use Object.assign, spread operator ..., or lodash merge), because setState,
Performs a shallow merge of nextState into current state.
this.setState({
connections: Object.assign(
{},
this.state.connections,
{ [e.target.name]: e.target.value }
),
contact: {}
});
Example
class Connection extends React.Component {
render() {
return <input
type="text"
placeholder={this.props.placeholder}
name={this.props.name}
value={this.props.value}
onChange={this.props.onChange} />
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
connections : {
facebook : "http://facebook.com",
flickr : null,
foursquare : null,
googleplus : null,
id : 0,
instagram : "http://instagram.com/",
openstreetmap : null,
pinterest : null,
place_id : 1,
tomtom : null,
tripadvisor : null,
twitter : "http://twitter.com",
vimeo : null,
wikipedia : null,
yelp : null,
youtube : null
},
contact: {}
}
}
handleChange(e) {
this.setState({
connections: Object.assign(
{},
this.state.connections,
{ [e.target.name]: e.target.value }
),
contact: {}
})
}
render() {
return (
<div>
<Connection
type="Facebook"
placeholder="Add Facebook Link"
name="facebook"
value={this.state.connections.facebook}
onChange={this.handleChange.bind(this)}
/>
<Connection
type="Twitter"
placeholder="Add Twitter Link"
name="twitter"
value={this.state.connections.twitter}
onChange={this.handleChange.bind(this)}
/>
<Connection
type="Instagram"
placeholder="Add Instagram Link"
name="instagram"
value={this.state.connections.instagram}
onChange={this.handleChange.bind(this)}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
setState performs a shallow merge instead of a deep one (more explanation in the docs), so you're completely overwriting all of connections with just your one value. If you want to keep the other keys in connections intact, you can change handleChange to this:
handleChange(e) {
this.setState({
connections : {...this.state.connections, [e.target.name]: e.target.value}
})
}
This will shallow copy all of this.state.connections, and then set e.target.name over top of it.
You are right, the problem is with your setState.
handleChange(e) {
this.setState({
connections : {[e.target.name]: e.target.value} //this will trigger twitter and instagram with empty value.
})
}
An simple workaround would be:
handleChange(e) {
this.setState({
connections : {[e.target.name]: e.target.value, twitter: 'initial val', instagram: 'initial val'}
})
}
You should actually rather use
handleChange(e) {
this.setState(oldState => ({
connections: {[e.target.name]: e.target.value, ...oldState.connections},
}));
}
because otherwise you might overwrite state-changes that have not been applied yet.
see https://reactjs.org/docs/state-and-lifecycle.html