Using a variable as part of a `this.state` call ReactJS - javascript

I'm attempting to created a form validation using react-bootstrap, and would like to avoid making a validation function for each input.
I have this input:
<Input
hasFeedback
type="text"
label="Username"
bsStyle={this.state.UsernameStyle}
onChange={this.handleChange.bind(this, 'Username')}
value={this.state.Username}
bsSize="large"
ref="inptUser"
placeholder="Enter Username"
/>
Plus, I have two function to handle the validation and updating of the state:
handleChange: function(name, e) {
var state = {};
state[name] = e.target.value;
this.setState(state);
var namestyle = name + 'Style';
this.setState(this.validationState(namestyle, e));
},
&
validationState: function(style, event) {
var length = event.target.value.length;
if (length > 4) this.setState({style: 'success'});
else if (length > 2) this.setState({style: 'warning'});
return {style};
}
At this point if I change style to Username I can get this solution to work for that specific input as well. I could make an if statement and depending on the string I get in style I can call the appropriate setState, but is their a better way to do this?
Thanks!

var yourVariable = "name";
this.state[yourVariable];
is equivelant to:
this.state.name;
if you want to use your variable in set state you can use this:
var value = "peter";
this.setState({[yourVariable]: value});
is equivelant to:
this.setState({name: value)};

handleChange should collect all of the information required to change state and call setState a single time. vaslidationState should not be changing state. It should just be returning the new style:
validationState(value) {
return (value.length > 4) ? "success" : (value.length > 2) ? "warning" : "error";
}
handleChange(name, e) {
const value = e.target.value;
const style = this.validationState(value);
const stateUpdates = {
[name]: value,
[`${name}Style`]: style
};
this.setState(stateUpdates);
}

Related

Simulate a text input change as if the user had typed a specific key in order to trigger React's onChange handler?

