Why can't update a state when use onKeyPress [duplicate] - javascript

This question already has answers here:
How to handle the `onKeyPress` event in ReactJS?
(12 answers)
Closed 4 years ago.
Please considering this follow code, I can't update inputVal when I using a Keypress event handler.
import React, { Component, Fragment } from 'react';
import List from './List';
import './ListTodos.css';
class Todos extends Component {
constructor(props) {
super(props);
this.state = {
inputVal: null
}
this.refInput = null
this._handleChange = this._handleChange.bind(this)
}
_handleChange(pEvt) {
if (pEvt.keyCode === "13") {
this.setState({
inputVal: this.refInput.value
})
console.log(this.state.refInput)
}
}
render() {
const { text } = this.props;
return (
<Fragment>
<div className="form">
<input ref={input => {this.refInput = input}} onKeyDown={pEvt => this._handleChange(pEvt)} className="form__input" placeholder={ text } />
<div>
<List TxtVal={this.state.inputVal} />
</div>
</div>
</Fragment>
)
}
}
export default Todos;

I really dont like using on onKeyDown. Instead you can use onChange which i think its better.
So Basically you need can do this too.
class Todos extends Component {
constructor(props) {
super(props);
this.state = {
inputVal: null
}
this._handleChange = this._handleChange.bind(this)
}
_handleChange(e) {
if (e.keyCode === "13") {
this.setState({
inputVal: e.target.value
})
console.log(e.target.value)
}
}
render() {
const { text } = this.props;
return (
<Fragment>
<div className="form">
<input name="todo" onChange={(e) => this._handleChange(e)} className="form__input" placeholder={ text } />
<div>
<List TxtVal={this.state.inputVal} />
</div>
</div>
</Fragment>
)
}
}
export default Todos;

Use pEvt.target.value instead of this.refInput.value
_handleChange(pEvt) {
if (pEvt.keyCode === "13") {
this.setState({
inputVal: pEvt.target.value
});
console.log(this.state.inputVal);
}
}

You're actually using the KeyDown event in your code instead of KeyPress as you asserted. It looks like you're just trying to get the value of the input element right?
I'd create a handler for the onchange event instead for the input. You're just trying to get the value of the input. And you wouldn't even need your ref.
_handleChange(e) {
this.setState({
inputVal: e.target.value
});
}
constructor() {
// wire up your event handler
this._handleChange = this._handleChange.bind(this);
}
...
<input onChange={this._handleChange} className="form__input" placeholder={ text } />

Related

How to insert text into a list that is typed in an input

