5
\$\begingroup\$

My company is going to make a big leap soon, and since we are currently still running on AngularJS 1.3.X we decided to make the switch to react for a myriad of reasons besides the obvious. Being the lead developer I decided to start messing around a bit.

After my first bit of research and hello world apps, I got to work on this calculator. It was made in about 30 minutes.

I was curious about my use of the state type properties, and if its the right way to tackle these kind of things. Any and all input is very much appreciated!

var Calculator = React.createClass({
    getInitialState: function() {
        return {
            currentNumber: 0,
        }
    },
    addDigit: function(n) {
        if(this.state.currentNumber == 0){
            this.setState({
                currentNumber: n 
            })
        } else {
            this.setState({
                currentNumber: "" + this.state.currentNumber + n
            })
        }
    },
    setOperator: function(f) {
        if(this.state.currentNumber.slice(-1) != " "){
            this.setState({
                currentNumber: "" + this.state.currentNumber + " " + f + " "
            })
        }
    },
    solve: function() {
        this.setState({
            currentNumber: eval(this.state.currentNumber)
        })
    },
    clear: function() {
        this.setState({
            currentNumber: 0
        })
    },
    render: function() {
        return (
            <div>
                <h1>This here Cal-Q-lator</h1>
                <div>
                    <input value={this.state.currentNumber}/>
                    <div>
                        <button onClick={this.clear} className="btn-1">C</button>
                        <button onClick={()=>this.setOperator('/')} className="btn-1">/</button>
                        <button onClick={()=>this.setOperator('*')} className="btn-1">*</button>
                        <button onClick={()=>this.setOperator('-')} className="btn-1">-</button>
                    </div>
                    <div>
                        <button onClick={()=>this.addDigit(7)} className="btn-1">7</button>
                        <button onClick={()=>this.addDigit(8)} className="btn-1">8</button>
                        <button onClick={()=>this.addDigit(9)} className="btn-1">9</button>
                        <button onClick={()=>this.setOperator('+')} className="btn-1">+</button>
                    </div>
                    <div>
                        <button onClick={()=>this.addDigit(4)} className="btn-1">4</button>
                        <button onClick={()=>this.addDigit(5)} className="btn-1">5</button>
                        <button onClick={()=>this.addDigit(6)} className="btn-1">6</button>
                        <button onClick={()=>this.setOperator('+')} className="btn-1">+</button>
                    </div>
                    <div>
                        <button onClick={()=>this.addDigit(1)} className="btn-1">1</button>
                        <button onClick={()=>this.addDigit(2)} className="btn-1">2</button>
                        <button onClick={()=>this.addDigit(3)} className="btn-1">3</button>
                        <button onClick={this.solve} className="btn-1">E</button>
                    </div>
                    <div>
                        <button onClick={()=>this.addDigit(0)} className="btn-1">0</button>
                        <button onClick={()=>this.addDigit(0)} className="btn-1">0</button>
                        <button onClick={()=>this.addDigit('.')} className="btn-1">.</button>
                        <button onClick={this.solve} className="btn-1">E</button>
                    </div>
                </div>
            </div>
        )
    }
});

ReactDOM.render(
    <Calculator />,
    document.getElementById('root1')
);

I used React, ReactDOM and Babel.

\$\endgroup\$
3
\$\begingroup\$

Fully functional example :

class Calculator extends React.Component {
	constructor(props) {
		super(props)

		this.state = {
			currentNumber: ''
		}
	}

	addChar = char => ev => {
		if (!(this.state.currentNumber || ' ').endsWith(' ') || !char.endsWith(' ')){
			this.setState(prevState => ({
				currentNumber: prevState.currentNumber + char
			}))
		}
	}

	solve = () => {
		this.setState({
			currentNumber: eval(this.state.currentNumber)
		})
	}

	clear = () => {
		this.setState({
			currentNumber: ''
		})
	}