I'm using the onKeyDown handler to prevent the user from adding more than 1 DECIMAL_SEPARATOR (which is "." in this example) into an input field.
And that is working as you can see from the GIF below.
But I would like to accept the THOUSAND_SEPARATOR (which is "," in the example), as if the user had typed the DECIMAL_SEPARATOR.
In other words, if the user hits 15 and then hits the THOUSAND_SEPARATOR, I would like to convert it into a DECIMAL_SEPARATOR.
Note: I don't want to replace the THOUSAND_SEPARATOR in the value string, because at some point there might be other THOUSAND_SEPARATORs present in the string and it would get tricky.
QUESTION
Is is possible for me to simulate a keyPress, keyDown, onChange ??? event for the DECIMAL_SEPARATOR key from my else block from the snippet below?
Basically what I want is: "Prevent default on the event for the THOUSAND_SEPARATOR (which I'm already doing) and I should create an event that should behave exactly like as if the user had typed the DECIMAL_SEPARATOR key".
How can I do it?
const DECIMAL_SEPARATOR = ".";
const THOUSAND_SEPARATOR = ",";
function App() {
const [inputValue, setInputValue] = React.useState("");
function onChange(event) {
const newValue = event.target.value;
setInputValue(newValue);
}
function onKeyDown(event) {
console.log("onKeyDown...");
if (event.key === DECIMAL_SEPARATOR || event.key === THOUSAND_SEPARATOR) {
const currentValue = event.target.value;
// PREVENTS USER OF ENTERING MORE THAN ONE DECIMAL_SEPARATOR
if (currentValue === "" || currentValue.indexOf(DECIMAL_SEPARATOR) >= 0) {
event.preventDefault();
}
else {
// AT THIS POINT I WOULD LIKE TO CONVERT THE THOUSAND_SEPARATOR INTO DECIMAL_SEPARATOR
}
}
}
return(
<input
type="text"
onChange={onChange}
onKeyDown={onKeyDown}
/>
);
}
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
Here is what happens now:
If it's not currency format that you want than you can do it like
function numberFormat(num) {
return num.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')
}
console.log(numberFormat(5000)); // 5,000.00
and you can decide if you want . or , by changing $1, or $1. here
num.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')
Simple way to do currency format is using the JavaScript Internationalization API and it is dynamic.
The Intl.NumberFormat object is a constructor for objects that enable language sensitive number formatting.
function App() {
let [inputValue, setInputValue] = React.useState("");
function onChange(event) {
let value = new Intl.NumberFormat("en-US").format((event.target.value).replace(/\D+/g, ""));
setInputValue(value === '0' ? '' : value);
}
return (
<div className="App">
<input type="text" onChange={onChange} value={inputValue} />
</div>
);
}
replace with empty string if not number
(event.target.value).replace(/\D+/g, "")
check working example here
This is in USD, however.
Different countries have different conventions to display values.
JavaScript makes it very easy for us with the ECMAScript Internationalization API, a relatively recent browser API that provides a lot of internationalization features, like dates and time formatting.
It is very well supported:
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2
})
formatter.format(1000) // "$1,000.00"
formatter.format(10) // "$10.00"
formatter.format(123233000) // "$123,233,000.00"
The minimumFractionDigits property sets the fraction part to be always at least 2 digits. You can check which other parameters you can use in the NumberFormat MDN page.
This example creates a number formatter for the Euro currency, for the Italian country:
const formatter = new Intl.NumberFormat('it-IT', {
style: 'currency',
currency: 'EUR'
})
Well, from this issue:
https://github.com/facebook/react/issues/10135
It seems that what I need is not possible. I was able to trigger the onChange method by dispatching a change event by doing this (suggested in the issue):
function onKeyDown(event) {
if (event.key === ",") {
event.preventDefault();
const previousValue = input_ref.current.value;
const caretPosition = input_ref.current.selectionStart;
const point = ".";
const newValue = [previousValue.slice(0, caretPosition), point, previousValue.slice(caretPosition)].join('');
input_ref.current.value = newValue;
const tracker = input_ref.current._valueTracker;
if (tracker) {
tracker.setValue(previousValue);
}
input_ref.current.dispatchEvent(new Event('change', { bubbles: true }));
}
}
This indeed triggers the onChange event. But you get a caret jump. So, it's not exactly as if the user had typed the key. You end up hard-changing the input value and get a caret jump anyway.
So I've ended up doing this:
https://codesandbox.io/s/broken-forest-1e3g3
import React, { useState, useRef } from "react";
import "./styles.css";
const DECIMAL_SEPARATOR = ".";
const THOUSAND_SEPARATOR = ",";
export default function App() {
console.log("Rendering App...");
const [inputValue, setInputValue] = useState("");
const input_ref = useRef(null);
function __setCaretPosition(caretPos) {
if (input_ref.current.createTextRange) {
var range = input_ref.current.createTextRange();
range.move("character", caretPos);
range.select();
} else {
if (input_ref.current.selectionStart) {
input_ref.current.focus();
input_ref.current.setSelectionRange(caretPos, caretPos);
} else {
input_ref.current.focus();
}
}
}
function onChange(event, moveCaret = false) {
console.log("From onChange...");
const newValue = event.target.value;
const caretPosition = input_ref.current.selectionStart;
setInputValue(newValue);
__setCaretPosition(caretPosition + (moveCaret ? 1 : 0));
setTimeout(() => {
__setCaretPosition(caretPosition + (moveCaret ? 1 : 0));
}, 0);
}
function onKeyDown(event) {
console.log("From onKeyDown...");
const inputValue = input_ref.current.value;
const caretPosition = input_ref.current.selectionStart;
if (event.key === THOUSAND_SEPARATOR) {
event.preventDefault();
const fakeEvent = {
target: {
value: [
inputValue.slice(0, caretPosition),
DECIMAL_SEPARATOR,
inputValue.slice(caretPosition)
].join("")
}
};
const moveCaret = true;
onChange(fakeEvent, moveCaret);
}
}
return (
<div className="App">
<input
type="text"
value={inputValue}
onChange={onChange}
onKeyDown={onKeyDown}
ref={input_ref}
/>
</div>
);
}

How to call a class and setState in one onClick(ReactJS)