import React, { Component, createElement } from "react";
export default class TodoList extends Component {
state = {
todo: [],
inputValue: "",
};
addTodo = () => {
this.setState({ todo: [...this.state.todo, this.state.inputValue] });
// I want to insert separate paragraph tags (todos from this.state.todo) into the list element here
};
handleKeyDown = (e) => {
if (e.keyCode === 13) {
if (this.state.inputValue === "") return;
this.addTodo();
}
};
handleChange = (e) => {
this.setState({ inputValue: e.target.value });
};
render() {
return (
<div className="list"></div>
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
);
}
}
I am creating a Todo List where the user types into an input, and the todo is then inserted into the div with class list element. I'm new to React so I don't know the best way I should go about doing this.
Any help is appreciated, thanks!
You can map the array, inside the .list div, and render each todo item, by wrapping it in p tag. I have added a button element, to handle the addTodo() function.
Also, you may want to move the .list div, below the input.
import React, { Component, createElement } from "react";
export default class TodoList extends Component {
state = {
todo: [],
inputValue: ""
};
addTodo = () => {
this.setState({ todo: [...this.state.todo, this.state.inputValue] });
// I want to insert separate paragraph tags (todos from this.state.todo) into the list element here
};
handleKeyDown = (e) => {
if (e.keyCode === 13) {
if (this.state.inputValue === "") return;
this.addTodo();
}
};
handleChange = (e) => {
this.setState({ inputValue: e.target.value });
};
render() {
return (
<div>
<div className="list">
{this.state.todo.map((todo) => {
return <p>{todo}</p>;
})}
</div>
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
<button onClick={this.addTodo}>Add Todo</button>
</div>
);
}
}
Codesandbox link - https://codesandbox.io/s/busy-pascal-txh55?file=/src/App.js
class TodoList extends Component {
state = {
todo: [],
inputValue: "",
};
addTodo = () => {
// I want to insert separate paragraph tags (todos from this.state.todo) into the list element here
// Hint: when you want to add a todo, you simply set input value to empty here.
this.setState({
todo: [...this.state.todo, this.state.inputValue],
inputValue: "",
});
};
handleKeyDown = (e) => {
if (e.keyCode === 13) {
if (this.state.inputValue === "") return;
this.addTodo();
}
};
handleChange = (e) => {
// Hint: I prefer adding "...this.state" every time before updating.
this.setState({ ...this.state, inputValue: e.target.value });
};
render() {
return (
<>
{
// Hint: use React fragment ("<> ... </>") when there's
more than one element in the first level.
}
<div className="list">
{
// Hint: Adding the current list with map in here
}
<ul>
{this.state.todo.map((t, i) => (
<li key={i}>{t}</li>
))}
</ul>
</div>
{
// I would prefer adding it inside a form element and instead of onKeyDown, use onSubmit on the form
// (on enter it will submit automatically, but you will have to do an e.preventDefault() to not refresh the page).
}
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
</>
);
}
}
This is a working example with a few comments. Also setState runs asyncrounously so it's not a good idea to run multiple one at the same time. Hope this helps.
Using map like TechySharnav mentioned is a quick way of doing it. But if you need to do some more complex operations/layout stuff, then writing a custom function and calling it in the render jsx might be cleaner. So, you could have a function like:
renderItems() {
var rows = []
this.state.todo.forEach((elem, idx) => {
// for example
rows.push(
<p>{elem}</p>
)
});
return rows;
}
Then call it inside render:
//...
<div className="list">
{this.renderItems()}
</div>
//...
js map will certainly solve the problem.
this small snippet for printing the list,
render() {
return (
<div className="list">
{ this.state.todo.map((item) => {
return <p>{item}</p>
})}
</div>
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
);
}

Unable to type in text box when passing onChange prop from parent to child in React

I have a parent component which holds state that maintains what is being typed in an input box. However, I am unable to type anything in my input box. The input box is located in my child component, and the onChange and value of the input box is stored in my parent component.
Is there any way I can store all the form logic/input data on my parent component and just access it through my child components?
Here is a section of my parent component code:
export class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
screenType: 'init',
series: [],
isLoading: true,
title: '',
};
this.handleChange = this.handleChange.bind(this);
this.searchAPI = this.searchAPI.bind(this);
this.clickSeries = this.clickSeries.bind(this);
}
handleChange(e) {
this.setState({
value: e.target.value,
});
}
onKeyPress = (e) => {
if (e.which === 13) {
this.searchAPI();
}
};
async searchAPI() {
...some search function
}
render() {
return (
<Init onKeyPress={this.onKeyPress} value={this.state.value} onChange={this.handleChange} search={this.searchAPI} />
);
}
And here is a section of my Child component:
function Init(props) {
return (
<div className="container">
<div className="search-container-init">
<input
onKeyPress={props.onKeyPress}
className="searchbar-init"
type="text"
value={props.value}
onChange={props.handleChange}
placeholder="search for a TV series"></input>
<button className="btn-init" onClick={props.search}>
search
</button>
</div>
</div>
);
}
export default Init;
Incorrect function name used in the child. onChange prop passed into child in the parent and using that in the child as handleChange.
Also, you do not need to bind explicitly if using ES6 function definition.
Here is the updated code:
Search.js
export class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
screenType: 'init',
series: [],
isLoading: true,
title: '',
};
}
const handleChange = (e) => {
this.setState({
value: e.target.value,
});
}
const onKeyPress = (e) => {
if (e.which === 13) {
this.searchAPI();
}
};
const async searchAPI = () => {
...some search function
}
render() {
return (
<Init onKeyPress={this.onKeyPress} value={this.state.value} handleChange={this.handleChange} search={this.searchAPI} />
);
}
Child component:
function Init(props) {
return (
<div className="container">
<div className="search-container-init">
<input
onKeyPress={props.onKeyPress}
className="searchbar-init"
type="text"
value={props.value}
onChange={(e) => props.handleChange(e)}
placeholder="search for a TV series"></input>
<button className="btn-init" onClick={props.search}>
search
</button>
</div>
</div>
);
}
export default Init;
In your child component, you are using props.handleChange! But in your parent component, you passed it as onChange! Use the same name you used to pass the value/func. It should be like props.onChange! A silly error to watch out for

