I'm trying to capture keystrokes from a textarea and have tried using attributes onKeyUp, onKeyPress, onKeyPressCapture, onKeyDown, onKeyDownCapture. All of them seem to miss some key entries:
When I enter a new key, one of the ones that was not displaying before then shows, in order.
Because of that queued delay, I'm thinking I might need to put a delay on the console log. But that doesn't actually solve the underlying issue. Does anyone know why this behavior is happening?
Here is the parent (App) and child component (TypeArea)
Parent
class App extends React.Component {
constructor (props) {
super(props)
// sets up this.props to function
this.state = {
textbox_in_parent_state: 'string passed from state of Parent(App)',
someVar: 'parent_constructor_state',
text_from_textarea: ''
}
this.handler = this.handler.bind(this)
this.text_capture_from_parent = this.text_capture_from_parent.bind(this)
}
text_capture_from_parent(eventObject) {
this.setState({
text_from_textarea: eventObject.target.value
})
console.log(this.state.text_from_textarea)
}
render() {
return (
<div>
<div className="container">
<Header />
<div className="row">
<div className="col-sm-6">
<TypeArea textcapture={this.text_capture_from_parent}
/>
</div>
<div className="col-sm-6">
<MarkdownPreview />
</div>
</div>
</div>
{/* <div style={{textAlign: 'center'}}>*/}
{/* <h1>App component written in client/components/App.jsx</h1>*/}
{/* </div>*/}
</div>
)
}
}
export default App
Child
import React from 'react';
class TypeArea extends React.Component {
constructor(props) {
super(props)
this.state = {
textbox_text: 'string from state of child "TypeArea"'
}
console.log(this.state.textbox_text)
}
render() {
return (
<div>
<h1>Typing Area</h1>
<div className="form-group">
<textarea className="form-control" id="textbox" rows="25" placeholder="Type Here" onKeyPressCapture={this.props.textcapture}>
</textarea>
<button onClick={this.props.passdown}>Click me</button>
</div>
</div>);
}
}
export default TypeArea
text_capture_from_parent(eventObject) {
this.setState({
text_from_textarea: eventObject.target.value
})
console.log(this.state.text_from_textarea)
}
this.setState() is async, and you are calling directly console.log(..) after setting the state, at this moment the state maybe didn't successfully changed already. but luckily this.setState(..)is providing a callback when it finished setting the new state. so you can call it like this:
this.setState({
text_from_textarea: eventObject.target.value
}), () => {
console.log(this.state.text_from_textarea);
});
and you should see the actual value.
Related
In a React app I have a couple of working components like this one:
<div className="form-group col-md-6">
<label className='inp-lbl'>{utl.nameWord()}</label>
<div className='inp-name'>
<input
type="text"
ref="name"
defaultValue={this.name}
onChange={e => {
this.setState((state) => {
return {name: e.target.value};
});
}}
/>
</div>
</div>
Instead of repeating similar code for each or them, I want to extract a generic component that I will be able to reuse; but it does not work as I expect. Here is the new component I made:
class InputField extends React.Component {
constructor(props) {
super(props);
this.state = {
labelStr: props.lbStr,
nameStr: props.nmStr,
defltVal: props.dfVl,
onChgFnc: props.onChFn
};
// this.onChgFnc = props.onChFn
}
render() {
return (
<React.Fragment>
<div className="form-group col-md-6">
<label className='inp-lbl'>{this.state.labelStr}</label>
<div className='inp-name'>
<input
type="text"
ref={this.state.nameStr}
defaultValue={this.state.defltVal}
onChange={this.state.onChgFnc}
/>
</div>
</div>
</React.Fragment>
)
}
}
And this is how I call the new component:
<InputField lbStr={utl.nameWord()} nmStr='name'
dfVl={this.name}
onChFn={handleChange} />
The function handleChange is defined as:
function handleChange(event) {
this.setState((state) => {
return {name: event.target.value};
})
}
Though I thought this should work, it does not. So it would be great if somebody could spot the mistake I am making and let me know.
You could be suffering from an incorrectly bound this inside your handleChange function. Perhaps try just closing over the setState call instead of referencing it via this. For example
function handleChange(event) {
setState({ name: event.target.value })
}
(I've also changed the particular usage of the state-setter to accept a value as you weren't using the previous state value)
I would also go one further and change to a functional component and don't store your props in state as this will disconnected the component from the flow of prop values if/when they change.
function InputField({ name, label, onChange, defaultValue }){
return (
<div className="form-group col-md-6">
<label className='inp-lbl'>{label}</label>
<div className='inp-name'>
<input
type="text"
ref={name}
defaultValue={defaultValue}
onChange={onChange}
/>
</div>
</div>
)
}
I don’t understand why you need to assign the onChangehandler from props to state object in Child Component. You can directly bind the change handler from props’ change callback function.
<input type="text"
ref={this.state.nameStr}
defaultValue={this.state.defltVal}
onChange={this.props.onChFn} />
Storing props in state is an anti-pattern in React as it often leads to problem of multiple sources of truth(same info) in a component.
A better way to write your component would be to initialize the value of field from props and derive rest of the values directly from props.
You can directly bind the change handler from props’ change callback function.
class InputField extends React.Component {
constructor(props) {
super(props);
this.state = {
nameStr: props.nmStr,
};
}
render() {
return (
<React.Fragment>
<div className="form-group col-md-6">
<label className='inp-lbl'>{props.lbStr}</label>
<div className='inp-name'>
<input
type="text"
value={this.state.nameStr}
defaultValue={this.props.defltVal}
onChange={this.props.onChgFnc}
/>
</div>
</div>
</React.Fragment>
)
}
}
I'm a young dev trying to learn some Reactjs, but I'm having trouble understanding how to configure this Todo app. My goal is to have a button that will add items to the list once entered and submitted. I feel like I'm pretty close to having it figured out.
I've got an App component (parent), button component, and a List component(also a header and item component). the list has a variable that has an empty array for me to add items to, which I reference in my App component.
Here lies the problem. I have an event listener on my button that runs a function that sets the state. I'm logging the list every time I click, which shows that the array is receiving the text inputs and making a new object. However, the DOM is not re-rendering what confuses me even more, is that when I make a slight edit (random semicolon) the DOM renders the items that were entered and logged before I last saved, but remains unresponsive.
What am I missing here? Also, I understand that lifecycle methods like componentDidMount() or componentDidUpdate() may be useful, but I do not fully understand how and where to use them.
export class Button extends Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
const text = document.getElementById('text_field');
const input = text.value;
this.setState(() => {
TodoList.push({id: (TodoList.length+1), name: input})
})
console.log(TodoList)
}
render() {
return (
<div>
<div className='search-container'>
<input className='search' type='text' placeholder='type something...' id='text_field'></input>
</div>
<div className='button-container'>
<button type='submit' className='button-add' onClick={this.handleClick}> New Task </button>
</div>
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
todos: TodoList
}
}
render() {
const todoItems = this.state.todos.map(todo => {
console.log(todo.name, todo.id);
return <Item desc={todo.name} key={todo.id} />
})
return(
<div className='wrapper'>
<div className='card'>
<Header numTodos={this.state.todos.length}/>
<div className='todo-list'>
{todoItems}
</div>
<Button />
</div>
</div>
)
}
}
export default App
In your App.js, you should pass a function to <Button />, this technique called function as prop in react. The App.js code should look like below:
class App extends React.Component {
constructor() {
super()
this.state = {
todos: TodoList
}
}
addTodo = (todo) => {
this.setState({ todos: [...this.state.todos, todo] })
}
render() {
const todoItems = this.state.todos.map(todo => {
console.log(todo.name, todo.id);
return <Item desc={todo.name} key={todo.id} />
})
return(
<div className='wrapper'>
<div className='card'>
<Header numTodos={this.state.todos.length}/>
<div className='todo-list'>
{todoItems}
</div>
<Button todosList={this.state.todos} addTodo={(todo) => this.addTodo(todo)} />
</div>
</div>
)
}
In the code for Button.js, you get this function via this.props
export default class Button extends Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
const text = document.getElementById('text_field');
const input = text.value;
this.props.addTodo({id: this.props.todosList.length + 1, name: input })
console.log(this.props.todosList)
}
render() {
return (
<div>
<div className='search-container'>
<input className='search' type='text' placeholder='type something...' id='text_field'></input>
</div>
<div className='button-container'>
<button type='submit' className='button-add' onClick={this.handleClick}> New Task </button>
</div>
</div>
)
}
I'm having a trouble updating the state. I'm using redux so the state is updated on the reducer level. After updating the state from another component a new state is returned with new data. mapStateToProps is called but the component is not re-rendering.
This is my component
class Users extends Component {
render() {
console.log("RENDERING ------");
const usernames= this.props.usernames.map((username, key) => {
return (<div key={key} className="card mt-2">
<div className="card-body">
{username}
</div>
</div>)
})
return (
<div data-spy="scroll" data-target="#navbar-example3" data-offset="0">
{usernames}
</div>
)
}
}
const mapStateToProps = state => {
console.log("STATE", state);
return {
usernames: state.usernames.data
}
}
export default connect(mapStateToProps, null)(Users);
when loading the component the usernames are displayed. but adding a new username from another component the mapStateToProps is called but the component is not re-rendered.
Parent Component
class Parent extends Component {
render() {
return (
<div className="container">
<div className="row">
<div className="col">
<Editor />
</div>
<div className="col">
<Users />
</div>
</div>
</div>
)
}
This is the editor component where I'm dispatching the action
class Editor extends Component {
state = {
user: ""
}
handleChange = event => {
this.setState({ user: event.target.value });
}
onSubmit = e => {
e.preventDefault();
this.props.addUser(this.state.user);
}
render() {
return (
<form onSubmit={this.onSubmit}>
<div className="form-group">
<label htmlFor="exampleFormControlTextarea1">User</label>
<textarea className="form-control" id="exampleFormControlTextarea1" rows="3" value={this.state.user} onChange={this.handleChange} ></textarea>
</div>
<button type="submit" className="btn btn-primary mb-2">Submit</button>
</form>
)
}
}
const mapDispatchToProps = dispatch => {
return {
addUser: (user) =>
dispatch(addUser(user))
}
}
export default connect(null, mapDispatchToProps)(Editor);
After trying and searching online. I discovered that the problem was that the component re-render if mapStateToProps returns a different value from the last call. For my case I was handling my state on a mutable way. I was using push to add new user to the state on the reducer. The solution is to do it in the immutable way using concat:
const us = [];
us.push(action.results.user);
return { ...state, users: state.users.concat(us) };
You are mapping the usernames prop in your mapStateToProps, but not making use of it in your component. You are in fact creating a new variable called usernames and assigning it to the result of a map on this.props.messages.
Forgive me if I'm not understanding what you're trying to do, but it looks like you should just make use of this.props.usernames, and then when you update it through a dispatch, the prop will update and in turn the component will re-render.
I asked a question on here earlier, but I think I should have been more specific. Say I have two components, which I call from a higher order component:
So my higher order component, containing these components, looks like this (or at least its render method):
<div>
<Navigation />
<View />
</div>
Now, Navigation /> contains a button. Whenever that button is clicked, I want to set the focus on an input field, which is in <View />.
If both things were in the very same component, I learned that I would do something like this:
<button onClick={() => {this.myInp.focus()}}>Focus Input</button>
<input type="text" ref={(ip) => this.myInp = ip} />
I am using Redux by the way, if this should make a difference.
Since they are descendants of the same element you can use a state element and function to change that state element in the parent div and pass down as props to View and Navigation.
constructor(){
super()
this.state = {
inputFocus:false;
}
this.setInputFocus = this.setInputFocus.bind(this)
}
setInputFocus(value){
this.setState({inputFocus:false});
}
Then in the render method of the div just pass them down as props
<div>
<Navigation setInputFocus={this.setInputFocus}/>
<View inputFocus={this.state.inputFocus}/>
</div>
In the Navigation
<button onClick={() => {this.props.setInputFocus(true)}}>Focus Input</button>
and in the View
<input type="text" ref={(ip) => this.myInp = ip}/>
and have this in the componentWillReceiveProps which detects when props change and runs actions then.
componentWillReceiveProps(nextProps){
if(nextProps.inputFocus)
this.myInp.focus()
}
So when the inputFocus changes in the parent it will fire the componentWillReceiveProps in the View element and focus the input.
Along with the refs, this problems also involves child to parent and parent to child interaction.
So what I am doing in the below snippet is form the Navigation component, I have to call the parent component since a component can't directly have access to its siblings. From the parent you can get access to the child component through refs and in turn you can also access the refs which are defined in the child component
See the snippet below
class App extends React.Component {
focusInput = () => {
this.myView.myInput.focus()
}
render() {
return (
<div>
<Navigation focusInput={this.focusInput}/>
<View ref={(view) => {this.myView = view}}/>
</div>
)
}
}
class Navigation extends React.Component {
render() {
return (
<button onClick={() => this.props.focusInput()}>Focus</button>
)
}
}
class View extends React.Component {
render() {
return (
<input type="text" ref={(ip) => this.myInput = ip}/>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<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="app"></div>
In case you have multiple buttons in navigation thorugh which you want to focus multiple different inputs, you can do so in the following manner
class App extends React.Component {
focusInput = (ip) => {
this.myView[ip].focus()
}
render() {
return (
<div>
<Navigation focusInput={(val) => this.focusInput(val)}/>
<View ref={(view) => {this.myView = view}}/>
</div>
)
}
}
class Navigation extends React.Component {
render() {
return (
<div>
<button onClick={() => this.props.focusInput("myInput1")}>Focus1</button>
<button onClick={() => this.props.focusInput("myInput2")}>Focus2</button>
</div>
)
}
}
class View extends React.Component {
render() {
return (
<div>
<input type="text" ref={(ip) => this.myInput1 = ip}/>
<input type="text" ref={(ip) => this.myInput2 = ip}/>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<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="app"></div>
So I have a parent component and a log in component.
I want the user to enter their details and then hit submit and then store/pass those details around so they can be used by other components.
how is this best done in React?
for example I have this input field inside my log in component
<p>
<input type="text" id="playerName" value={this.props.nameValue} onChange={this.props.handleNameChange}/>
</p>
Then I want to pass the value that is entered to the parent component
I have this function in my parent component:
handleNameChange(event){
this.setState({nameValue: event.target.value})
};
and in my return I have:
return (
<div>
<LoginPage handleClick={this.handleClick.bind(this)} handleNameChange={this.handleNameChange.bind(this)}/>
</div>
)
However, when I console.log(nameValue) I get undefined. any ideas? can add more code if necessary/relevant
From your example you never pass nameValue to the child component.
Updated your example of rendering the LoginPage, passing this.state.nameValue into the child component via props:
return (
<div>
<LoginPage
handleClick={this.handleClick.bind(this)}
handleNameChange={this.handleNameChange.bind(this)}
nameValue={this.state.nameValue}
/>
</div>
)
Your approach using state and props is fine. Are you sure that you shouldn't just be using...
console.log(this.state.nameValue);
This is a working example
class Parent extends React.Component {
constructor() {
super();
this.state = {
nameValue:''
};
}
render() {
return (
<Child handleClick={this.handleClick.bind(this)} handleNameChange={this.handleNameChange.bind(this)} nameValue={this.state.nameValue} />
);
}
handleNameChange(e) {
this.setState({
nameValue: e.target.value
});
}
handleClick() {
alert(this.state.nameValue);
}
}
class Child extends React.Component {
render() {
return (
<div>
<input type="text" value={this.props.nameValue} onChange={this.props.handleNameChange} />
<button onClick={this.props.handleClick}>Click Me!</button>
</div>
);
}
}
JSFiddle here.