In this tutorial, we will be building a simple calculator app using React and Redux. The calculator will have basic functionality such as addition, subtraction, multiplication, and division. The app will also have a history panel to display the previous calculations.
Prerequisites
To follow along with this tutorial, it is recommended to have a basic understanding of JavaScript, React, and Redux. You will also need to have Node.js installed on your machine.
Setting up the Project
First, let’s set up the project by creating a new directory and initializing a new React project:
mkdir calculator-app
cd calculator-app
npx create-react-app .
Now, let’s install the required dependencies for Redux:
npm install redux react-redux
Project Structure
Inside the src
directory, delete all the default files except for App.js
and index.js
.
Create a new directory called redux
inside the src
directory. Inside the redux
directory, create the following files:
-
actions.js
: This file will define the actions for our calculator app. -
reducers.js
: This file will define the reducers for our app. -
store.js
: This file will create the Redux store.
Finally, update the App.js
and index.js
files to reflect the following changes.
App.js
:
import React from 'react';
import Calculator from './Calculator';
function App() {
return (
<div className="App">
<Calculator />
</div>
);
}
export default App;
index.js
:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import './index.css';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Building the Calculator Component
Now, let’s start building the calculator component. Create a new file called Calculator.js
in the src
directory and update it with the following code:
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { addNumber, clear, equals } from './redux/actions';
function Calculator(props) {
const [input, setInput] = useState('');
const handleClick = (value) => {
setInput(input + value);
};
const handleClear = () => {
setInput('');
props.clear();
};
const handleEqual = () => {
props.equals(input);
setInput('');
};
return (
<div>
<input type="text" value={input} disabled />
<br />
<button onClick={() => handleClick('1')}>1</button>
<button onClick={() => handleClick('2')}>2</button>
<button onClick={() => handleClick('3')}>3</button>
<button onClick={() => handleClick('4')}>4</button>
<br />
<button onClick={() => handleClick('5')}>5</button>
<button onClick={() => handleClick('6')}>6</button>
<button onClick={() => handleClick('7')}>7</button>
<button onClick={() => handleClick('8')}>8</button>
<br />
<button onClick={() => handleClick('9')}>9</button>
<button onClick={() => handleClick('0')}>0</button>
<button onClick={() => handleClick('+')}>+</button>
<button onClick={() => handleClick('-')}>-</button>
<br />
<button onClick={() => handleClick('*')}>*</button>
<button onClick={() => handleClick('/')}>/</button>
<button onClick={handleClear}>Clear</button>
<button onClick={handleEqual}>=</button>
</div>
);
}
const mapDispatchToProps = (dispatch) =>
bindActionCreators({ addNumber, clear, equals }, dispatch);
export default connect(null, mapDispatchToProps)(Calculator);
In the Calculator
component, we are using the useState
hook to manage the input value. We have also defined three event handlers:
-
handleClick
: This function appends the clicked value to the input string. -
handleClear
: This function clears the input string and calls theclear
action. -
handleEqual
: This function triggers theequals
action with the current input string.
We are also mapping the addNumber
, clear
, and equals
actions to the component props using the mapDispatchToProps
function.
Now, let’s define the actions in the actions.js
file.
actions.js
:
export const addNumber = (number) => {
return {
type: 'ADD_NUMBER',
payload: number,
};
};
export const clear = () => {
return {
type: 'CLEAR',
};
};
export const equals = (input) => {
return {
type: 'EQUALS',
payload: input,
};
};
The addNumber
action takes a number
argument and returns an action object with the ADD_NUMBER
type and the number as the payload.
The clear
action does not take any arguments and returns an action object with the CLEAR
type.
The equals
action takes an input
argument and returns an action object with the EQUALS
type and the input as the payload.
Next, let’s define the reducers in the reducers.js
file.
reducers.js
:
const initialState = {
history: [],
result: 0,
};
const calculatorReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_NUMBER':
return {
...state,
result: Number(state.result + action.payload),
};
case 'CLEAR':
return {
...state,
result: 0,
};
case 'EQUALS':
const result = eval(action.payload);
return {
...state,
history: [...state.history, action.payload + ' = ' + result],
result: result,
};
default:
return state;
}
};
export default calculatorReducer;
The calculatorReducer
function takes the state
and action
as inputs and returns the updated state based on the action type. In the case of the ADD_NUMBER
action, the result
is updated by adding the payload to the current result. In the case of the CLEAR
action, the result
is reset to 0. In the case of the EQUALS
action, the input is evaluated using the eval
function and the result is added to the history array.
Finally, let’s create the Redux store in the store.js
file.
store.js
:
import { createStore } from 'redux';
import calculatorReducer from './reducers';
const store = createStore(calculatorReducer);
export default store;
The createStore
function from Redux is used to create the store by passing in the calculatorReducer
function.
Displaying the Results
In the Calculator
component, let’s add a new section to display the previous calculations.
Update the Calculator
component with the following code:
function Calculator(props) {
// ...
return (
<div>
<input type="text" value={input} disabled />
<br />
{/* ... */}
<br />
<button onClick={handleClear}>Clear</button>
<button onClick={handleEqual}>=</button>
<br />
<h3>History</h3>
{props.history.map((item, index) => (
<p key={index}>{item}</p>
))}
</div>
);
}
In the JSX code, we have added a new heading element <h3>
to display “History” and a mapping function to render each item of the history
array as a separate paragraph element <p>
. The key
prop is set to the index
to provide a unique identifier for each item.
Also, in the mapStateToProps
function in the Calculator.js
file, let’s add the following code:
const mapStateToProps = (state) => {
return {
history: state.history,
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Calculator);
The mapStateToProps
function maps the history
array from the state to the history
prop of the calculator component.
Now, when you run the app using npm start
, you should see the calculator interface with an input field to enter values and buttons to perform operations. The history panel will display the previous calculations.
Running the App
To start the development server and run the app, open your terminal and run the following command in the project directory:
npm start
Now, the calculator app should be running on `http://localhost:3000`.
Conclusion
In this tutorial, you have learned how to build a simple calculator app using React and Redux. You have implemented the basic functionality of the calculator, including addition, subtraction, multiplication, and division. You have also added a history panel to display the previous calculations. By following this tutorial, you can further enhance the calculator app by adding more advanced operations or improving the user interface.