In the fast-paced world of cryptocurrency, keeping track of your investments can feel like navigating a maze. Prices fluctuate wildly, new coins emerge constantly, and the sheer volume of information can be overwhelming. Wouldn’t it be great to have a simple, easy-to-use tool that allows you to monitor your cryptocurrency holdings in real-time? This is where a React-based cryptocurrency portfolio tracker comes in. This guide will walk you through building your own, step-by-step, providing you with a practical project to hone your React skills while gaining a valuable tool for managing your digital assets. We’ll break down complex concepts into manageable chunks, making it accessible for beginners while offering valuable insights for those with some React experience. Let’s dive in!
Why Build a Cryptocurrency Portfolio Tracker?
Beyond the obvious benefit of tracking your investments, building a portfolio tracker offers several advantages. Firstly, it’s an excellent learning opportunity. By working on a real-world project, you’ll gain hands-on experience with core React concepts like component creation, state management, and API integration. Secondly, it allows you to customize the tracker to your specific needs. You can choose which cryptocurrencies to monitor, how the data is displayed, and what features to include. Finally, it provides a sense of accomplishment and a tangible product you can use daily. This project isn’t just about learning; it’s about creating something useful.
Prerequisites
Before we begin, ensure you have the following:
- A basic understanding of HTML, CSS, and JavaScript.
- Node.js and npm (or yarn) installed on your computer.
- A code editor (like VS Code, Sublime Text, or Atom).
- A free API key from a cryptocurrency data provider (we’ll use CoinGecko for this example).
Don’t worry if you’re not an expert in all these areas. This guide is designed to be beginner-friendly. We’ll explain the concepts as we go, and provide clear instructions to get you started.
Setting Up Your React Project
Let’s start by creating a new React project using Create React App. Open your terminal and run the following command:
npx create-react-app cryptocurrency-portfolio-tracker
This command sets up a new React project with all the necessary files and dependencies. Once the installation is complete, navigate into your project directory:
cd cryptocurrency-portfolio-tracker
Now, start the development server:
npm start
This will open your React app in your web browser (usually at http://localhost:3000). You should see the default Create React App welcome screen. Now, let’s clean up the project. Open the src folder in your code editor and delete the following files: App.css, App.test.js, index.css, logo.svg, and reportWebVitals.js. Then, modify App.js and index.js to remove the unused imports and content. Your App.js should look something like this:
import React from 'react';
function App() {
return (
<div>
<h1>Cryptocurrency Portfolio Tracker</h1>
</div>
);
}
export default App;
And your index.js should look like this:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Fetching Cryptocurrency Data with CoinGecko API
We’ll use the CoinGecko API to fetch cryptocurrency data. CoinGecko provides a free API with various endpoints for accessing real-time cryptocurrency prices, market capitalization, and other relevant information. First, you need to sign up for a free API key on the CoinGecko website. Once you have your API key, you’re ready to start fetching data. We will use the useState and useEffect hooks to manage the data fetching and component rendering.
Let’s create a new component called CryptoList to handle the data fetching and display. Create a new file named CryptoList.js in the src directory and add the following code:
import React, { useState, useEffect } from 'react';
function CryptoList() {
const [cryptoData, setCryptoData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(
'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false'
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setCryptoData(data);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h2>Cryptocurrency Prices</h2>
<ul>
{cryptoData.map((crypto) => (
<li key={crypto.id}>
<img src={crypto.image} alt={crypto.name} style={{ width: '20px', marginRight: '5px' }} />
{crypto.name} ({crypto.symbol.toUpperCase()}): ${crypto.current_price}
</li>
))}
</ul>
</div>
);
}
export default CryptoList;
Let’s break down this code:
- We import
useStateanduseEffectfrom React. - We declare three state variables:
cryptoData(to store the fetched data),loading(to indicate whether the data is being fetched), anderror(to handle any errors during the fetching process). - The
useEffecthook is used to fetch data from the CoinGecko API when the component mounts. The empty dependency array[]ensures that the effect runs only once, when the component first renders. - Inside the
useEffecthook, we define an asynchronous functionfetchDatato fetch the data. - We use the
fetchAPI to make a GET request to the CoinGecko API endpoint. The URL includes parameters to fetch data for USD, order by market cap, limit to 100 coins, and disable sparkline charts. - We check if the response is successful using
response.ok. If not, we throw an error. - If the response is successful, we parse the JSON data and update the
cryptoDatastate usingsetCryptoData. We also setloadingtofalseto indicate that the data has been fetched. - If there’s an error during the fetching process, we catch the error, set the
errorstate, and setloadingtofalse. - We render a “Loading…” message while the data is being fetched and an error message if there’s an error.
- Finally, we map over the
cryptoDataarray and render a list of cryptocurrency prices. We display the coin image, name, symbol, and current price.
Now, import and use the CryptoList component in your App.js file:
import React from 'react';
import CryptoList from './CryptoList';
function App() {
return (
<div>
<h1>Cryptocurrency Portfolio Tracker</h1>
<CryptoList />
</div>
);
}
export default App;
Save both files and check your browser. You should now see a list of cryptocurrency prices fetched from the CoinGecko API.
Adding Portfolio Functionality
Now, let’s add the functionality to track a portfolio of cryptocurrencies. This involves allowing users to input the cryptocurrencies they own, the amounts, and then calculating the total value of their portfolio. We’ll need to create a form for users to input their holdings and a section to display the portfolio value.
Creating the Portfolio Input Form
Let’s create a new component called PortfolioForm. Create a new file named PortfolioForm.js in the src directory and add the following code:
import React, { useState } from 'react';
function PortfolioForm({ onAddHolding }) {
const [coin, setCoin] = useState('');
const [amount, setAmount] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (coin && amount) {
onAddHolding({ coin, amount: parseFloat(amount) });
setCoin('');
setAmount('');
}
};
return (
<div>
<h3>Add Holding</h3>
<form onSubmit={handleSubmit}>
<label htmlFor="coin">Coin:</label>
<input
type="text"
id="coin"
value={coin}
onChange={(e) => setCoin(e.target.value)}
required
/>
<br />
<label htmlFor="amount">Amount:</label>
<input
type="number"
id="amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
required
/>
<br />
<button type="submit">Add</button>
</form>
</div>
);
}
export default PortfolioForm;
Let’s break down this code:
- We import
useStatefrom React. - We define a functional component
PortfolioFormthat takes anonAddHoldingprop (a function to add a new holding). - We use
useStateto manage the input values for the coin and amount fields. - The
handleSubmitfunction is called when the form is submitted. It prevents the default form submission behavior, calls theonAddHoldingfunction (passed as a prop) with the coin and amount, and then resets the input fields. - The form includes input fields for the coin and amount, and a submit button. The
onChangeevent handlers update the state variables as the user types.
Creating the Portfolio Display Component
Next, we will create a component to display the portfolio holdings and calculate the total value. Create a new file named Portfolio.js in the src directory and add the following code:
import React, { useState, useEffect } from 'react';
function Portfolio({ holdings, cryptoData }) {
const [portfolioValue, setPortfolioValue] = useState(0);
useEffect(() => {
let totalValue = 0;
if (cryptoData && holdings.length > 0) {
holdings.forEach((holding) => {
const coinData = cryptoData.find((crypto) => crypto.symbol.toLowerCase() === holding.coin.toLowerCase());
if (coinData) {
totalValue += coinData.current_price * holding.amount;
}
});
}
setPortfolioValue(totalValue);
}, [holdings, cryptoData]);
return (
<div>
<h3>Portfolio</h3>
{holdings.length === 0 ? (
<p>Add some holdings to see your portfolio value.</p>
) : (
<div>
<p>Total Portfolio Value: ${portfolioValue.toFixed(2)}</p>
<ul>
{holdings.map((holding, index) => {
const coinData = cryptoData.find(
(crypto) => crypto.symbol.toLowerCase() === holding.coin.toLowerCase()
);
if (coinData) {
return (
<li key={index}>
{holding.coin.toUpperCase()}: {holding.amount} units @ ${coinData.current_price.toFixed(2)} = ${(coinData.current_price * holding.amount).toFixed(2)}
</li>
);
}
return null;
})}
</ul>
</div>
)}
</div>
);
}
export default Portfolio;
Let’s break down this code:
- We import
useStateanduseEffectfrom React. - The
Portfoliocomponent receives two props:holdings(an array of user-entered holdings) andcryptoData(the array of cryptocurrency data fetched from the API). - We use
useStateto manage theportfolioValue. - The
useEffecthook recalculates theportfolioValuewhenever theholdingsorcryptoDatachange. - Inside the
useEffecthook, we iterate through theholdingsarray and calculate the total value by multiplying the amount of each coin by its current price (obtained from thecryptoDataarray). - We render a message if the user has not added any holdings.
- If the user has added holdings, we display the total portfolio value and a list of holdings with their individual values.
Integrating the Components in App.js
Now, let’s integrate these components into our App.js file:
import React, { useState } from 'react';
import CryptoList from './CryptoList';
import PortfolioForm from './PortfolioForm';
import Portfolio from './Portfolio';
function App() {
const [holdings, setHoldings] = useState([]);
const addHolding = (newHolding) => {
setHoldings([...holdings, newHolding]);
};
return (
<div>
<h1>Cryptocurrency Portfolio Tracker</h1>
<PortfolioForm onAddHolding={addHolding} />
<CryptoList />
<Portfolio holdings={holdings} cryptoData={cryptoData} />
</div>
);
}
export default App;
Let’s break down these changes:
- We import
PortfolioFormandPortfolio. - We create a
holdingsstate variable to store the user’s portfolio holdings. - We define an
addHoldingfunction to update theholdingsstate when a new holding is added. - We pass the
addHoldingfunction as a prop to thePortfolioFormcomponent. - We pass the
holdingsand thecryptoDatato thePortfoliocomponent.
However, we are missing the cryptoData in the App.js. We need to pass the cryptoData from CryptoList to Portfolio. To do this, we need to lift the state up to the App.js component. Modify the App.js file:
import React, { useState, useEffect } from 'react';
import CryptoList from './CryptoList';
import PortfolioForm from './PortfolioForm';
import Portfolio from './Portfolio';
function App() {
const [holdings, setHoldings] = useState([]);
const [cryptoData, setCryptoData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(
'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false'
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setCryptoData(data);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
};
fetchData();
}, []);
const addHolding = (newHolding) => {
setHoldings([...holdings, newHolding]);
};
return (
<div>
<h1>Cryptocurrency Portfolio Tracker</h1>
<PortfolioForm onAddHolding={addHolding} />
{loading ? (
<p>Loading...</p>
) : (
<>
<Portfolio holdings={holdings} cryptoData={cryptoData} />
<CryptoList />
</>
)}
</div>
);
}
export default App;
Modify the CryptoList.js file:
import React, { useState, useEffect } from 'react';
function CryptoList({ setCryptoData }) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(
'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false'
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setCryptoData(data);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
};
fetchData();
}, [setCryptoData]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return null;
}
export default CryptoList;
Now, save all the files and check your browser. You should now be able to add holdings and see the portfolio value update dynamically.
Adding Styling
Let’s add some basic styling to make our application more visually appealing. Create a new file named App.css in the src directory and add the following CSS:
body {
font-family: sans-serif;
margin: 20px;
}
h1 {
text-align: center;
}
form {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="text"], input[type="number"] {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #3e8e41;
}
ul {
list-style: none;
padding: 0;
}
li {
margin-bottom: 5px;
}
Import the CSS file into your App.js file:
import './App.css';
Save all files and check your browser. Your application should now have a more polished look.
Adding Error Handling and Input Validation
To improve the user experience and make our application more robust, let’s add error handling and input validation.
Error Handling in API Calls
We already have basic error handling in our API calls. We are checking for HTTP errors and displaying an error message if something goes wrong. However, we can improve this by:
- Providing more specific error messages.
- Implementing a retry mechanism for failed API requests. (This is a more advanced topic and is not covered in this guide).
For example, you can modify the error message in the CryptoList.js component to provide more context:
if (error) return <p>Error fetching data: {error.message}</p>;
Input Validation in the Form
In the PortfolioForm.js component, we can add client-side validation to ensure that the user enters valid input. For example, we can:
- Prevent the user from submitting the form if either the coin or amount field is empty. We already implemented this.
- Validate that the amount is a positive number.
Here’s how you can modify the handleSubmit function in PortfolioForm.js to validate the amount:
const handleSubmit = (e) => {
e.preventDefault();
if (!coin || !amount) {
alert('Please enter both coin and amount.');
return;
}
const parsedAmount = parseFloat(amount);
if (isNaN(parsedAmount) || parsedAmount <= 0) {
alert('Please enter a valid positive amount.');
return;
}
onAddHolding({ coin, amount: parsedAmount });
setCoin('');
setAmount('');
};
In this updated code:
- We check if the
coinoramountfields are empty, and display an alert if they are. - We parse the
amountto a floating-point number usingparseFloat(). - We use
isNaN()to check if the parsed amount is a valid number. We also check if the amount is greater than 0. If either condition fails, we display an alert. - If all validations pass, we call the
onAddHoldingfunction.
Common Mistakes and How to Fix Them
Here are some common mistakes beginners make when building React applications and how to fix them:
- Incorrect State Updates: Failing to update the state correctly can lead to unexpected behavior. Always use the
set...functions provided by theuseStatehook to update state variables. For example, instead of directly modifying an array, create a new array with the updated values using the spread operator (...). - Forgetting Dependencies in
useEffect: TheuseEffecthook’s dependency array is crucial. If you forget to include a dependency, your effect might not run when the dependent values change, leading to stale data or incorrect behavior. Double-check your dependency array to ensure it includes all the variables used inside the effect. - Incorrectly Passing Props: Make sure you are passing the correct props to your components. Check for typos and ensure that the prop names match what the component expects.
- Not Handling Errors Properly: Always handle potential errors in your API calls and other asynchronous operations. Display informative error messages to the user to help them understand what went wrong.
- Ignoring Component Re-renders: React components re-render when their state or props change. Be mindful of this and avoid unnecessary operations or calculations inside your components, especially inside the render method. Use
React.memooruseMemoto optimize performance when needed.
Summary / Key Takeaways
In this guide, you’ve learned how to build a simple cryptocurrency portfolio tracker using React. You’ve covered the essential steps, from setting up a React project and fetching data from an API to creating a portfolio input form and displaying the portfolio value. You’ve also learned about state management with useState and side effects with useEffect, which are fundamental concepts in React development. Furthermore, you’ve gained practical experience with component creation, API integration, and user input handling, all of which are valuable skills for any React developer. This project provides a solid foundation for further exploration. You can expand it by adding features like real-time price updates, historical price charts, support for more cryptocurrencies, and user authentication.
Optional FAQ
Here are some frequently asked questions about building a cryptocurrency portfolio tracker in React:
- Can I use a different API? Yes, you can. There are many cryptocurrency APIs available. Just make sure to adjust the API endpoint and data parsing logic in your code.
- How can I add more cryptocurrencies? You can modify the API request to fetch data for more cryptocurrencies or add a search feature to allow users to select from a list of available coins.
- How can I store the portfolio data? For a more persistent solution, you can store the portfolio data in local storage, a database, or a cloud storage service.
- How do I handle real-time price updates? You can use WebSockets or server-sent events to receive real-time price updates from a cryptocurrency API.
- What about user authentication? You can implement user authentication using libraries like Firebase Authentication or by creating your own authentication system.
This project is a starting point. The world of React development is vast and offers endless possibilities. By continuing to experiment, learn new techniques, and build upon your existing knowledge, you can create even more sophisticated and feature-rich applications. Remember that the key to mastering React, like any skill, is consistent practice and a willingness to learn from your mistakes. Embrace the challenges, celebrate your successes, and enjoy the journey of becoming a proficient React developer. The skills you’ve gained in building this portfolio tracker can be applied to a wide range of web development projects, opening doors to exciting new opportunities in the ever-evolving world of technology. Keep coding, keep learning, and keep building!
