React spread operator throwing error on nested state updatey - javascript

I am trying to update a nested state in my React login component to handle change to the login form fields.
Login Container Component
class Login extends Component {
constructor() {
super();
// define state
this.state = {
credentials: { 'username':'', 'password':'' },
redirect: false
};
// do the binding thing that React should really just do for you
this.handleChangeField = this.handleChangeField.bind(this);
this.httpRequest = this.httpRequest.bind(this);
this.renderRedirect = this.renderRedirect.bind(this);
}
handleChangeField(e) {
// perform coding acrobatics to update a nested state
const credentials = {...this.state.credentials} // !!! Unexpected token error
credentials[ e.target.name] = e.target.value;
this.setState({credentials})
}
renderRedirect(){
if(this.state.redirect) {
return <Redirect to='/' />
}
}
httpRequest(){
const self = this;
axios.post('/login/',this.state.credentials).then(function(res){
if(res.data.success){ // loggedIn = true;
self.setState({ redirect: true })
}
});
}
render() {
return (
<div>
{this.renderRedirect()}
<LoginForm
handleChangeField = {this.handleChangeField}
httpRequest = {this.httpRequest}
state = {this.state}
/>
</div>
)
}
}
Login Form Component
const LoginForm = ({handleChangeField, httpRequest, state}) => (
<div className="login-form block-center">
<div className="h4 mbot10">Login</div>
<div>
<input type="text" name="username" placeholder="username" value={state.credentials.username} onChange={handleChangeField} className="form-control" />
<input type="text" name="password" placeholder="password" value={state.credentials.password} onChange={handleChangeField} className="form-control" />
<button onClick={httpRequest} >Login</button>
</div>
</div>
)
It seems that the compiler is not recognizing the ... spread operator apparently need to update a nested state.
Many solutions Ive found use this exact syntax, what am I missing here?
Non-rhetorical side-question in the form of an editorial:
Why is it so goddamn hard to update a state for a nested object. Why would the people designing React ever assume that developers would use anything but a nested object for the state in most cases. I feel like React really needs to do a better job to support updating these kinds of operations. How about..
this.setState({credentials[e.target.name] : e.target.value})
Is that really so hard to implement?

Related

Send selected options from react dual listbox with post request

