Disable submit button if field is empty - reactjs

I have a React app using hooks and trying to figure out how to keep a submit button disabled if the search field is empty.
Assuming a regular form field with submit button, how can I set a state hook that keeps the search button disabled until the user inputs text. I assume there should be an onChange function that probably updates the state on input change, but not exactly sure of the implementation.
const [disabled, isDisabled] = useState(true);
<input type="text" id="q" name="q" placeholder="Search.." name="search">
<button type="submit"><i class="fa fa-search"></i></button>

If you want to disable a button when an input string is empty, then the only state you need is the value of the input string.
const [inputVal, setInputVal] = useState('')
// ...
<input value={inputVal} onChange={e => setInputVal(e.target.value)} />
// ...
<button disabled={!inputVal}> /* ... */ </button>
Here we connect the input component to the state value. This is called a controlled component, because its value is controlled from by an external source (the state value) as opposed to an uncontrolled component, which means the input element holds it's own internal state (the default way inputs work if you don't set their value prop directly.
When the input component receives input (such as someone typing a character) the onChange prop is called. All we do then is take the new value of the input element (e.target.value) and use it to set the state.
If you can derive state from other state, then you shouldn't be storing it in state. Having a state variable called disabled only makes things more complex. The general idea is to use as little state as possible, and compute as much as you can from that state.

Please check this complete example where I used class component and use a disable property under state object. When you write something on textbox, the disable property will be set as false.
import React from "react";
export default class Login extends React.Component {
constructor(props) {
super(props);
this.state = {disable: true};
}
handleChange = (event) => {
this.setState({disable: event.target.value === ''})
};
render() {
return (
<div>
<div>
Name: <input onChange={this.handleChange}/> <br/>
<button disabled={this.state.disable} >Login</button>
</div>
</div>
);
}
}
here is the same example of functional component
import React, {useState} from "react";
export default function Login() {
const [disable, setDisable] = useState(true);
function handleChange(event) {
setDisable(event.target.value === '');
}
return (
<div>
<div>
Name: <input onChange={handleChange}/> <br/>
<button disabled={disable}>Login</button>
</div>
</div>
);
}

Related

how to access value of a input tag in a component through another component in react

I'm very new to react and js basically.
I want to access the value of input in Input so that I can give the person a hint while he is typing (for example he types New and He sees New york as like a placeholder for that input form).
Any help would be appreciated.
Note that I can only change the app.js file
App.js:
import React from 'react';
import Input from './Input.js';
import Cities from './cities.json';
function App() {
return <div>
<Input
/>
</div>
}
export default App;
input.js:
import React from 'react';
const Input = ({handleChange, hint}) => {
return (
<div className="input">
<label htmlFor="input" data-testid="hint">
{hint}
</label>
<input
data-testid="input"
type="text"
id="input"
onChange={handleChange}
/>
</div>
);
};
export default Input;
There are many ways to accomplish this. The simplest one would be to set up state in your App component to keep track of the input's value, and pass the input component a handleChange function that updates the state:
function App() {
// set up state to keep track of the input value
const [inputValue, setInputValue] = React.useState('');
// declare a function to update the state when the input changes
const handleChange = event => setInputValue(event.target.value);
// pass the value and the change handler to the input
return <div>
<Input
value={inputValue}
handleChange={handleChange}
/>
</div>
}
With this setup, the App component knows (and is in charge of) the input's value and can do other things with it.
The event argument passed to the handler will contain all the information you need about the input via its target property, including the new value.
Read up on useState here and controlled inputs here.

Able to type inside input text field without onChange method

I am learning React and below one is sample code I am trying out. I am able to render this component and able to type in characters in input field without any handleChange() method ? Is this fine ? because what I know is, in order to make input fields available for typing, we need to add handleChange method something like below
handleChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
import React from "react";
class StudentForm extends React.Component {
constructor() {
super();
}
render() {
return (
<div>
<form>
<h1>Student Form</h1>
<input type="text" name="firstname"></input>
</form>
</div>
);
}
}
export default StudentForm;
handleChange is for setting the state value.
without onChange handler you can type in but your value is not getting stored anywhere.
For example, if you try to access your state this.state.firstname you will always get undefined.
You should have controlled component. Which is a simple and cleaner way access and store value in state.
To make your component controlled, you should have value and onChange props on input,
<input type="text" name="firstname" value={this.state.firstname} onChange={this.handleChange.bind(this)}></input>
Yes, consider the following
<input type="text" name="firstname" />
This is an uncrontrolled input which means React doesn't now about it's value nor how to change it. To make an input controlled you need to explicitly specify the value and onChange properties to bind this input to React's state
const Input = () =>{
const [value, setValue] = useState('')
return <input value={value} onChange={e => setValue(e.target.value)} />
}
Now the input is fully controlled by React, which provides the value it must print and a way to change it
After making below changes, I made this input element as controlled element and now I am not able to type in anything without using onChange handler.
import React from "react";
class StudentForm extends React.Component {
constructor() {
super();
this.state = {
firstname: ""
};
}
render() {
return (
<div>
<form>
<h1>Student Form</h1>
<input
type="text"
name="firstname"
value={this.state.firstname}
></input>
</form>
</div>
);
}
}
export default StudentForm;

