Input not focusing on componentDidUpdate - javascript

I have an input that is disable by default, but when I dispatch an action to enable it, it should become able. I also want this input to become focused, but I am not able to do that. Here is my component:
class UserInput extends Component {
constructor (props) {
super(props);
this.state = { responseValue: '' };
this.responseHandler = this.responseHandler.bind(this);
this.submitAnswer = this.submitAnswer.bind(this);
}
componentDidUpdate (prevProps) {
if (!this.props.disable && prevProps.disable) {
this.userInput.focus();
}
}
responseHandler (e) {
this.setState({ responseValue: e.target.value });
}
submitAnswer () {
this.props.submitUserAnswer(this.state.responseValue);
this.setState({ responseValue: '' })
}
render () {
return (
<div className="input-container">
<input ref={(userInput) => { this.userInput = userInput; }}
className="input-main"
disabled={this.props.disable}
value={this.state.responseValue}
onChange={this.responseHandler}
/>
<button className="input-button" onClick={this.submitAnswer}>{this.props.strings.SEND}</button>
</div>
);
}
}
UserInput.defaultProps = {
strings: {
'SEND': 'SEND',
},
};
UserInput.contextTypes = {
addSteps: React.PropTypes.func,
};
export default Translate('UserInput')(UserInput);
Thanks in advance!