I'm pretty new to React and I was wondering how I could call a function inside a class (so like this.handleChange) and an arrow function with a setState inside within one onClick of a button.
I've tried using commas and && in between the 2 lines of code.
<button name="Plus" onClick={() => this.setState({ Operator: 'plus' }) && this.handlePlus}>+</button>
The above code is inside the render and return.
The expected result is that the code calls the function and changes the state.Instead it just changes the state(so only does the first part).
Please help, thanks in advance!
setState() has an optional second parameter that is a function that will be invoked once the state has been set. You could do:
this.setState({ Operator: 'plus' }, this.handlePlus);
You can also use the componentDidUpdate lifecycle:
componentDidUpdate(prevProps, prevState) {
if(this.state.Operator === 'plus' && prevState.Operator !== 'plus') {
this.handlePlus();
}
}
< button name = "Plus"
onClick = {
() => this.setState({
Operator: 'plus'
}, () => this.handlePlus())
} > + < /button>
this would work fine.or
if you are looking
for something like this....
you can handle multiple buttonClick event like this
<
button name = "Plus"
value = "yourValue"
onClick = {
this.handleChange
} > + < /button>
handleChange = (e) => {
const {
name,
value
} = e.target;
switch (name) {
case 'Plus':
this.setState({
[name]: value
});
break;
default:
//enter code here
break;
}
}
Just add the this.setState({ Operator: 'plus' } within the handlePlus function.

prevent immediate evaluation

I'm building a dynamic form in react.
I try to bind checked value of a radio button but the comparaison seems to be executed immediatelly.
How do I differ comparasion ?
buildRadio(field) {
const radios = field.questionnairefieldlabelSet.map((label) => {
const radioValue = `${field.id}-${label.id}`
return <Form.Radio
key={radioValue}
label={label.text}
checked={this.state[field.id] === radioValue} // evaluated once, never changes on state change
value={radioValue}
onChange={(e, { value }) => {
this.setState({ [field.id]: value })}} />
})
return <Form.Group key={field.id}>
{radios}
</Form.Group>
}
I've tried to pass a function instead but I get an error message
checked={() => this.state[field.id] === radioValue}
Failed prop type: Invalid prop checked of type function supplied to Checkbox, expected boolean

Select in react does not reflect the state changes

Something weird is happening when I try to us select in reactjs.
Here is my code:
onDropDownChange=(e)=>{
let val = this.props.contactUsReducer.contactUsList.filter(function(item) {
return item.topic == e.target.value
});
let key= e.target.value;
let name= e.target.name;
console.log("key "+key+" val: "+val[0].displayName+" name:"+name);
this.setState(prevState => {
return ( {...prevState, [name]:key, displayName: val[0].displayName})
}, function () {
console.log(this.state);
});
}
and in my render I have
<select type="select" className="text-input flex-row-right-item" onChange={this.onDropDownChange} name="topic" value={this.state.displayName}>
{this.createSelectItems(this.props.contactUsReducer.contactUsList)}
</select>
Also here is my helper function:
createSelectItems=(itemsList)=> {
let items = [];
for (let i = 0; i < itemsList.length; i++) {
items.push(<option key={itemsList[i].topic} value={itemsList[i].topic}>{itemsList[i].displayName}</option>);
}
return items;
}
Now when I try to change the select box nothing will change in the UI so select box does not show the updated selection but I clearly see in the state that the state has been updated. Why the changes does not get reflected in select box.
Any idea?
For some reason you are supplying a function to your setState function. I personally find making a copy of the state and setting the properties to work better.
onDropDownChange = e => {
let val = this.state.myArr.filter(function(item) {
return item.topic === e.target.value;
});
let key = e.target.value;
let name = e.target.name;
console.log(
"key " + key + " val: " + val[0].displayName + " name:" + name
);
let prevState = { ...this.state };
prevState[name] = key;
prevState["displayName"] = val[0].displayName;
this.setState({ ...this.state, prevState });
console.log(this.state);
};
Change value set to displayName in your setState as,
this.setState(prevState => {
return ( {...prevState, [name]:key, displayName: key})
}, function () {
console.log(this.state);
});
Because the value prop should have the value of the selected option
The value on the select should be one of the values on the options tags, but as values on those options you're mapping the topic but you made value={this.state.displayName} on the select tag ?
if you replace it with value={this.state.topic} it will work since it uses the same data mapping.
here is a codesandbox exp.

React.js Understanding setState

I've been having a go at learning React.js by writing a small calculator application. I thought things were going quite well until I learned that setState is asynchronous and my mutations therefore do not get immediately applied.
So my question is, what is the best way to keep a running total based upon the values being added to an input. Take the following example:
var Calculator = React.createClass({
total : 0,
getInitialState : function(){
return {
value : '0'
};
},
onValueClicked : function (value) {
var actual, total, current = this.state.value;
if(value === '+') {
actual = this.total = parseInt(this.total, 10) + parseInt(current, 10);
} else {
if(parseInt(current, 10) === 0) {
actual = value;
} else {
actual = current.toString() + value;
}
}
this.setState({ value : actual });
},
render : function () {
return (
<div className="calc-main">
<CalcDisplay value={this.state.value} />
<CalcButtonGroup range="0-10" onClick={this.onValueClicked} />
<CalcOpButton type="+" onClick={this.onValueClicked} />
</div>
)
}
});
var CalcDisplay = React.createClass({
render : function () {
return (
<input type="text" name="display" value={this.props.value} />
);
}
});
var CalcButtonGroup = React.createClass({
render : function () {
var i, buttons = [], range = this.props.range.split('-');
for(i = range[0]; i < range[1]; i++) {
var handler = this.props.onClick.bind(null, i);
buttons.push(<CalcNumberButton key={i} onClick={ handler } />);
}
return (
<div className="calc-btn-group">{ buttons }</div>
);
}
});
var CalcNumberButton = React.createClass({
render : function () {
return (
<button onClick={this.props.onClick}>{this.props.key}</button>
);
}
});
var CalcOpButton = React.createClass({
render : function () {
var handler, op = this.props.type;
handler = this.props.onClick.bind(null, op);
return (
<button onClick={handler}>{op}</button>
);
}
});
React.renderComponent(<Calculator />, document.getElementById('container'));
In the example above I gave up completely on storing the total within the state and kept it outside. I've read that you can have a callback run when setState has finished but in the case of a calculator I need it to be snappy and update quickly. If the state isn't getting updated with each button press and I quickly hit the buttons - things are going to fall out of sync. Is the callback all I am missing or am I thinking about this in completely the wrong way?
Any help appreciated!
It's asynchronous, but much faster than the fastest possible human click.
Aside from that, you should declare instance variables in componentDidMount, e.g.
componentDidMount: function(){
this.total = 0;
}
... but in this case you probably want to store it in state.
.split returns an array of strings, you want to be using numbers:
range = this.props.range.split('-').map(Number)
Or avoid the strings altogether (prefered) with one of these:
<CalcButtonGroup range={[0, 10]} onClick={this.onValueClicked} />
<CalcButtonGroup range={{from: 0, till: 10}} onClick={this.onValueClicked} />
You have define the total variable for your business logic state. Why not store more information like that?
var Calculator = React.createClass({
previous: 0, // <-- previous result
current: 0, // <-- current display
op: '', // <-- pending operator
getInitialState : function(){
return {
value : '0'
};
},
onValueClicked : function (value) {
var actual;
if(value === '+') {
this.previous = this.current;
this.op = '+';
actual = 0; // start a new number
} else if (value === '=') {
if (this.op === '+') {
actual = this.previous + this.current;
} else {
actual = this.current; // no-op
}
} else {
actual = current * 10 + value;
}
this.current = actual; // <-- business logic state update is synchronous
this.setState({ value : String(actual) }); // <-- this.state is only for UI state, asynchronous just fine
},
render : function () {
return (
<div className="calc-main">
<CalcDisplay value={this.state.value} />
<CalcButtonGroup range="0-10" onClick={this.onValueClicked} />
<CalcOpButton type="+" onClick={this.onValueClicked} />
<CalcOpButton type="=" onClick={this.onValueClicked} />
</div>
)
}
});
The basic idea to resolve this.state is use other variables to store your business logic state, and reserve this.state only for UI state.
PS. A real calculator has more complex business logic than this. You should define every state and state machine clearly in spec.

Categories