	render() {
		const layout = [
			[
				{ name: 'C', func: this.clear },
				{ name: ' / ' },
				{ name: ' * ' },
				{ name: ' - ' },
			], 
			[
				{ name: '7' },
				{ name: '8' },
				{ name: '9' },
				{ name: ' + ' },
			], 
			[
				{ name: '4' },
				{ name: '5' },
				{ name: '6' },
				{ name: ' + ' },
			], 
			[
				{ name: '1' },
				{ name: '2' },
				{ name: '3' },
				{ name: 'E', func: this.solve },
			],
			[
				{ name: '0' },
				{ name: '0'},
				{ name: '.' },
				{ name: 'E', func: this.solve },
			]
		]

		return (
			<div>
				<h1>This here Cal-Q-lator</h1>
				<div>
					<input value={this.state.currentNumber || '0'} />
					{layout.map((line, i) => 
						<div key={i}>
							{line.map((field, i2) => <button key={i2} onClick={field.func || this.addChar(field.name)} className="btn-1">{field.name.trim()}</button>)}
						</div>
					)}
				</div>
			</div>
		)
	}
}

ReactDOM.render(
	<Calculator />,
	document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'/>

For this review I will be using a class extending React.Component.

First, your initial data. By default your calculator will be showing a 0 in the input field. Since this value is simply going to be deleted once the user types anything into the calculator interface, I suggest setting the default value of your input into an empty string, and just showing a 0 on the render side when the string is empty :

State:

constructor(props) {
   super(props)

   this.state = {
       currentNumber: ''
   }
}

Clear function:

clear = () => {
    this.setState({
        currentNumber: ''
    })
}

Render:

<input value={this.state.currentNumber || '0'} />

Next up, handling operators and values. Both operators and value will do the same thing, adding characters to your currentNumber, the only difference being that operators will add spaces and cannot be displayed next to each other or at the beginning of the input.

When setting a state value depending on an older one, you should avoid using the raw this.state and use the callback version of setState, allowing you to get the previous state and avoid any unexpected behavior :

addChar = char => ev => {
    if (!(this.state.currentNumber || ' ').endsWith(' ') || !char.endsWith(' ')){
        this.setState(prevState => ({
            currentNumber: prevState.currentNumber + char
        }))
    }
}

Finally, layout.

Every single button is made out of the same node, with the same classname. This part of the code can be drastically reduced. First, let's make an array containing what differs between every button: what is shown, and the action it does when you click it:

const layout = [
    [
        { name: 'C', func: this.clear },
        { name: ' / ' },
        { name: ' * ' },
        { name: ' - ' },
    ],
    [
        { name: '7' },
        { name: '8' },
        { name: '9' },
        { name: ' + ' },
    ],
    [
        { name: '4' },
        { name: '5' },
        { name: '6' },
        { name: ' + ' },
    ],
    [
        { name: '1' },
        { name: '2' },
        { name: '3' },
        { name: 'E', func: this.solve },
    ],
    [
        { name: '0' },
        { name: '0' },
        { name: '.' },
        { name: 'E', func: this.solve },
    ]
]

By default, every button will call the addChar function we made earlier, allowing use to reduce the code even further and make an nested array of JSON objects containing mostly button names.

We can now render it by mapping every element to make buttons:

{layout.map((line, i) => 
    <div key={i}>
        {line.map((field, i2) => <button key={i2} onClick={field.func || this.addChar(field.name)} className="btn-1">{field.name.trim()}</button>)}
    </div>
)}

Yes, I know this question is from 2017

\$\endgroup\$
  • \$\begingroup\$ I've worked with React after I posted this question for more then a year, I have to say it's nice to see my old code and how I've improved. Still, this gave some more ideas to optimise my logic in the future, Thanks for taking the time to answer this question :) \$\endgroup\$ – Stagg Jan 11 at 10:04
  • \$\begingroup\$ You're welcome, glad it helped a year after posting it :) \$\endgroup\$ – Treycos Jan 11 at 11:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.