In this tutorial, I am going to build a pricing component in React using the HTML structure from the previous article of this series.
Let's create a Pricing.js component file, and add the HTML into the return statement π
import React from "react";
class Pricing extends React.Component {
render() {
return (
<div className="pricing">
<div className="pricing-slider center-content">
<label className="form-slider">
<span>How many users do you have?</span>
<input type="range" />
</label>
<div className="pricing-slider-value">
{/* Current slider value */}
</div>
</div>
<div className="pricing-items">
<div className="pricing-item">
<div className="pricing-item-inner">
<div className="pricing-item-content">
<div className="pricing-item-header center-content">
<div class="pricing-item-title">Basic</div>
<div className="pricing-item-price">
<span className="pricing-item-price-currency" />
<span className="pricing-item-price-amount">Free</span>
</div>
</div>
<div className="pricing-item-features">
<ul className="pricing-item-features-list">
<li className="is-checked">Excepteur sint occaecat</li>
<li className="is-checked">Excepteur sint occaecat</li>
<li className="is-checked">Excepteur sint occaecat</li>
<li>Excepteur sint occaecat</li>
<li>Excepteur sint occaecat</li>
</ul>
</div>
</div>
<div class="pricing-item-cta">
<a class="button" href="http://cruip.com/">
Buy Now
</a>
</div>
</div>
</div>
<div className="pricing-item">
<div className="pricing-item-inner">
<div className="pricing-item-content">
<div className="pricing-item-header center-content">
<div class="pricing-item-title">Advanced</div>
<div className="pricing-item-price">
<span className="pricing-item-price-currency">$</span>
<span className="pricing-item-price-amount">13</span>
/m
</div>
</div>
<div className="pricing-item-features">
<ul className="pricing-item-features-list">
<li className="is-checked">Excepteur sint occaecat</li>
<li className="is-checked">Excepteur sint occaecat</li>
<li className="is-checked">Excepteur sint occaecat</li>
<li className="is-checked">Excepteur sint occaecat</li>
<li className="is-checked">Excepteur sint occaecat</li>
</ul>
</div>
</div>
<div class="pricing-item-cta">
<a class="button" href="http://cruip.com/">
Buy Now
</a>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default Pricing;
Shaping input and output data
We already have a data scheme with input and output values (check out the first article of this series to know more).
Key | Slider value | Price, currency | Price, amount | Price, after |
---|---|---|---|---|
0 | 1,000 | Free | ||
1 | 1,250 | $ | 13 | /m |
2 | 1,500 | $ | 17 | /m |
3 | 2,000 | $ | 21 | /m |
4 | 2,500 | $ | 25 | /m |
5 | 3,500 | $ | 42 | /m |
6 | 6,000 | $ | 58 | /m |
7 | 15,000 | $ | 117 | /m |
8 | 50,000 | $ | 208 | /m |
9 | 50,000+ | Contact us |
We will use the state
object to contain that data.
Defining the component state
React has a built-in state object where to store property values that belongs to the component. I will use that to set input range and data scheme values.
state = {
priceInputValue: "0", // initial input value
priceInput: { // slider values
0: "1,000",
1: "1,250",
2: "1,500",
3: "2,000",
4: "2,500",
5: "3,500",
6: "6,000",
7: "15,000",
8: "50,000",
9: "50,000+"
},
priceOutput: { // output values
plan1: {
0: ["", "Free", ""],
1: ["$", "13", "/m"],
2: ["$", "17", "/m"],
3: ["$", "21", "/m"],
4: ["$", "25", "/m"],
5: ["$", "42", "/m"],
6: ["$", "58", "/m"],
7: ["$", "117", "/m"],
8: ["$", "208", "/m"],
9: ["", "Contact Us", ""]
}
}
};
Now pass the priceInputValue
to the input range defaultValue
attribute:
<input type="range" defaultValue={this.state.priceInputValue} />
Setting range slider attributes
Create a ref
and attach it to the input range element
// Create ref
slider = React.createRef();
// Attach ref
<input
type="range"
defaultValue={this.state.priceInputValue}
ref={this.slider} />
Let's set the range slider min
, max
attributes, right after the component output has been rendered to the DOM π
componentDidMount() {
this.slider.current.setAttribute("min", 0);
this.slider.current.setAttribute(
"max",
Object.keys(this.state.priceInput).length - 1
);
}
We have now a range slider whose values go from 0 to 9! π
Here is a recap of what we have built so far:
Binding input and output data
We have a working range slider, but it is still disconnected from the visualized price.
Before anything else, we need to update the priceInputValue
property value every time a user interacts with the slider. To do that, let's create a method ...
handlePricingSlide = e => {
this.setState({ priceInputValue: e.target.value });
};
... to be invoked when the input onChange
event occurs.
<input
type="range"
ref={this.slider}
defaultValue={this.state.priceInputValue}
onChange={this.handlePricingSlide}
/>
OK, now we need another method to retrieve the data to be outputted, accordingly with the current input value
getPricingData = (obj, pos) => {
return set !== undefined
? obj[this.state.priceInputValue][pos]
: obj[this.state.priceInputValue];
};
This method has two parameters:
-
obj
- The input or output object we want to retrieve data from -
pos
(optional) - The position of a required element in the array, if there is any. It is required for the plan objects, since to each key corresponds an array of values (e.g.0: ["", "Free", ""], ...
).
So, to output the current slider value, we will call the method like this π
<div className="pricing-slider-value">
{this.getPricingData(this.state.priceInput)}
</div>
And here is how to output the price data instead π
<div className="pricing-item-price">
<span className="pricing-item-price-currency">
{this.getPricingData(this.state.priceOutput.plan1, 0)}
</span>
<span className="pricing-item-price-amount">
{this.getPricingData(this.state.priceOutput.plan1, 1)}
</span>
{this.getPricingData(this.state.priceOutput.plan1, 2)}
</div>
Adjusting the slider value element position
Almost there. π We want the slider value to be following the slider thumb.
We need a new method for that but, before proceeding, there is another DOM element that needs to be referenced.
Create a ref
and pass it to the current slider value
// Create ref
sliderValue = React.createRef();
// Attach ref
<div className="pricing-slider-value" ref={this.sliderValue}>
Get the slider thumb size from a CSS property π
this.thumbSize = parseInt(
window
.getComputedStyle(this.sliderValue.current)
.getPropertyValue("--thumb-size"),
10
);
Now that we have a reference for the .pricing-slider-value
element, we can create the method.
handleSliderValuePosition = input => {
const multiplier = input.value / input.max;
const thumbOffset = this.thumbSize * multiplier;
const priceInputOffset =
(this.thumbSize - this.sliderValue.current.clientWidth) / 2;
this.sliderValue.current.style.left =
input.clientWidth * multiplier - thumbOffset + priceInputOffset + "px";
};
Here is a visual representation of what the method does π
Call the method as soon as the component has rendered ...
componentDidMount() {
this.handleSliderValuePosition(this.slider.current);
}
... and every time the input range value changes:
handlePricingSlide = e => {
this.handleSliderValuePosition(e.target);
};
Conclusion
Here is the final result. Click on Open Sandbox to see the full code.
I hope you enjoyed this tutorial. If you want to see this in action, here is a landing page template where itβs implemented π Storm
Top comments (0)