I reckon your problem lies here:
if (!this.props.disable && prevProps.disable) {
this.userInput.focus();
}
this.props.disable will still be its initial value (false) after the update (it's not being updated by a parent component from what I can see) so the call to focus is never invoked.

I ended up doing this, because I needed to also add a placeholder to the disabled input:
class UserInput extends Component {
constructor (props) {
super(props);
this.state = { responseValue: '' };
this.responseHandler = this.responseHandler.bind(this);
this.submitAnswer = this.submitAnswer.bind(this);
}
responseHandler (e) {
this.setState({ responseValue: e.target.value });
}
submitAnswer () {
this.props.submitUserAnswer(this.state.responseValue);
this.setState({ responseValue: '' })
}
render () {
return (
<div className="input-container">
{ this.props.disable
? <div className="disable-input-box">Wait to type your response</div>
: <input
className="input-main"
disabled={this.props.disable}
value={this.state.responseValue}
onChange={this.responseHandler}
autoFocus
/>
}
<button className="input-button" onClick={this.submitAnswer}>{this.props.strings.SEND}</button>
</div>
);
}
}

Related

How to change the state in react with a form

I have a form that is supposed to update the initial state, I've followed many tutorials and my code looks the same as them but for some reason it doesn't update the state.
import React, { Component } from 'react';
class Create extends Component{
constructor(props){
super(props);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e) {
const title = e.target.value;
this.setState({title})
}
onBodyChange(e) {
const body = e.target.value;
this.setState({body})
}
onSubmit(e) {
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState(() => ({ error: "Please fill in all gaps"}))
} else {
this.setState(() => ({ error: "" }))
// Send info to the main page
alert(this.state.title);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange= {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default Create;
When I check the current state in the React developer tools it shows that the state remains the same and I don't know why because there are not errors in the log.
I'm working with webpack, babel and react.
////////////////////
EDITED
////////////////////
I edited my code following the suggestions you guys gave me but still it doesn't work. An alert is supposed to appear when submitted the form but that doesn't get fired either, so I believe that non of the functions are getting fired.
This is my edited code:
import React, { Component } from 'react';
class Create extends Component{
constructor(props){
super(props);
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e) {
const title = e.target.value;
this.setState((prevState) => {
return {
...prevState,
title
};
});
}
onBodyChange(e) {
const body = e.target.value;
this.setState((prevState) => {
return {
...prevState,
body
};
});
}
onSubmit(e) {
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState((prevState) => {
return {
...prevState,
error: "Please fill in all gaps"
};
});
} else {
this.setState((prevState) => {
return {
...prevState,
error: ""
};
});
// Send info to the main page
alert(this.state.title);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange= {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default Create;
You should try binding the event handlers in the constructor, because it seems like this within those event handling functions could be undefined. The React documentation outlines why the binding is necessary, and here's another useful page on handling forms in React.
constructor(props) {
super(props);
...
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmitChange = this.onSubmitChange.bind(this);
}
An alternative to binding in the constructor would also be to use arrow functions for your event handlers which implicitly bind this.
class Create extends Component {
...
onTitleChange = () => { ... }
onBodyChange = () => { ... }
onSubmitChange = () => { ... }
}
EDIT: Can't comment on your post since my reputation is too low, but it seems like there's a typo in the changes you just made.
this.onSubmitC = this.onSubmit.bind(this) should be changed to
this.onSubmit = this.onSubmit.bind(this)
React's setState() accepts an object, not a function. So change your onSubmit() to this:
if(!this.state.title || !this.state.body){
this.setState({ error: "Please fill in all gaps"})
} else {
this.setState({ error: "" })
// Send info to the main page
alert(this.state.title);
}
It better to use the previous state and only update the required (input value) values.
in your case you are replacing the existing state (whole object) just with the title and the same goes for body onBodyChange
onTitleChange () {
const title = e.target.value;
this.setState((prevState) => {
return {
...prevState,
title
};
});
};
<

ReactJS - New item even if the field is empty

I'm practicing React and I'm making a TodoList Component. But currently, I can add a todo item that is empty. And I want a message saying that it's not allowed.
My issue is even if the field is empty, I can create a new item.
Here is my code:
import React, { Component } from "react";
class TodoList extends Component {
constructor() {
super();
this.state = {
userInput: '',
items: []
};
}
onChange(event) {
this.setState({
userInput: event.target.value
}, () => console.log(this.state.userInput));
}
addTodo(event) {
event.preventDefault();
this.checkField();
this.setState({
userInput: '',
items: [...this.state.items, this.state.userInput] },
() => console.log(this.state.items));
}
deleteTodo(item) {
const array = this.state.items;
const index = array.indexOf(item);
array.splice(index, 1);
this.setState({
items: array
})
}
checkField() {
if(this.state.userInput.length === 0) {
let emptyMessageDom = document.createElement("p");
document.body.appendChild(emptyMessageDom);
emptyMessageDom.innerHTML ="This is empty!!"
}
}
renderTodos() {
return this.state.items.map((item, index) => {
return (
<div key={index}>
{item} {index} | <button onClick={this.deleteTodo.bind(this, item)}>X</button>
</div>)
})
}
render() {
return(
<div>
<form>
<input
value={this.state.userInput}
type="text"
placeholder="New item"
onChange={this.onChange.bind(this)}
required
/>
<button onClick={this.addTodo.bind(this)}>Add</button>
</form>
<div>
{this.renderTodos()}
</div>
</div>
);
}
}
export default TodoList;
I tried to put the code of the function checkField() into the setState of addTodo() function, but it doesn't work.
Thanks in advance!
You should use state to show an error message. This will help clean up the way you render and remove the message. Heres a full working example
Update addTodo to conditionally add based on your criteria.
addTodo(event) {
event.preventDefault();
if (!this.checkField()) {
return
}
this.setState({
userInput: '',
items: [...this.state.items, this.state.userInput]
})
}
and then update checkField to validate and return a boolean
checkField() {
if(this.state.userInput.length === 0) {
this.setState({error: 'Field is required.'})
return false;
}
return true;
}
you can then update the render portion to show the error message
<form>
<input
value={this.state.userInput}
type="text"
placeholder="New item"
onChange={this.onChange.bind(this)}
required
/>
{!!this.state.error && <label>{this.state.error}</label>}
<button onClick={this.addTodo.bind(this)}>Add</button>
</form>
Then finally don't forget to remove the error when a change event happens on the input as the validation is now stale.
onChange(event) {
this.setState(
{
userInput: event.target.value,
error: ''
}
);
}
you can modify checkField ,addToDoas follows
checkField() {
if(this.state.userInput.length === 0) {
let emptyMessageDom = document.createElement("p");
document.body.appendChild(emptyMessageDom);
emptyMessageDom.innerHTML ="This is empty!!"
//invalid input to be added as to do item
return false;
}
//valid input to be added as to do item
return true
}
addTodo(event) {
event.preventDefault();
if(this.checkField())
{
this.setState({
userInput: '',
items: [...this.state.items, this.state.userInput] },
() => console.log(this.state.items));
}
}

How can I check the value of this.state with an if statement and not console.log()?

I want to check the values of this.state.cityCodeval and this.state.idVal using an if statement inside the displayName() method so it can display what's inside the return() if the values inputted by the user are correct.
In my Webstorm IDE, I get a warning that says:
Binary operation argument type string is not compatible with type string
Which makes me believe I'm checking for their values the wrong way.
I know I could just do console.log(this.state.cityCodeval); or console.log(this.state.idVal);, but I need to check for what the user input is.
Here's my code
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actionType from '../../store/actions/actions';
class SearchArticle extends Component {
constructor(props) {
super(props);
this.state = {
flag: false,
idVal: '',
cityCodeval: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleArticleId = this.handleArticleId.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
console.log("IDValue --> " + this.state.idVal);
this.props.articleIdValueRedux(this.state.idVal);
this.setState({flag: true});
}
handleChange = event => {
this.setState({value: event.target.value});
this.props.cityCodeReducerRedux(event.target.value);
}
handleArticleId = event => {
event.preventDefault();
this.setState({idVal: event.target.value});
}
displayName = () => {
if(this.state.cityCodeval === 'nyc' && this.state.idVal === '1') {
return (
<div>
<p>author name: {this.state.authorNameValue}</p>
<p>article text: {this.state.storyTextValue}</p>
</div>
);
}
}
render() {
return(
<div>
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.cityCodeValue} type="text" placeholder="city code"/>
<input onChange={this.handleArticleId} value={this.state.idVal} placeholder="article id"/>
<button type="submit" value="Search">Submit</button>
{this.state.flag ? this.displayName() : null}
</form>
</div>
);
}
}
const mapStateToProps = state => {
return {
cityCodeValue: state.cityCodeValue.cityCodeValue,
authorNameValue: state.authorNameValue.authorNameValue,
articleIdValue: state.articleIdValue.articleIdValue,
storyTextValue: state.storyTextValue.storyTextValue
};
};
const mapDispatchToProps = dispatch => {
return {
cityCodeReducerRedux: (value) => dispatch({type: actionType.CITY_CODE_VALUE, value}),
articleIdValueRedux: (value) => dispatch({type: actionType.ARTICLE_ID_VALUE, value})
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchArticle);
You should still return null; as a safe clause just in case your condition doesn't match.
displayName = () => {
if(this.state.cityCodeval === 'nyc' && this.state.idVal === '1') {
console.log(this.state.cityCodeval); // console it here
console.log(this.state.idVal); // console it here
return (
<div>
<p>author name: {this.state.authorNameValue}</p>
<p>article text: {this.state.storyTextValue}</p>
</div>
);
}
return null;
}
Also now, in your render method you can do this.
{this.state.flag && this.displayName()}
This means that if the flag variable is true, call displayName it then executes the function. If you first condition matches it will return that otherwise it will return null;
Previously in your current code if the flag variable was true and it executed the function displayName where the if condition didn't meet. This caused an error because it had nothing to return.

Automatically render child component when state has been updated in parent component

The parent component Dashboard holds the state for every ListItem I add to my Watchlist. Unfortunately, every time I am adding an Item, it gets added to the DB, but only shows up when I refresh the browser.
class UserDashboard extends React.Component {
state = {
data: []
}
componentWillMount() {
authService.checkAuthentication(this.props);
}
isLoggedIn = () => {
return authService.authenticated()
}
getAllCoins = () => {
//fetches from backend API
}
addWishlist = () => {
this.getAllCoins()
.then(things => {
this.setState({
data: things
})
})
console.log("CHILD WAS CLICKED")
}
componentDidMount() {
this.getAllCoins()
.then(things => {
this.setState({
data: things
})
})
}
render() {
return (
<div className="dashboard">
<h1>HI, WELCOME TO USER DASHBOARD</h1>
<SearchBar
addWishlist={this.addWishlist}
/>
<UserWatchlist
data={this.state.data}
/>
</div>
);
}
}
The User Watchlist:
class UserWatchlist extends React.Component {
constructor(props) {
super(props)
}
// componentDidUpdate(prevProps) {
// if (this.props.data !== prevProps.data) {
// console.log("CURRENT", this.props.data)
// console.log("PREVs", prevProps.data)
// }
// }
render() {
return (
<div>
<h2>These are tssssyou are watching:</h2>
<ul className="coin-watchlist">
{
this.props.data.map((coin, idx) => {
return <ListItem key={idx}
coin={coin.ticker}
price={coin.price}
/>
})
}
</ul>
</div>
)
}
}
The search Bar that shows potential Items to watch over:
class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = {
coins: [],
searchValue: ""
}
}
searchHandler = e => {
e.preventDefault()
const value = e.target.value
this.setState({
searchValue: value
});
if (value === "") {
this.setState({
coins: []
})
} else {
this.getInfo()
}
}
getInfo = () => {
// Searches the API
}
addWishlist = () => {
this.props.addWishlist();
}
render() {
const {coins, searchValue} = this.state
return (
<div className="coin-search">
<form>
<input
type="text"
className="prompt"
placeholder="Search by ticker symbol"
value={searchValue}
onChange={this.searchHandler}
/>
</form>
<ul className="search-suggestions">
{
coins.filter(searchingFor(searchValue)).map( coin =>
<Currency
coin={coin}
addWishlist={this.addWishlist}
/>
)
}
</ul>
</div>
);
}
}
And the actual Currency that gets clicked to be added:
class Currency extends React.Component {
addToWatchlist = () => {
// POST to backend DB to save
};
fetch("/api/add-coin", settings)
.catch(err => {
return err
})
}
clickHandler = () => {
this.addToWatchlist()
this.props.addWishlist()
}
render() {
return(
<div className="search-results">
<li>
<h3> { this.props.coin.currency } </h3>
<button
className="add-to-list"
onClick={this.clickHandler}
>
+ to Watchlist
</button>
</li>
</div>
)
}
}
As you can see, I am sending props down all the way down to child. When I click the button to Add to Watchlist, I see the console.log message appear, saying "CHILD WAS CLICKED". I've even tried just calling the method to fetch from backend API again.
Also, in UserWatchlist, I've tried a componentDidUpdate, but both prevProps and this.props show the very same array of data. Somewhere in the chain, my data is getting lost.
This is also my first time posting a question here, so if it can be improved, I am happy to add extra details and contribute something to this community
You probably forgot to wait for addToWatchlist to complete:
addToWatchlist = () => {
// POST to backend DB to save
return fetch("/api/add-coin", settings)
.catch(err => {
return err
})
}
clickHandler = () => {
this.addToWatchlist().then(() => {
this.props.addWishlist()
})
}

Converting a stateful component to stateless

i'm pretty new to react and redux and i'm having an issue here. It's mandatory to use only stateless components with containers whenever state handing is required. These two components are:
import React from 'react';
import DatePicker from '../DatePicker';
class DayInput extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
dateValue: new Date(),
activeDateWidget: false,
};
}
changeDate(date) {
this.setState({
dateValue: date,
});
}
changeActiveDateWidget(e) {
e.stopPropagation();
this.setState({
activeDateWidget: !this.state.activeDateWidget,
});
}
render() {
const { input, meta } = this.props;
const { dateValue, activeDateWidget } = this.state;
return (
<div>
<input
{...input}
className="form-control"
type="text"
value={dateValue}
onClick={this.changeActiveDateWidget}
// onBlur={this.changeActiveDateWidget}
/>
{activeDateWidget ? (
<div>
<DatePicker
changeActiveDateWidget={this.changeActiveDateWidget}
changeDate={this.changeDate}
dateValue={dateValue}
/>
</div>
) : (
<div />
)}
</div>
);
}
}
export default DayInput;
import React from 'react';
import 'react-day-picker/lib/style.css';
import DayPicker, { DateUtils } from 'react-day-picker';
class DatePicker extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
selectedDay: new Date(),
};
}
componentDidMount() {
if (this.input) {
this.input.focus();
}
}
handleDayClick(e, day, { disabled }) {
e.stopPropagation();
if (disabled) {
return;
}
this.setState({ selectedDay: day }, () => {
this.props.changeDate(day);
this.props.changeActiveDateWidget();
});
}
focusThisComponent(e) {
if (e) {
this.input = e;
}
}
render() {
const { changeActiveDateWidget } = this.props;
const { selectedDay } = this.state;
return (
<div
ref={this.focusThisComponent}
tabIndex="1"
>
<DayPicker
id="THISTHING"
initialMonth={selectedDay}
selectedDays={day => DateUtils.isSameDay(selectedDay, day)}
onDayClick={this.handleDayClick}
/>
</div>
);
}
}
export default DatePicker;
As you can see the first component is wrapped inside the second component. I tried to convert the first component myself like this:
const DayInput = props => {
<input
{...props.input}
type="text"
value= {new Date()}
onClick={()=>??}
/>
}
but as you can see i dont know how to handle the onclick event. Can someone help me to achieve this?
To turn your component in a stateless component, you must pass everything as properties of the component.
This will be your DayInput splitted into 2 components :
const DayInputShow = props => {
return (<input
{...props.input}
type="text"
value= {props.value}
onClick={(event)=>props.onClick()}
/>);
};
const DayInputEdit = props => {
return (<DatePicker
changeActiveDateWidget={props.changeActiveDateWidget}
changeDate={props.onChange}
dateValue={props.value}
/>);
};
DayInputShow.propTypes = {
value: PropTypes.date,
onClick: PropTypes.func,
}
DayInputEdit.propTypes = {
value: PropTypes.date,
onChange: PropTypes.func,
}
And this will be the root component (uncomplete and still statefull) :
class DatePicker extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
selectedDay: new Date(),
};
}
componentDidMount() {
if (this.input) {
this.input.focus();
}
}
handleDayClick(e, day, { disabled }) {
e.stopPropagation();
if (disabled) {
return;
}
this.setState({ selectedDay: day }, () => {
this.props.changeDate(day);
this.props.changeActiveDateWidget();
});
}
focusThisComponent(e) {
if (e) {
this.input = e;
}
}
render() {
const { changeActiveDateWidget } = this.props;
const { selectedDay } = this.state;
let dayPicker;
if (this.input) {
dayPicker = <DayPickerEdit
value={this.state.selectedDay}
onChange={(value) => {this.setState({selectedDay: value})}}
selectedDays={day => DateUtils.isSameDay(selectedDay, day)}
onDayClick={this.handleDayClick}
/>
} else {
dayPicker = <DayPickerShow
value={this.state.selectedDay}
ref={(input) => { this.inputRef = input; }} />
onClick={() => {this.focusThisComponent(this.inputRef )}}
/>
}
return (
<div
ref={this.focusThisComponent}
tabIndex="1"
>
{dayPicker }
</div>
);
}
}
export default DatePicker;

Categories