Using the component Field of redux-form, I am trying to display the length of the string written in the input. But it seems like the handleChange function is never fired by the onChange.
Here is the component :
import React from 'react';
import {Field} from 'redux-form';
var max_chars = 160;
var LimitedCharsField = React.createClass({
getInitialState: function() {
return {
chars_left: max_chars
};
},
handleChange(event) {
var input = event.target.value;
this.setState({
chars_left: max_chars - input.length
});
},
render: function() {
return (
<div>
<Field name="short_description" id="short_description" component={this.props.renderSmallTextField} type="text"
label="Qualification du site" onChange={this.handleChange.bind(this)} maxLength={max_chars}/>
<br/>
<small id="short_description_countdown" >{this.state.chars_left} caractères restants</small>
</div>
);
}
});
export default LimitedCharsField;
And I am calling in a parent component which is the main form:
<LimitedCharsField renderSmallTextField={renderSmallTextField}/>
The component does appear in the form but the handleChange dosen't fire at all.
What should be done in order to get the function handleChange fired?
Many thanks.
Edit: Below the renderSmallTextField
const renderSmallTextField = ({input, label, meta:{touched, error}, ...custom}) => (
<TextField
hintText={label}
errorText={touched && error}
{...input}
{...custom}
/>
);
I would imagine this is because the onChange prop isn't passed around correctly. Couldn't say for sure without seeing the code for the renderSmallTextField component though. Try setting component="input" as a simple test. If the handleChange method starts firing you have found your problem.
Related
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;
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.
I'm trying to get input tags' HTML inner values when submitting a form.
private handleSubmit = (event: any) => {
event.preventDefault();
console.log(event.currentTarget);
};
When the submit the form, it calls the function handleSubmit and it console logs the following.
Under the form tag, the first div has username value and the second div has password value. I would like to acess the two values. I think I should use DOM to do that, but can't be sure if I'm going for the right direction cuz I found some postings saying using DOM is not recommended.
Can anyone explain how I can acheive this?
Ideally you should update your state as the user enters information, and then access the data from the state. This would also allow you to run any validation on the data prior to it going into the state if you'd like.
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
username: null,
password: null
}
this.submitForm = this.submitForm.bind(this);
this.updateState = this.updateState.bind(this);
}
updateState (e) {
this.setState({[e.target.name]: e.target.value})
}
submitForm (e) {
e.preventDefault();
console.log(this.state);
}
render() {
return (
<div className="App">
<form onSubmit={this.submitForm}>
<input type="text" name="username" placeholder="username" onChange={this.updateState} /><br />
<input type="password" name="password" placeholder="password" onChange={this.updateState} /><br />
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default App;
The above code does the following:
Stores default values for username and password. While this isn't required, it makes the code more readable
binds this to functions that need to access state
Uses an updateState() function that is called onChange of the inputs
updateState uses the name attribute of the input as the key for the state
You could customize the updateState() function to do some validation, before saving to state if you'd like.
Excessive Rendering
ReactJS is pretty smart to no re-render the REAL DOM if your render() method doesn't actually rely on the state values that were updated; however, if you'd like to prevent ReactJS from even creating the Virtual DOM and comparing, you could utilize the shouldComponentUpdate() lifecycle hook.
In the particular example above, since render doesn't rely on ANYTHING in state, you could simply add the following method:
shouldComponentUpdate(prevState, nextState) {
return false;
}
That will prevent the render method from EVER re-rendering, which is most likely not going to work in a normal component, thus you could do something like this instead, only re-rendering on values you care about.
shouldComponentUpdate(prevState, nextState) {
if (nextState.email !== prevState.email) {
return true
}
return false;
}
Demo
https://repl.it/#AnonymousSB/SO53689072
If you want to use the DOM, once you have the form element (event.currentTarget in your case), you can use the .elements property to access a list of child inputs and buttons.
Alternatively, you can use React refs to keep track of the underlying HTML element when it's rendered.
render() {
return ... <input ref={(e) => this._name = e; } ....> ... ;
}
handleSubmit(e) {
var name = this._name ? this._name.value : '';
....
}
This can achieve what you want to
class Login extends Component{
state={
username:"",
password:""
}
onChange = (event)=>{
event.preventDefault()
this.setState({
[event.target.name]: event.target.value})
}
onSubmit = (event)=>{
event.preventDefault()
// submit whatever is in state from here
console.log(this.state)
}
render(){
return(<div>
<form onSubmit={handleSubmit}>
<input type="text" name="username" onChange={this.onChange} /><br />
<input type="password" name="password" onChange={this.onChange} /><br />
<button type="submit">Submit</button>
</form>
</div>)
}
}
I'm trying to implement a very simple use case, a UI feature, where:
There is a label with some content in it
If clicked, a text input replaces it with the content of label available
User can edit the content
When enter is pressed, the input hides and label is back with updated content
I could get finally all correct (in fact with a MongoBD backend, redux, etc.), and the only thing I couldn't ever do (paying a complete day in googling and reading S.O.F similar posts) was this:
When my text input appears, I can't transfer focus to it! First I tired this way:
<div className={((this.state.toggleWordEdit) ? '' : 'hidden')}>
<input id={this.props.word._id} className="form-control"
ref="updateTheWord"
defaultValue={this.state.word}
onChange={this.handleChange}
onKeyPress={this.handleSubmit}
autoFocus={this.state.toggleWordEdit}/></div>
<div className={((this.state.toggleWordEdit) ? 'hidden' : '')}>
<h3 onClick={this.updateWord}>
{this.state.word}</h3>
</div>
but autoFocus sure didn't work (I "guess" because the form is rendered, but in hidden state, making autoFocus useless).
Next I tried in my this.updateWor, many of suggestions I found on google and S.O.F.:
this.refs.updateTheWord.focus();
which together with similar suggestions all didn't work. Also I tried to fool React just to see if at all I can do something! I used real DOM:
const x = document.getElementById(this.props.word._id);
x.focus();
and it didn't work either. One thing I even could not understand to put into word is a suggestion like this:
having ref as a method (I "guess")
I didn't even try it because I have multiples of these components and I need ref to further get value of, per component, and I couldn't imagine if my ref is not named, how I could get the value of!
So could you please give an idea, helping me to understand that in case I'm not using a Form (because I need a single input box replacing a label) how I could set its focus when it's CSS (Bootstrap) class is losing 'hidden' please?
The way you have used refs is not the most preferred way or else its not the best practice anymore . try some thing like this
class MyClass extends React.Component {
constructor(props) {
super(props);
this.focus = this.focus.bind(this);
}
focus() {
this.textInput.current.focus();
}
render() {
return (
<div>
<input
type="text"
ref={(input) => { this.textInput = input; }} />
<input
type="button"
value="Set Focus"
onClick={this.focus}
/>
</div>
);
}
}
Update
From React 16.3 upwards you can use the React.createRef() API
class MyClass extends React.Component {
constructor(props) {
super(props);
// create a ref to store the textInput DOM element
this.textInput = React.createRef();
this.focus = this.focus.bind(this);
}
focus() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}
render() {
// tell React that we want to associate the <input> ref
// with the `textInput` that we created in the constructor
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Set Focus"
onClick={this.focus}
/>
</div>
);
}
}
From React 18.xx upwards you can use the useRef Hook
import React, { useRef } from "react";
export const Form = () => {
const inputRef = useRef(null);
const focus = () => {
inputRef.current.focus();
};
return (
<div>
<input type="text" ref={inputRef} />
<input type="button" value="Set Focus" onClick={focus} />
</div>
);
};
Just add autofocus attribute to the input. (of course in JSX it is autoFocus)
<input autoFocus ...
useFocus hook
// General Focus Hook
const useFocus = (initialFocus = false, id = "") => {
const [focus, setFocus] = useState(initialFocus)
const setFocusWithTrueDefault = (param) => setFocus(isBoolean(param)? param : true)
return ([
setFocusWithTrueDefault, {
autoFocus: focus,
key: `${id}${focus}`,
onFocus: () => setFocus(true),
onBlur: () => setFocus(false),
},
])
}
const FocusDemo = () => {
const [labelStr, setLabelStr] = useState("Your initial Value")
const [setFocus, focusProps] = useFocus(true)
return (
<> {/* React.Fragment */}
<input
onChange={(e)=> setLabelStr(e.target.value)}
value={labelStr}
{...focusProps}
/>
<h3 onClick={setFocus}>{labelStr}</h3>
</>
)
}
For a more complete demo click here.
In addition to the previous answers, I've added setTimeout to make it work
handleClick() {
if (this.searchInput) {
setTimeout(() => {
this.searchInput.focus();
}, 100);
}
}
where searchInput is the jsx ref of the input
<input
type="text"
name="searchText"
ref={(input) => { this.searchInput = input; }}
placeholder="Search" />
and the handleClick() is an onClick handler to any element
#BenCarp's answer in typescript
Pass the inputRef to an input and just call setFocus to set the focus to it.
export const useInputFocus = (): [MutableRefObject<HTMLInputElement | undefined>, () => void] => {
const inputRef = useRef<HTMLInputElement>();
const setFocus = (): void => {
const currentEl = inputRef.current;
if (currentEl) {
currentEl.focus();
}
};
return [inputRef, setFocus];
};
Use componentDidUpdate method to every time update the component
componentDidUpdate(prevProps, prevState) {
this.input.focus();
}
You can use "useRef" hook and make a reference to your input control, then use your reference.current.focus()
I am using react bootstrap and this framework provides some nice FormControls.
But I would like to make the Input field that is generated within the FormControls to have a prop of readonly="readonly". This way, this field looks the same as my other FormControls, but does not give a keyboard input on IOS.
In my case, the input will be provided by a calendar picker which will be triggered by an popover.
Does anyone know how to give FormControl the parameter readonly="readonly", so that the generated Input field in the browser will have the prop readonly="readonly"?
Many thnx for the answers!
It doesn't look like a problem with react-bootstrap, but rather with react itself.
React is not transferring the 'readonly' prop to the generated (real) DOM element:
React-bootstrap create the following react virtual dom input:
Yet, react generated the following real DOM element, omitting the readonly attribute:
Maybe using 'disabled' could help in your case:
<FormControl
disabled
type="text"
placeholder="Enter text"
onChange={this.handleChange}
/>
For differences between readonly & disbabled see here:
https://stackoverflow.com/a/7730719/1415921
I have created an issue in React's github repo: #6783
UPDATE
After getting an answer in the above issue. You need to write it with camelcase: readOnly.
So it should be:
<FormControl
readOnly
type="text"
placeholder="Enter text"
onChange={this.handleChange}
/>
Old problem, new approach: Take advantage of onChange event to control if you'll call handleChange event or not. I defined editForm as a props value controlled by buttons, to see if i'm in view or edit mode.
Example:
<TextField
name="id"
label="ID
value={entityState.entity.Id || ""}
onChange={(a) => (props.formEdit ? handleChange(a) : "")}
/>
On the basis of values this attribut will be readOnly={!!value} to make input field disable to edit
class Input extends React.Component {
render () {
const { defaultValue, value, onChange } = this.props
const nothing = () => {}
return (
<input
type='text'
defaultValue={defaultValue}
value={value ? value.toUpperCase() : undefined}
readOnly={!!value}
onChange={value ? nothing : onChange}
/>
)
}
}
class App extends React.Component {
constructor () {
super ()
this.state = {
value: 'arr'
}
}
handleChange (e) {
const { target: { value }} = event
this.setState({ value })
}
render () {
const { value } = this.state
return (
<div>
<Input
onChange={this.handleChange.bind(this)}
defaultValue={'patata'}
/>
<Input
value={value}
/>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('arr'))