React: 2 way binding props

How can I do a 2 way binding of a variable from the parent component (Form.js), such that changes occurred in the child component (InputText.js) will be updated in the parent component?
Expected result: typing values in the input in InputText.js will update the state of Form.js
In Form.js
render() {
return (
<div>
<InputText
title="Email"
data={this.state.formInputs.email}
/>
<div>
Value: {this.state.formInputs.email} // <-- this no change
</div>
</div>
)
}
In InputText.js
export default class InputText extends React.Component {
constructor(props) {
super(props);
this.state = props;
this.handleKeyChange = this.keyUpHandler.bind(this);
}
keyUpHandler(e) {
this.setState({
data: e.target.value
});
}
render() {
return (
<div>
<label className="label">{this.state.title}</label>
<input type="text" value={this.state.data} onChange={this.handleKeyChange} /> // <-- type something here
value: ({this.state.data}) // <-- this changed
</div>
)
}
}
You can manage state in the parent component itself instead of managing that on child like this (lifting state up):
In Form.js
constructor(props) {
super(props);
this.handleKeyChange = this.keyUpHandler.bind(this);
}
keyUpHandler(e) {
const { formInputs } = this.state;
formInputs.email = e.target.value
this.setState({
formInputs: formInputs
});
}
render() {
// destructuring
const { email } = this.state.formInputs;
return (
<div>
<InputText
title="Email"
data={email}
changed={this.handleKeyChange}
/>
<div>
Value: {email}
</div>
</div>
)
}
In InputText.js
export default class InputText extends React.Component {
render() {
// destructuring
const { title, data, changed } = this.props;
return (
<div>
<label className="label">{title}</label>
<input type="text" value={data} onChange={changed} />
value: ({data})
</div>
)
}
}
You can also make your InputText.js a functional component instead of class based component as it is stateless now.
Update: (How to reuse the handler method)
You can add another argument to the function which would return the attribute name like this:
keyUpHandler(e, attribute) {
const { formInputs } = this.state;
formInputs[attribute] = e.target.value
this.setState({
formInputs: formInputs
});
}
And from your from you can send it like this:
<input type="text" value={data} onChange={ (event) => changed(event, 'email') } />
This assumes that you have different inputs for each form input or else you can pass that attribute name also from parent in props to the child and use it accordingly.
You would need to lift state up to the parent
parent class would look something like
onChangeHandler(e) {
this.setState({
inputValue: e.target.value // you receive the value from the child to the parent here
})
}
render() {
return (
<div>
<InputText
title="Email"
onChange={this.onChangeHandler}
value={this.state.inputValue}
/>
<div>
Value: {this.state.inputValue}
</div>
</div>
)
}
children class would look something like
export default class InputText extends React.Component {
constructor(props) {
super(props);
this.state = props;
}
render() {
return (
<div>
<label className="label">{this.state.title}</label>
<input type="text" value={this.state.value} onChange={this.props.onChange} />
value: ({this.state.value})
</div>
)
}
}
You can simply pass a callback from Form.js to InputText and then call that callback in InputText on handleKeyChange

Stop Relay: Query Renderer in reloading data for certain setStates