I'm trying to implement in my react app, two react double listbox in my component. At the moment the listboxes are filled automatically after a get request when component mounts. I need some help on how to get the selected options in each double listbox and send them to the server as json data.
I need two arrays from these lists.
This is my dual listbox classes:
import React from 'react';
import DualListBox from 'react-dual-listbox';
import 'react-dual-listbox/lib/react-dual-listbox.css';
import 'font-awesome/css/font-awesome.min.css';
export class FirstList extends React.Component {
state = {
selected: [],
};
onChange = (selected) => {
this.setState({ selected });
};
render() {
const { selected } = this.state;
return (
<DualListBox
canFilter
filterPlaceholder={this.props.placeholder || 'Search From List 1...'}
options={this.props.options}
selected={selected}
onChange={this.onChange}
/>
);
}
}
export class SecondList extends React.Component {
state = {
selected: [],
};
onChange = (selected) => {
this.setState({ selected });
};
render() {
const { selected } = this.state;
return (
<DualListBox
canFilter
filterPlaceholder={this.props.placeholder || 'Search From List 2...'}
options={this.props.options}
selected={selected}
onChange={this.onChange}
/>
);
}
}
In my component I started importing this:
import React, { useState, useEffect } from 'react'
import LoadingSpinner from '../shared/ui-elements/LoadingSpinner';
import ErrorModal from '../shared/ui-elements/ErrorModal';
import { FirstList, SecondList } from '../shared/formElements/DualListBox';
import { useHttpClient } from '../shared/hooks/http-hook';
const MyComponent = () => {
const { isLoading, error, sendRequest, clearError } = useHttpClient();
const [loadedRecords, setLoadedRecords] = useState();
useEffect(() => {
const fetchRecords = async () => {
try {
const responseData = await sendRequest(
process.env.REACT_APP_BACKEND_URL + '/components/get'
);
setLoadedRecords(responseData)
} catch (err) { }
};
fetchRecords();
}, [sendRequest]);
...
...
return (
<React.Fragment>
<ErrorModal error={error} onClear={clearError} />
<form>
<div className="container">
<div className="row">
<div className="col-md-6">
<fieldset name="SerialField" className="border p-4">
<legend className="scheduler-border"></legend>
<div className="container">
<p>SERIALS</p>
{loadedRecords ? (
<FirstList id='Serials' options={loadedRecords.firstRecordsList} />
) : (
<div>
<label>List is loading, please wait...</label>
{isLoading && <LoadingSpinner />}
</div>
)}
</div>
</fieldset>
</div>
<div className="col-md-6">
<fieldset name="SystemsField" className="border p-4">
<legend className="scheduler-border"></legend>
<div className="container">
<p>SYSTEMS</p>
{loadedRecords ? (
<SecondList options={loadedRecords.secondRecordsList} />
) : (
<div>
<label>List is loading, please wait...</label>
{isLoading && <LoadingSpinner />}
</div>
)}
</div>
</fieldset>
</div>
...
...
If anyone could guide me it'll be much appreciated.
Thanks in advance!
FirstList and SecondList are using internal state to show the selected values. Since a parent component should do the server request, it needs access to this data. This can be achieved by a variety of options:
Let the parent component (MyComponent) handle the state completely. FirstList and SecondList would need two props: One for the currently selected values and another for the onChange event. MyComponent needs to manage that state. For example:
const MyComponent = () => {
const [firstListSelected, setFirstListSelected] = useState();
const [secondListSelected, setSecondListSelected] = useState();
...
return (
...
<FirstList options={...} selected={firstListSelected} onChange={setFirstListSelected} />
...
<SecondList options={...} selected={secondListSelected} onChange={setSecondListSelected} />
...
)
Provide only the onChange event and keep track of it. This would be very similar to the first approach, but the lists would keep managing their state internally and only notify the parent when a change happens through onChange. I usually don't use that approach since it feels like I'm managing the state of something twice and I also need to know the initial state of the two *List components to make sure I am always synchronized properly.
Use a ref, call an imperative handle when needed from the parent. I wouldn't recommend this as it's usually not done like this and it's getting harder to share the state somewhere else than inside of the then heavily coupled components.
Use an external, shared state like Redux or Unstated. With global state, the current state can be reused anywhere in the Application and it might even exist when the user clicks away / unmounts MyComponent. Additional server requests wouldn't be necessary if the user navigated away and came back to the component. Anyways, using an external global state needs additional setup and usually feels "too much" and like a very high-end solution that is probably not necessary in this specific case.
By using option 1 or 2 there is a notification for the parent component when something changed. On every change a server request could be sent (might even be debounced). Or there could be a Submit button which has a callback that sends the saved state to the server.

React: How to display error on incorrect user login

I am a beginner in React and was implementing a function where on a button click in the render method, I go to a function foo. In that function, I am sending the username and password to a server.
If the username and password are correct, it returns a JSON object like
{"Result":1,"Cookie":"COOKIE!!!"}
Now, if the Result is 0, I want to print an error message saying invalid username and password and if it correct, I want to redirect it to a different component class. Can someone please help me?
I have tried reading a lot of posts but cannot do it. The issue is, I want to print something on the webpage but that can only be done in render. I am in a function foo when I get the cookie so I do not know how to print an error message.
I also do not know how to take it to a new page on successful authentication, ie, the cookie will be 1
import React from 'react';
import './style.scss';
import LoginImage from './LoginImage.png'
import Button from 'react-bootstrap/Button'
import Form from 'react-bootstrap/Form'
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom'
//import Logfailed from './Logfailed'
import Flood from './Flood'
class UserLogin extends React.Component {
constructor(props) {
super(props);
this.state = {userName:'', password:'', act:'l', flag:0};
this.handleChange1 = this.handleChange1.bind(this);
this.handleChange2 = this.handleChange2.bind(this);
this.handleClick = this.handleClick.bind(this);
}
async handleClick(e) {
const url = 'http://52.8.557.164/user'
const data = {username:this.state.userName, password:this.state.password, action:this.state.act};
try {
const response = await fetch(url,
{
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
});
const json = await response.json();
if(json['Result'] === 1) {
this.state.flag=1;
}
else {
this.state.flag=2;
}
console.log('Success', JSON.stringify(json));
console.log(json['Cookie']);
} catch (error) {
console.error('Error', error);
}
}
handleChange1(e) {
this.setState({userName: e.target.value})
}
handleChange2(e) {
this.setState({password: e.target.value})
}
render() {
const err = this.state.flag;
return (
<div className = 'outer-container' ref={this.props.containerRef}>
<div className = 'header'> Login </div>
<div className="content">
<div className="image">
<img src={LoginImage} />
</div>
<Form className = 'form'>
<Form.Group controlId="formBasicEmail" className = 'form-group'>
<Form.Label style={{marginTop: '90px'}}>Username</Form.Label>
<Form.Text className="text-muted" htmlFor="username"></Form.Text>
<input type="text" value = {this.state.userName} name="username" placeholder="username" onChange={this.handleChange1}/>
</Form.Group>
<Form.Group controlId="formBasicPassword" className = 'form-group'>
<Form.Label>Password</Form.Label>
<Form.Text className="text-muted" htmlFor="password"></Form.Text>
<input type="password" value = {this.state.password} name="password" placeholder="password" onChange={this.handleChange2} />
</Form.Group>
</Form>
</div>
<div className="footer">
<Button variant="outline-primary" size="lg" onClick={this.handleClick} className="btn" block>
Login
</Button>
</div>
</div>
);
}
}
export default UserLogin;
First off, in your handleClick() method, you are trying to directly modify your component's state instead of using React's built-in setState() method. This will prevent your UI from reacting to the update as React will have no way of knowing the state of your component has changed.
if(json['Result'] === 1) {
this.state.flag=1;
} else {
this.state.flag=2;
}
Instead, if you replace that code with:
if(json['Result'] === 1) {
this.setState({flag: 1});
} else {
this.setState({flag: 2});
}
Then this will cause your component to be re-rendered on each state change, and hence your app will become reactive.
Next, if you want to display a warning message to the user when the flag property is equal to 2, simply add a conditional expression in your JSX.
{ this.state.flag === 2 && <p>Your login credentials could not be verified, please try again.</p>}
What this means is that the <p> tag will only be evaluated (hence rendered) if the first part of the expression, i.e. this.state.flag === 2, evaluates to true.
If you want to redirect the user to another route upon successful login, simply import the Redirect component from react-router-dom and replace the return statement in your render() method with the following:
if (this.state.flag === 1) {
return <Redirect to='/route' />
}
// else
return (
<div className = 'outer-container' ref={this.props.containerRef}>
// ... add the rest of your JSX
)
For more info on this solution, please see this link.
So ultimately, your render() method could look something like the following:
render() {
if (this.state.flag === 1) {
return <Redirect to='/route' />
}
// else
return (
<div className = 'outer-container' ref={this.props.containerRef}>
<div className = 'header'> Login </div>
<div className="content">
<div className="image">
<img src={LoginImage} />
</div>
{ this.state.flag === 2 && <p>Your login credentials could not be verified, please try again.</p>}
// ... add the rest of your JSX
)
}
~~~ UPDATE ~~~
If the <Redirect> approach does not work, there is another method we can try as well (also detailed here). We can redirect the user programatically after your fetch request resolves, assuming we get the correct flag back from the API. So you can try:
if(json['Result'] === 1) {
this.setState({ flag: 1 });
this.props.history.push('/route')
}
In order to use this approach, however, you will also need to wrap your component with the withRouter HOF in your export statement. So instead of:
export default MyComponent
You will need to do:
export default withRouter(MyComponent)
Just make sure to import withRouter from react-router-dom.
If you take this approach, you will no longer need this block of code:
if (this.state.flag === 1) {
return <Redirect to='/route' />
}

another alternative to following approach without updating internal state

I have a usecase that there is a form which should be controlled one and should have its field be pre-populated so that user can edit the form. For this what i have done is
const mapStateToProps = createStructuredSelector({
myInfo: makeSelectMyInfo(),
errorResponse: makeSelectMyInfoErrorResponse()
});
const mapDispatchToPropes = dispatch => ({
loadMyInfo: () => dispatch(getMyInfo()),
updateMyInfo: (myInfo, token) => dispatch(updateMyInfo(myInfo, token))
});
class ConfirmPropertyByUser extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
user_info: {
contact_fname: "",
contact_lname: "",
agree_terms_condition: false,
}
};
}
componentDidMount() {
this.props.loadMyInfo();
}
componentWillReceiveProps(nextProps) {
if (nextProps.myInfo !== this.props.myInfo) {
console.log("object", Object.values(nextProps.myInfo));
this.setState(state => ({
user_info: {
...state.user_info,
contact_fname: nextProps.myInfo.contact_fname,
contact_lname: nextProps.myInfo.contact_lname,
}
}));
}
}
handleChange = e => {
this.setState({
user_info: { ...this.state.user_info, [e.target.name]: e.target.value }
});
};
handleUserTerms = e =>
this.setState({
user_info: {
...this.state.user_info,
agree_terms_condition: e.target.checked
}
});
handleSubmit = e => {
e.preventDefault();
this.props.updateMyInfo(this.state.user_info, this.props.match.params.id);
};
render() {
const { errorResponse } = this.props;
const { user_info } = this.state;
let message;
if (errorResponse && typeof errorResponse === "string") {
message = <Notification message={errorResponse} timeout={5000} />;
}
return (
<div className="container">
{message && message}
<div className="card card-lg">
<h1>Register</h1>
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label>First Name</label>
<input
type="text"
name="contact_fname"
className="form-control"
value={user_info && user_info.contact_fname}
onChange={this.handleChange}
/>
</div>
<div className="form-group">
<label>Last Name</label>
<input
type="text"
name="contact_lname"
className="form-control"
value={user_info && user_info.contact_lname}
onChange={this.handleChange}
/>
</div>
<div className="form-group">
<input
className="custom-control-input"
type="checkbox"
onChange={this.handleUserTerms}
/>
</div>
<button
className="btn btn-default btn-block btn-lg"
disabled={
!user_info.password || !user_info.agree_terms_condition
}
>
Submit Details
</button>
</fieldset>
</form>
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToPropes)(
ConfirmPropertyByUser
);
I am using redux and also updating the internal state. But I have heard somewhere that when using redux its unnecessary to update the internal state. How can i approach the following problem without updating the internal state? Can anyone help me on this, please?
What you're doing appears to be fine. Per the Redux FAQ, there's nothing wrong with using component state in a Redux app. For forms, it's very common to need to have both an "original" set of values and a "work-in-progress" copied set of values, and it's up to you whether the "WIP" values are stored in Redux or in a React component.
For what it's worth, I did show some examples of putting the "WIP" form state into Redux in my blog post Practical Redux, Part 8: Form Draft Data Management, which might be a useful reference. But, overall, your code here looks good - you're correctly copying props to state in the constructor and in componentWillReceiveProps, and the conceptual approach you're following is perfectly fine.
One small stylistic suggestion: I generally recommend that people use the object shorthand syntax for the mapDispatch argument. In your case, it would look like:
const actions = {loadMyInfo : getMyInfo, updateMyInfo : updateMyInfo};
// later
export default connect(mapState, actions)(ConfirmPropertyByUser);

How to get state / value from form component?

Consider having form component like:
export default class Form extends React.Component {
constructor (props) {
this.state = { email: '' }
this.onChange = this.onChange.bind(this)
}
onChange(event) {
this.setState({ email: event.target.value })
}
render () {
return (
<div>
<h2>{this.props.title}</h2>
<form className={cx('Form')} onSubmit={this.props.onSubmit}>
<input className={cx('Form-email')} type='email' placeholder='email' value={this.state.email} onChange={this.onChange} />
<input className={cx('Form-btn')} type='submit' value='sign up' />
</form>
</div>
)
}
}
I would then use this <Form onSubmit={this.someFunction} /> component elsewhere within my app, lets assume inside HomePage component. Inside that home page I would have this.someFunction that executes when form is summited, how can I pass form value / state to it?
Create a callback in your component that will call the function sent to Form with the state as parameter.
export default class Form extends React.Component {
constructor (props) {
this.state = { email: '' }
this.onChange = this.onChange.bind(this)
this.onSubmit = this.onSubmit.bind(this)
}
onChange(event) {
this.setState({ email: event.target.value })
}
onSubmit() {
this.props.onSubmit(this.state);
}
render () {
return (
<div>
<h2>{this.props.title}</h2>
<form className={cx('Form')} onSubmit={this.onSubmit}>
<input className={cx('Form-email')} type='email' placeholder='email' value={this.state.email} onChange={this.onChange} />
<input className={cx('Form-btn')} type='submit' value='sign up' />
</form>
</div>
)
}
}
What you're (essentially) looking to do is pass some data up the component chain (to a parent component). You could implement this with vanilla React, but I'm not going to advise you to do this.
If you try implementing some kind of state management yourself, unless your app is incredibly simple or you are an incredibly disciplined one-man-team, it's likely to get messy and unpredictable fast.
I advocate one way data flow. Data should flow one way through your app - down. I recommend you look at implementing a solution with Flux or Redux (Redux is my preference). These are both state containers that will propagate state throughout your app and enforce a set of conventions which you help you maintain structure to your data flow as your app grows.
I admit, you're adding to the learning curve by implementing a solution with these containers, but remember that React is only the view layer and it can't help you much with problems surrounding state management.
You could do this:
export default class Form extends React.Component {
constructor (props) {
this.state = { email: '' }
this.onChange = this.onChange.bind(this)
this.onSubmit = this.onSubmit.bind(this)
}
onChange(event) {
this.setState({ email: event.target.value })
}
// Wrap around this.props.onSubmit and add data to it.
onSubmit() {
this.props.onSubmit(this.state);
}
render () {
return (
<div>
<h2>{this.props.title}</h2>
<form className={cx('Form')} onSubmit={this.onSubmit}>
<input className={cx('Form-email')} type='email' placeholder='email' value={this.state.email} onChange={this.onChange} />
<input className={cx('Form-btn')} type='submit' value='sign up' />
</form>
</div>
)
}
}
Very similar to how you bound and use your onChange.

Working with the same ReactJS form component on the same page

I am fairly new to reactJS and I am wondering what's the best way to handle having the same form component on a single page. Please keep in mind that i am using flux and the component is talking to a store.
For example:
< SearchForm />
< SearchForm />
When I try to use form #1 input field, Form #2 gets the value from form #1 at the same time. i think the problem is coming from the store. the components are speaking to the same store and the store is updating all the components at once.
How can i handle this problem?
here is the code i have so far.
const SearchField = React.createClass({
propTypes: {
isSearchActivated: React.PropTypes.bool.isRequired,
},
_onChange() {
var previousHighlightedIndex = this.state.highlightedIndex;
this.setState(getStateFromStores(), function() {
if (previousHighlightedIndex == 0 &&
this.state.highlightedIndex == -1) {
this.refs.SearchBar.selectAll();
}
});
},
componentDidMount() {
if (window.location.pathname == "/" && !Modernizr.mq("screen only and (max-width: 768px)")) {
$(ReactDOM.findDOMNode(this.refs.SearchBar)).find("input").focus();
}
},
componentWillUnmount() {
SearchResultStore.removeChangeListener(this._onChange);
},
onChangeSearchString(e) {
SearchResultsUtils.search(e.target.value);
},
onBlur(e) {
var self = this;
var cb = function() {
if (!self.state.selectedResult && self.state.results.length) {
self.handleSelectedResult(0);
}
SearchResultsActions.disallowResultsDisplay();
};
if($(".search-bar").hasClass("active")) {
$(".search-bar.active").removeClass("active");
}
},
onFocus(e) {
$(ReactDOM.findDOMNode(this.refs.SearchBar)).closest(".search-bar").addClass("active");
},
handleSubmit() {
var self = this;
},
render() {
var className = "search-bar clearfix";
return (
<div className={className}>
<div className="search-bar-search">
<SearchBar
searchString={this.state.searchString}
onChange={this.onChangeSearchString}
onKeyDown={this.onKeyDownSearchString}
onFocus={this.onFocus}
onBlur={this.onBlur}
placeholder="Search Meds or Conditions"
ref="SearchBar" />
</div>
<SearchButton
handleSubmit={this.handleSubmit} />
</div>
);
},
});
module.exports = SearchField;
Thanks for the help in advance.
Definitely you can reuse your components multiple times across your application.
1.If you do not want to have any state:
In your situation you can provide form submit handler as callback prop. If you do not want to maintain any state on your search form.
e.g
For form 1
<Searchform submitHandler={searchForm1Handler}/>
For form 2
<Searchform submitHandler={searchForm2Handler}/>
and Inside your search form component
render(){
return (
<form id="searchform" onSubmit={this.props.submitHandler} role="form">
// other input and buttons
</form>
)
}
2. With states
With this approach each component will have its own separate states which will be private to them.
Here is sample component to illustrate this
import React ,{ Component } from 'react';
export default class SearchForm extends Component {
constructor(props){
super(props);
this.state = {
searchTerm : ''
};
this.submit = this.submit.bind(this);
this.changeSearchTerm = this.changeSearchTerm.bind(this);
}
submit(e){
e.preventDefault();
let searchTerm = this.state.searchTerm;
//Now perform some action based on search term you get
}
changeSearchTerm(e){
this.setState({searchTerm :e.target.value});
}
render(){
return(
<div>
<div className="row">
<div className="col-md-12">
<form role="form" className="form-horizontal" onSubmit={this.submit}>
<fieldset>
<div className="form-group">
<div className="col-sm-6">
<input id="st" type="text" placeholder="search term" onChange={this.changeSearchTerm} value={this.state.searchTerm} required autofocus/>
</div>
</div>
<div className="form-group">
<div className="col-xs-12 text-center">
<button className="btn">
Search
</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
)
}
}
Now to use them
For form 1
<Searchform/>
For form 2
<Searchform/>
React has stateful components:
a component can maintain internal state data (accessed via this.state)
which allows you to have <SearchForm />#1 with value1, and <SearchForm />#2 with value2 stored in this.state
To build a form, check this: https://facebook.github.io/react/docs/forms.html
It looks like your store only saves one state - which includes search value - for one form.
If you render this form twice, then both forms retrieve the same state on each change. So they are an exact copy of each other by design. If one form changes, both forms notice the change, and both forms will retrieve the exact same state from the store.
To fix this, I would suggest:
change the store so that it can save a state for multiple forms, each with its own ID
pass this ID to each form as a prop, e.g. <SearchForm formID='form1'/> <SearchForm formID='form2'/>
pass the formID down as a prop to all children of the form, such as <SearchField>
inside the <SearchField>, make sure your component only renders state from its own formID. You probably need to update the getStateFromStores() method itself. Or inside <SearchField> filter out relevant stuff first, before passing it to setState().
when your component informs the store that something has changed (I guess this is what SearchResultsUtils.search(e.target.value); does), then you need to pass the formID as well. So the store will know which form to update.
PS: I think your code is missing some lines in componentDidMount(): you have code to remove the listener to store changes, but your code to add the listener is missing. The code to invoke the _onChange() method is missing from your component.

Categories