I´m trying to make a React calculator. It´s mostly done, but I have one problem I don´t know how to correct: I can´t seem to limit the number of operators an user can enter (for instance, I want to limit "++++" to just "+" and also prevent two operators getting joined: +- must become -). I tried resetting the state everytime an user enters an operator, but no dice. I´m seriously lost here. I thought about a Regex, but it seems to be rather problematic (way too many contexts to try).
class Calculator extends Component {
constructor(props) {
super(props);
this.state = { value: '' };
this.handleClick = this.handleClick.bind(this);
}
handleClick(evt) {
const id = evt.target.id;
const result = evt.target.value;
this.setState(prevState => ({
value: `${prevState.value}${result}`.replace(/^0+\B/, '')
}));
if (id === 'equals') {
this.setState({ value: math.eval(this.state.value) });
} else if (id === 'clear') {
this.setState({ value: 0 });
}
}
}
You could use regular expressions to solve this. The main problem with using the includes() based approach is that it does not enforce a correct format in the input string. Perhaps you could use a regular expression like this?
/^\d*([/\+-/*=]\d+)*$/gi
This would prevent problems like multiple operands like +++, and so on:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = { value: "" };
this.handleClick = this.handleClick.bind(this);
}
handleClick(evt) {
const result = evt.target.value;
// Update state
this.setState({
value: result
});
// Comine previous state with input value
//const combination = `${ this.state.value }${result}`;
console.log(result, 'combination', result)
// Use regular expression to check valid input. If invalid
// prevent further processing
if(!result.match(/^\d*([/\+-/*=]\d+)*$/gi)) {
console.error('Invalid input')
return
}
this.setState({ calculated: eval(result) });
}
render() {
return <h1>
<input value={this.state.value} onChange={(e) => this.handleClick(e)}/>
<p>{ this.state.calculated }</p>
</h1>
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
There is a functioning JSFiddle here for you to try out
You could save a list with operations and the crrently typed character. Then before inserting check if it's an operation to prevent adding multiple of them
example
const ops = ['/', '+', '-', '^']
this.setState({lastChar: result})
// Before setState with the full formula
if(this.state.lastChar === result && ops.includes(result)) else if (ops.includes(this.state.lastChat) && ops.includes(result)) return;
assuming result is only the typed/clicked character
problems
This strategy is definitely not perfect. For example, typing 1+-2 or 1*-3 wouldn't work. But from here on you could tweak it to fit your needs
I'm on my phone, sorry for the bad formatting
Related
I'm working on a React component that the user can dynamically add / remove children (kind of like a todo-list style).
The challenge is that the user can click a button to add in / remove children and, so, any new child may not have anything unique about it to set as the key element (aside from, maybe, the creation time) and, since, the user can delete any one of these component and, then, re-add more, the index won't work.
Here's what I've come up with that seems to work:
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: [ XXXX ]
};
}
addSection(title, content, key = this.generateNewKeyVal()) {
this.setState({
data:
[...this.state.data,
{
title: title,
content: content,
key: key
}
]
});
}
generateNewKeyVal() {
if(this.state.data.length === 0)
return 1;
return Math.max.apply(Math, this.state.data.map(d => d.key)) + 1;
}
removeFunc(key) {
this.setState({
data: this.state.data.filter(e1 => e1.key !== key)
});
}
}
export default ParentComponent;
As I said, my generateNewKeyVal() function seems to work perfectly since it ensures it generates a new, unique integer value for key (based upon the values currently in the array, that is) and, so long as that key remains in the array, the function will ensure a higher number will be created for a new item's key.
My challenge is that I'm SO new to React that I'd like to make sure I'm not making some huge mistake here or if there is a better way to generate a key in this kind of situation.
I am trying to have a input field that does not allows user to type numeric value. (A non-numeric or a special character value will not be even allow to be typed).
One of the common approach on SO is the <input type="number" />. However, it suffers from two things:
1: The arrow: which I am able to get rid of following another stack post, which is not a problem.
2: The input field still allows negative sigh "-" as input.
I also tried: <input type="text" pattern="[0-9]*" onChange={this.handleInputToken}/>
handleInputToken(e){
console.log(e.currentTarget.value)
}
But this still allows non-numeric input
Is there an existing npm module or library that allows this simple implementation? Since it feels like a very common stuff with form data.
You can build a typed input component and have whatever logic.
This is very, very basic and not tested but will refuse to accept anything that does not coerce to a number and keep the old value, effectively silently dropping anything bad. it will still accept negative numbers, but you can extend the handleChange to fix it
class NumericInput extends React.Component {
state = {
value: ''
}
handleChange = e => {
const { onChange } = this.props;
const value = e.target.value.trim();
const num = Number(value);
let newValue = this.state.value;
if (!isNaN(num) && value !== '') {
newValue = num;
}
else if (value === ''){
newValue = value;
}
this.setState({
value: newValue
}, () => onChange && onChange(this.state.value));
}
render() {
const { onChange, ...rest } = this.props;
return <input {...rest} value={this.state.value} onChange={this.handleChange} />;
}
}
https://codesandbox.io/s/zq5ooml10l
obviously, you can use static getDerivedStateFromProps() to keep a value={} in sync from upstream and have it as a true 'controlled' component
Try something like this:
<input
type="number"
min="0"
value={this.state.number}
onChange={this.handleNumberChange}
/>
handleNumberChange(e) {
const asciiVal = e.target.value.charCodeAt(0);
if (
!(
(asciiVal > 95 && asciiVal < 106) ||
(asciiVal > 47 && asciiVal < 58) ||
asciiVal == 8
)
) {
return false;
}
this.setState({ number: e.target.value });
}
As soon as you try entering negative number, it will return false and the value won't be set in the input field
Having a massive nightmare, trying to calculate the sum of an array (of numbers), in ReactJS/State.
The code I've posted below works, if I'm not using state.
Basically, via a form a user enters a number and then submits.
What the user types, is watched via handleChange.
And then on handlSubmit, the number/value is stored in this.state.donated and this.state.sum. These are arrays.
When I look at console, both states, store an array of numbers. And collect each entry into the array fine.
I want to find and print the total of these numbers. So I'm using:
// FUNCTION TO CALCULATE TOTAL DONATIONS
const numbers = this.state.sum;
function add(a, b) {
return a + b;
}
// // TOTAL VALUE OF NUMBERS IN THE ARRAY
const cal = numbers.reduce(add, 0);
console.log('CALC', cal);
However, the problem I'm having is, if I submit for example, number 20. The sum function, console logs 0 first. When I enter a second number, it then console logs the first number I entered...
What am I doing wrong? There's my full/relevant code:
class App extends React.Component {
constructor() {
super();
this.state = {
number: '',
donated: [],
sum: [],
total: 0
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
// we need to bind otherwise this is undefined
}
//HANDLE FUNCTIONS
handleChange(e) {
this.setState({ number: e.target.value }, () =>
console.log('NUMBER', this.state.number));
}
handleSubmit(e) {
e.preventDefault();
this.setState({donated: this.state.donated.concat(this.state.number).map(Number)}, () => console.log('DONATED', this.state.donated));
this.setState({sum: this.state.donated.concat(this.state.number).map(Number)}, () => console.log('SUM', this.state.sum));
// FUNCTION TO CALCULATE TOTAL DONATIONS
const numbers = this.state.sum;
function add(a, b) {
return a + b;
}
// TOTAL VALUE OF NUMBERS IN THE ARRAY
const cal = numbers.reduce(add, 0);
console.log('CALC', cal);
document.forms['id_form'].reset();
}
render() {
return (
<main>
<section className="section">
<h1 className="is-size-2">DONATE FOR A GOOD CAUSE</h1>
<ProgressBar donated={this.state.donated} sum={this.state.sum}/>
<Form donated={this.state.donated} handleChange={this.handleChange} handleSubmit={this.handleSubmit} />
</section>
</main>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
According to official documentation
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
Use setState callBack to calculate sum to get updated values
this.setState({sum: this.state.donated.concat(this.state.number).map(Number)}, () => {
console.log('SUM', this.state.sum);
// FUNCTION TO CALCULATE TOTAL DONATIONS
const numbers = this.state.sum;
function add(a, b) {
return a + b;
}
// TOTAL VALUE OF NUMBERS IN THE ARRAY
const cal = numbers.reduce(add, 0);
console.log('CALC', cal);
document.forms['id_form'].reset();
});
or you can simply update a sum variable after every new value submission. like this
setState({sum: this.state.sum + this.state.number, number: 0});
I'm new to javascript and I want to run some code depending if the state.value != null or "".it doesn't throw an error but freezes there. please see my code down below.any help would be really appreciated.
constructor(){
super();
this.state = {
value:null,
list:[]
}
}
handleList = () => {
//let list = this.state.list.slice();
if (this.state.value != null || this.state.value.length() > 0 ) {
let list = [...this.state.list];
list.push(<li>{this.state.value}</li>);
this.setState({list});
console.log(list.length,this.state.list.length);
}else{
console.log("Cant enter null");
}
}
render() {
return(
<div className = 'global'>
<button onClick={() => {this.handleList()}
}>Add-New</button>
<input
onChange = {
(e)=>{this.setState({value: e.target.value})}
}
type = 'text'
placeholder = 'Enter New Todo!'/>
<hr/>
<ul>
{
this.state.list.map((li) => {
return (li);
})
}
</ul>
</div>
);
}
}
Evaluating the existence of Strings
In JavaScript: empty Strings '' are falsey (evaluate to false).
const x = ''
if (x) console.log('x = true')
else console.log('x = false')
As a result, the existence of this.state.value be tersely verified as follows:
if (this.state.value) .. // Do something if this.state.value != ''
This strategy can be leveraged and chained by simply referencing variables followed by && (which results in only the last truthy variable being returned). If no truthy variable is found: false is returned. ie. in the case of the onClick method of the <button/> tag below.
Rendering Lists
In React: it is typical to store lists of plain variables (Strings, Objects, etc) and handle conversion to element form on the fly.
Rendering Strings representing HTML elements is a security flaw. In production: someone could very easily type a malicious todo and ruin your entire application. You may need to use dangerouslySetInnerHTML if you wish to continue down that path.
See the docs for more info on how to render lists.
Example
See below for a rough example of a todo container.
// Container.
class Container extends React.Component {
// Constructor.
constructor(props) {
super(props)
this.state = {
value: '',
list: []
}
}
// Render.
render = () => (
<div className = 'global'>
<button onClick={() => this.state.value && this.setState({value: null, list: [...this.state.list, this.state.value]})}>Add</button>
<input value={this.state.value} onChange={(e) => this.setState({value: event.target.value})} placeholder="Todo.."/>
<hr/>
<ul>
{(this.state.list.length > 0 && (this.state.list.map((todo, index) => <li key={index}>{todo}</li>))) || '..'}
</ul>
</div>
)
}
// Mount.
ReactDOM.render(<Container/>, document.querySelector('#root'))
<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="root"></div>
Because you are using OR, both criteria are checked. So even if value is NULL, the code is still attempting to check the length of the string. But a NULL object doesn't have a "length" property, so this will result in an "value does not have property: length" error. To fix this, using AND ( && ) would be more appropriate.
Additionally, the "length" property is a value, not a function, so attempting to call as function will result in an "length is function of value" error.
These errors should appear in the console when viewing your web-page. If you press F12, a window should appear at the bottom of your browser. If you then select the console tab, you should be able to see all errors output. You might need to make sure you aren't filtering error messages.
I am trying to remove a value from my state.
I am using .filter as I believe this is the simplest way of doing it. I also want to implement an undo function ( but that's outside the scope of this question).
I have put this code in a sandbox
https://codesandbox.io/s/yrwo2PZ2R
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: x.movies,
};
}
remove = (e) => {
e.preventDefault();
console.log('remove movie.id:', e.target.value)
const index = e.target.value
this.setState({
movies: this.state.movies.filter((_, e) => e.id !== index)
});
}
render() {
return (
<div>
{this.state.movies.map(e =>
<div key={e.id}>
<li>{e.name} {e.id}</li>
<button value={e.id} onClick={this.remove}>remove</button>
</div>,
)}
</div>
);
}
}
Two problems.
First of all, the index you're getting from the event target value is a string, but you're comparing against a number. Change the index declaration as follows:
const index = Number(e.target.value);
Secondly, your filter is a little off. This will work:
this.state.movies.filter(movie => movie.id !== index)
The problem is index has string type, but id in objects has number type. You need type cast, for example:
const index = Number(e.target.value);
Other than that, you have some wrong _ in callback of filter function call. You don't need it. You need:
this.state.movies.filter(e => e.id !== index)
By the way I don't recommend to name values this way. Why e? You have array of movies. Use movie. Why index? You have id to remove. Then use idToRemove name.
You also have problem with adding items.
Firstly, you can add items like this:
this.setState({
movies: [...this.state.movies, { name: item.value.name, id: item.value.id }],
})
Another point: you have to autoincrement id. You can store last value in a variable. this.idCounter for example. And add will look like:
this.setState({
movies: [...this.state.movies, { name: item.value.name, id: this.idCounter++ }],
})
Example: https://codesandbox.io/s/2vMJQ3p5M
You can achieve the same in the following manner
remove = (e) => {
e.preventDefault();
console.log('remove movie.id:', e.target.value)
const index = e.target.value
var movies = [...this.state.movies]
var idx = movies.findIndex((obj) => obj.id === parseInt(index))
movies.splice(idx, 1);
this.setState({
movies
});
}
Also use parseInt to convert index to a string before comparing.
Directly setting the current state from the previous state values can cause problems as setState is asynchronous. You should ideally create a copy of the object and delete the object using splice method
CODESANDBOX