I'm currently following this and I did get it to work. But I would like to know if there is a way to stop the Query Render from reloading the data when calling this.setState(). Basically what I want is when I type into the textbox, I don't want to reload the data just yet but due to rendering issues, I need to set the state. I want the data to be reloaded ONLY when a button is clicked but the data will be based on the textbox value.
What I tried is separating the textbox value state from the actual variable passed to graphql, but it seems that regardless of variable change the Query will reload.
Here is the code FYR.
const query = graphql`
query TestComponentQuery($accountId: Int) {
viewer {
userWithAccount(accountId: $accountId) {
name
}
}
}
`;
class TestComponent extends React.Component{
constructor(props){
super(props);
this.state = {
accountId:14,
textboxValue: 14
}
}
onChange (event){
this.setState({textboxValue:event.target.value})
}
render () {
return (
<div>
<input type="text" onChange={this.onChange.bind(this)}/>
<QueryRenderer
environment={environment}
query={query}
variables={{
accountId: this.state.accountId,
}}
render={({ error, props }) => {
if (error) {
return (
<center>Error</center>
);
} else if (props) {
const { userWithAccount } = props.viewer;
console.log(userWithAccount)
return (
<ul>
{
userWithAccount.map(({name}) => (<li>{name}</li>))
}
</ul>
);
}
return (
<div>Loading</div>
);
}}
/>
</div>
);
}
}
Okay so my last answer didn't work as intended, so I thought I would create an entirely new example to demonstrate what I am talking about. Simply, the goal here is to have a child component within a parent component that only re-renders when it receives NEW props. Note, I have made use of the component lifecycle method shouldComponentUpdate() to prevent the Child component from re-rendering unless there is a change to the prop. Hope this helps with your problem.
class Child extends React.Component {
shouldComponentUpdate(nextProps) {
if (nextProps.id === this.props.id) {
return false
} else {
return true
}
}
componentDidUpdate() {
console.log("Child component updated")
}
render() {
return (
<div>
{`Current child ID prop: ${this.props.id}`}
</div>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
id: 14,
text: 15
}
}
onChange = (event) => {
this.setState({ text: event.target.value })
}
onClick = () => {
this.setState({ id: this.state.text })
}
render() {
return (
<div>
<input type='text' onChange={this.onChange} />
<button onClick={this.onClick}>Change ID</button>
<Child id={this.state.id} />
</div>
)
}
}
function App() {
return (
<div className="App">
<Parent />
</div>
);
}

React changing input type via event

I'm trying to extend a React input component, which needs to be type password and after certain event on the element or element next to it - it needs to toggle the type of the input (type="text/password").
How this can be handled by React?
I have this as class for my component:
import { React, Component } from 'lib'
export class PasswordInput extends Component {
constructor(props, context){
super(props, context)
const { type, validate, password } = this.props
if(context.onValidate && password) {
context.onValidate(password, validate)
}
if(context.showHide && password) {
context.onValidate(password, validate)
}
}
render() {
let inputType = 'password'
return (
<div className="form">
<label htmlFor="password">{ this.props.label }</label>
<input {...this.constructor.filterProps(this.props)} type={ inputType } />
{ this.props.touched && this.props.error && <div className="error">{ this.props.error }</div> }
<button onClick={ showHide() }>{ this.props.btnLabel }</button>
</div>
)
}
showHide(field) {
return function(event){
console.log(`state before is ${this.state}`)
}
}
// the meld is
// export function meld(...objects) {
// return Object.assign({}, ...objects)
// }
static filterProps(props) {
const result = meld(props)
delete(result.validate)
return result
}
}
PasswordInput.contextTypes = {
onValidate: React.PropTypes.func
}
EDIT
I've edited the render method and added a function that handles the event, but I'm now getting:
Warning: setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
And my browser crashes.
.....
render() {
return (
<div className="form__group">
<label htmlFor="password">{ this.props.label }</label>
<input {...this.constructor.filterProps(this.props)} type={ this.state.inputType } />
{ this.props.touched && this.props.error && <div className="form__message form__message_error">{ this.props.error }</div> }
<button onClick={ this.handleClick() }>{ this.props.btnLabel }</button>
</div>
)
}
handleClick(){
this.setState({ inputType: this.state.inputType === 'password' ? 'text' : 'password' })
}
.....
You can configure the type of the input according to component's state, and set it on some event, for example:
<input {...this.constructor.filterProps(this.props)} type={ this.state.inputType } onChange={ event => this.onChange(event) } />
And implement the onChange method:
onChange(event) {
var length = event.currentTarget.value.length;
this.setState({ inputType: length < 5 ? 'text' : 'password' });
}
You might have to bind the onChange function.

Categories