Spy function in react test is not firing

I have a controlled component with a form that consists of two radio buttons and a text input. I have a function that is called for onChange event of the text input and have written a test that fires a change event. I expect that the spy function should be called once but the test always fails.
Test
test('Update function is called when text entered into searchbox', () => {
const spy = jest.fn();
const {getByTestId} = render(<SearchDropdown handleChange={spy}/>);
expect(getByTestId('searchText').value).toBe("");
fireEvent.change(getByTestId('searchText'), { target: { value: "23" } });
expect(getByTestId('searchText').value).toBe("23");
expect(spy).toHaveBeenCalledTimes(1);
});
Component - does not take in any props
import React, { Component } from 'react'
import { faSearch } from '#fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import SearchResults from './SearchResults';
export default class SearchDropdown extends Component {
constructor(props){
super(props)
this.state = {
type: 'Spec',
number: '',
}
}
handleChange = (event) => {
let name = event.target.name;
let target = event.target.value;
this.setState({
[name]: target}
);
}
handleSearch = (event) => {
event.preventDefault();
if (this.state.number !== "") {
console.log("Make request to API");
}
}
render() {
return (
<div data-testid="searchMenu">
<div className="list-group dropdown-menu dropdown-menu-right mr-5 text-center">
<h5><FontAwesomeIcon icon={faSearch}/> Quick Search</h5>
<form className="m-2">
<div className="form-group" onChange={this.handleChange}>
<input type="radio" value="Spec" name="type" defaultChecked /> Specification
<input type="radio" value="Claim" name="type" className="ml-2"/> Claim
</div>
<div className="form-group">
<label>Search Number</label>
<input type="text" data-testid="searchText" onChange={this.handleChange} value={this.state.number} name="number" className="form-control"/>
</div>
<SearchResults />
</form>
<div className="panel panel-default">
</div>
<button className="bg-transparent border-0 link">Advanced Search</button>
</div>
</div>
)
}
}
Error Message
expect(jest.fn()).toHaveBeenCalledTimes(1)
Expected mock function to have been called one time, but it was called zero times.
fireEvent.change(getByTestId('searchText'), { target: { value: "23" } });
expect(getByTestId('searchText').value).toBe("23");
expect(spy).toHaveBeenCalledTimes(1);
^
});
test('Clicking search brings up results modal', () => {
at Object.toHaveBeenCalledTimes (src/Tests/SearchDropdown.test.js:18:17)
Am I missing something obvious?
I'm fairly new to React and React Testing Library, but there are a couple of things I noted:
In your test you're passing a spy as the value for prop handleChange to your component, which it doesn't have; just as you state: "does not take in any props".
Your component defines handleChange function which you use an event handler, it doens't get it from a prop.
Your handleChange function should be passed only as prop to input elements, not a div.
You should have one handler per each input instead of calling the same handler for all of them.
That said, in order for your test to provide any value, you should change your SearchDropdown component so that it receives the handlers from a parent or you test that firing click/change events on the inputs actually updates the view as expected.
I don't think there's any value in testing if an internal event handler function has been called.
You're using React Testing Library in your test, but not in the way it's intended to.
From React Testing Library Intro:
The utilities this library provides facilitate querying the DOM in the
same way the user would. Finding for elements by their label text
(just like a user would), finding links and buttons from their text
(like a user would). It also exposes a recommended way to find
elements by a data-testid as an "escape hatch" for elements where the
text content and label do not make sense or is not practical.

React Redux - How to store form state so that it can be populated again when user returns to page

I have a search form that loads results. Everything gets updated in the Redux state except for the values selected in the form fields.
Lets say the search below shows 3 results. User clicks on result #2 and does not like it. The click the back button but the form state is cleared. How can I avoid this and make sure the form state stays ?
I am storing the results in Redux and able to load that but not sure how to handle state. I dont want to use packages such as redux-form if possible because this is the only place I need to store form state.
The state does not update when I change the selections. This is probably a slight change in code for an expert but I am beginner and this is my first React-Redux app.
Also, when I navigate away from the page, the state that was last stored is not retained. This is probably because of the local state formValues: {} that I am initializing but without this, there are errors. I tried to delete local state and just use props but it is giving me errors on the select box at this line - value={this.state.formValues['gender']}
Here is the overall code of the component:
import React, { Component } from 'react'
import { get } from 'axios'
import { connect } from 'react-redux'
import { FadeIn } from 'animate-components'
import {
getSearchResults,
setSearchedOnce,
setPageNum,
setFormValues,
} from '../../actions/searchresults'
import SearchResult from './Result/searchResult'
import SearchNoResult from './Result/searchNoResult'
class SearchAdvanced extends Component {
constructor(props) {
super(props)
this.state = { searchedOnce: false, users: [] }
}
handleChange(event) {
event.preventDefault()
this.props.setFormValues(this.props.formValues)
}
handleSubmit(event) {
event.preventDefault()
this.props.setSearchedOnce(true)
const apiSearchURL = `/api/search/religion/${this.props.formValues.religion}/gender/${this.props.formValues.gender}`
get(apiSearchURL, { maxContentLength: 400 })
.then((searchResults) => {
this.props.getSearchResults(searchResults)
})
}
loadMoreClick() {
this.props.setPageNum(this.props.pageNo + 1)
}
render() {
const { users } = this.props
let mapPageNo = this.props.pageNo
let map_usersList = users.data && users.data.slice(0, mapPageNo * 2).map((userlist => (
<SearchResult key={userlist.id} {...userlist} />
)))
let mapSearchedOnce = this.props.searchedOnce
return (
<div>
<FadeIn duration="300ms">
<div className="mid_rig_inner">
<div className="mid_inner">
<ul>
{ mapSearchedOnce
? map_usersList
: <SearchNoResult/>
}
{
mapSearchedOnce ?
(mapPageNo * 2 >= 3)
?
<div className="text-center my3 text-danger">
No more profiles. Try to modify search criteria.
</div> :
<div className="text-center my3">
<button type="button" className="btn btn-primary" onClick={this.loadMoreClick.bind(this)}>
Load More
</button>
</div>
: ''
}
</ul>
</div>
<div className="rig_inner">
<div className="my-4">
<div className="recomm">
<div className="recomm_top">
<span>Search</span>
</div>
</div>
<div className="search_advan_box">
<form onSubmit={this.handleSubmit.bind(this)}>
<select
name="religion"
className="mb-2"
value={this.props.formValues.religion}
onChange={this.handleChange.bind(this)}
>
<option value="" disabled="">
Select Religion
</option>
<option value="Any">Any Religion</option>
<option>Christian</option>
<option>Hindu</option>
<option>Muslim</option>
<option>Jain</option>
<option>Buddhist</option>
<option>Sikh</option>
<option>Parsi</option>
<option>Jewish</option>
<option>Spiritual</option>
<option>No Religion</option>
<option>Other</option>
</select>
<select
name="gender"
className="mb-2"
value={this.props.formValues.gender}
onChange={this.handleChange.bind(this)}
>
<option value="" disabled="">
Select Gender
</option>
<option>Male</option>
<option>Female</option>
<option>Other</option>
</select>
<input
type="submit"
className="my-4 btn btn-primary p2"
value={mapSearchedOnce ? "Refine Results":"Search Profiles"}
/>
</form>
</div>
</div>
</div>
</div>
</FadeIn>
</div>
)
}
}
const mapStateToProps = state => ({
users: state.Result.users,
searchedOnce: state.Result.searchedOnce,
pageNo: state.Result.pageNo,
formValues: state.Result.formValues
})
const mapDispatchToProps = {
getSearchResults,
setSearchedOnce,
setPageNum,
setFormValues
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchAdvanced)
The state does not update when I change the selections
This is because you are not dispatching a Redux action in your handleChange method, only in your handleSubmit method. Simply dispatching the appropriate action in handleChange will resolve this issue.
when I navigate away from the page, the state that was last stored is not retained
This is because the values from before (before you navigate away) will only be kept in the Redux store, while the form fields are populated from the local state of the SearchAdvanced component.
To solve this one well, you should get rid of your local state entirely. Instead only use the Redux store. Keeping both intact is unnecessary and breaks the 'Single Source of Truth' that Redux is meant for. I recommend you do away with the local state and only update the Redux state and then pass values to the form inputs from the Redux store (props for the component).
Regarding your note that you tried this, but get errors: you need to change anything like:
<input value={ this.state.formValues.someValue }/>
to
<input value={ this.props.formValues.someValue }/>
where formValues comes from the Redux store.
Update
Problem now is the line
this.props.setFormValues(this.props.formValues)
in handleChange. You're using the old formValues to update, so the store never actually updates. I think you want something like:
handleChange(event) {
event.preventDefault();
const { name, value, type, checked } = event.target;
this.props.setFormValues({
...this.props.formValues,
[name]: type === 'checkbox' ? checked : value
});
}
So that you are updating the store's formValues with the input from the user. The ternary operator is necessary for checkbox inputs since the value of a checked checkbox is 'on' rather than true, but the checked attribute is true if checked.
Extra
It seems that you pass the dispatch method to the SearchAdvanced via props from the parent. You can (and should) do this more cleanly by using the second argument of connect, which is mapDispatchToProps. Here's an example:
const mapDispatchToProps = {
getSearchResults,
setSearchedOnce,
setPageNum,
setFormValues
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchAdvanced);
Then you can just call any of these as methods that already have a dispatch bound to them. So
this.props.dispatch(setSearchedOnce(true))
becomes
this.props.setSearchedOnce(true)
and you can remove the passing of the dispatch from the parent component.
Docs on connect: https://github.com/reduxjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options

Posting changed form field values in React

I have a form which is passed a value through props, and submits to an endpoint to update a users information. However, I'm unable to send an edited value of the text input field, as its state needs to be managed and updated when a user changes its value, but having trouble setting/updating the state of the input when the user changes the value, allowing a different value to be posted.
class DisplayNameModal extends React.Component {
constructor (props){
super(props)
this.state = {
displayName: this.props.displayName,
email: this.props.email.split('#')[0]
}
this.updateDisplayName = this.updateDisplayName.bind(this)
}
updateDisplayName () {
const email = this.props.email
const displayName = this.state.displayName
const user = {
email,
displayName
}
superagent
.put('/api/user')
.send({user})
.then(this.closeModal)
}
handleDisplayNameChange = e => this.setState({ displayName: e.target.value })
render (props) {
const {contentStrings} = this.props.config
return (
<div>
{ !this.props.displayNameModalActive &&
<div className='display-name-container' style={{ backgroundImage: `url(${this.props.bgImgUrl})` }}>
<div className='display-name-content'>
<h2 className='heading'>{contentStrings.displayNameModal.heading}</h2>
<p>{contentStrings.displayNameModal.subHeading}</p>
<input type="text"
defaultValue={this.state.displayName}
onChange={this.handleDisplayNameChange}
minLength="3"
maxLength="15"/>
<button
type='submit'
onClick={this.updateDisplayName}
className='btn btn--primary btn--md'>
<span>{contentStrings.displayNameModal.button}</span>
</button>
<p className='cancel'>{contentStrings.displayNameModal.cancel}</p>
</div>
</div>
}
</div>
)
}
}
export default DisplayNameModal
I think you need an onChange on your <input /> to update displayName on component state.
handleDisplayNameChange = e => this.setState({ displayName: e.target.value });
<input type="text"
value={this.state.displayName}
minLength="3"
maxLength="15"
onChange={this.handleDisplayNameChange}
/>
and instead of defaultValue, use value to make it a controlled input
So then in your updateDisplayName, you would use this.state.displayName instead of this.props.displayName. The prop is just being used to set the initial component state value, allowing it to be edited.
onChange event, call a method, and inside it use this.setState to set the changed text to state as you type in Input box.
On submit, use the updated State value to pass it to the API.
In this way, you can maintain updated value in local state.
You are using uncontrolled input element, ie React doesnt know, wats going on with ur input element.
In order, for React to know about it, it hould be made controlled component.
This can be done by connecting it value to the the state of the component,
Check example below.
This means, that at any time, react will know, wat is the value of the input element
Controlled Components
In HTML, form elements such as , , and typically maintain their own state and update it based on user input. In React, mutable state is typically kept in the state property of components, and only updated with setState().
We can combine the two by making the React state be the “single source of truth”. Then the React component that renders a form also controls what happens in that form on subsequent user input. An input form element whose value is controlled by React in this way is called a “controlled component”.
For example, if we want to make the previous example log the name when it is submitted, we can write the form as a controlled component:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Since the value attribute is set on our form element, the displayed value will always be this.state.value, making the React state the source of truth. Since handleChange runs on every keystroke to update the React state, the displayed value will update as the user types.
With a controlled component, every state mutation will have an associated handler function. This makes it straightforward to modify or validate user input. For example, if we wanted to enforce that names are written with all uppercase letters, we could write handleChange as:
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}

Resources