Counter

We need a text (p element) that tells us how many characters we have inserted and the svg element containing the Progress Ring (circle element).

Since we need two colors (one to create the gray part and one to create the colored moving part) we will also need two circle elements. For the elements contained in the svg canvas there is no way to set a z-index, but (and that’s what we need) the second element will display on top of the first one (if they have the same coordinates), so we just need to make sure that our colored circle will be the last inserted.

class Counter extends React.Component{

render() {

return (

<div id="counter">

<p>You inserted {/*here will go our number*/} characters</p>

<svg>

<circle id="gray"></circle>

<circle id="colored"></circle>

</svg>

</div>

);

}

}

TweetText

Then, we can add a textarea where we’ll go to insert the text to our TweetText component, and since we are taking inspiration from Twitter, we’ll add a placeholder with value “What’s happening?”.

Copyright — Twitter

class TweetText extends React.Component{

render() {

return (

<div id="tweetText">

<textarea placeholder="What's happening?"></textarea>

</div>

);

}

}

Tweet

Our Tweet Component, as we said earlier, will contain both TweetText and Counter. So let’s add them.

class Tweet extends React.Component{

render() {

return (

<div id="tweet">

<TweetText/>

<Counter/>

</div>

);

}

}

App

Finally, in the App component, let’s replace our “Hello!” with the name of the project and let’s add the Tweet component.

class App extends React.Component{

constructor(props) {

super(props);



}



render() {

return (

<div>

<h1>Twitter Inspired Character Counter</h1>

<Tweet/>

</div>

);

}

}

We should be now able to visualize this:

Let’s give it some style! ✨

This is not the main focus of this tutorial, however, giving some style to the project can help to understand better how things are nested (at least, it works for me). To make things shorter I’ll just copy and paste the CSS code, you can find a brief explanation in the /*comments*/ but feel free to reach out if you need more in depth details! 🙂

Now our character counter should look like this:

The reason of that white block after the text “You inserted {} characters” is because there is an SVG element already defined, that has a width and an height property, but there’s nothing inside. We’ll go to make our Progress Ring appear in there in Part 3. Now let’s go to Part 2 and create a simple function to fill in that curly brackets in the p tag! 🙂

Part 2: Create a Character Counter function

The first thing we want to do is creating a function to update the number of characters inserted in the p tag. We can do this by adding a state object inside our App constructor and then adding a key numChar initialized with the value of 0. We want this number to change every time we enter a character in the textarea, so we can create a function countChar() that sets the value of numChar to the length of the string entered. Then, we’ll pass this function as a prop to the Tweet component.

class App extends React.Component{

constructor(props) {

super(props);



this.state = {

numChar: 0

}



this.countChar = this.countChar.bind(this);

}



countChar(){

this.setState(()=>({

numChar: event.target.value.length

}));

}



render() {

return (

<div>

<h1>Twitter Inspired Character Counter</h1>

<Tweet countChar={this.countChar}/>

</div>

);

}

}

In turn, the TweetText component will have the function passed from Tweet as a prop.

class Tweet extends React.Component{

render() {

return (

<div id="tweet">

<TweetText countChar={this.props.countChar}/>

<Counter/>

</div>

);

}

}

Finally, we will pass this function from the textarea and it will be called each time there will be a “change” in the element (via the onChange() function).

class TweetText extends React.Component{

render() {

return (

<div id="tweetText">

<textarea placeholder="What's happening?"

onChange={this.props.countChar}></textarea>

</div>

);

}

}

Now, the numChar value of the state object will be correctly updated (as you can see below) but still we will be not able to visualize it since we didn’t pass the value to the p element.

In order to see this yourself you can add “console.log(this.state.numChar)” to the App component, at the beginning of the render() method or in the countChar() method.

Now, let’s add the value to the p tag, we can do this by creating a prop numChar and passing it to the Tweet component.

class App extends React.Component{

constructor(props) {

super(props);



this.state = {

numChar: 0

}



this.countChar = this.countChar.bind(this);

}



countChar(){

this.setState(()=>({

numChar: event.target.value.length

}));

}



render() {

return (

<div>

<h1>Twitter Inspired Character Counter</h1>

<Tweet countChar={this.countChar}

numChar={this.state.numChar}/>

</div>

);

}

}

Then, we can pass it down to the Counter component.

class Tweet extends React.Component{

render() {

return (

<div id="tweet">

<TweetText countChar={this.props.countChar}/>

<Counter numChar={this.props.numChar}/>

</div>

);

}

}

And last, in order to show it on the DOM we need to add it in the curly brackets inside the p element.

class Counter extends React.Component{

render() {

return (

<div id="counter">

<p>You inserted {this.props.numChar} characters</p>

<svg>

<circle id="gray"></circle>

<circle id="colored"></circle>

</svg>

</div>

);

}

}

Part 2 is done too! 🎉

Here’s the result you should have so far:

Part 3: Style the SVG Progress Ring

Ok, we are almost there!

First of all, as promised, let’s make our svg Progress Ring appear to the screen. We can do this by giving to both our circles element 3 properties: cx, cy and r, where cx and cy indicates respectively the x and y coordinates of the center and r indicates the radius.

class Counter extends React.Component{

render() {

return (

<div id="counter">

<p>You inserted {this.props.numChar} characters</p>

<svg>

<circle id="gray" cx="50%" cy="50%" r="15"></circle>

<circle id="colored" cx="50%" cy="50%" r="15"></circle>

</svg>

</div>

);

}

}

A black circle should now be present next to the p element

But we want it to be similar to this. (Copyright: Twitter)

In order to do this we can remove the black color by adding a fill: none (to both our circles) setting a width of 3 with stroke-width: 3 (this value is totally random, you can also put another value in the CSS section) and then we can make our #gray element gray with stroke: lightgray (you can give it another color if you’d like).

circle{

fill: none;

stroke-width: 3;

} #gray{

stroke: lightgray;

}

Some math first

Now, in order to make our circle stroke dynamically move based on the number of words inserted, we need a little math (I promise it won’t be long or too difficult!).

The length of a circle can be measured with the following formula: 2 π r where r is the radius, in our case 15.

We can think to create a function styleRing() in our App component and insert this as a start.

styleRing(){

const r = 15;

const circleLength = 2*Math.PI*r;



}

Then, we need to make a proportion between our length and the max-length of characters that can be inserted (for Twitter is 280 characters):

280 : circleLength = numChar : x

The x will be the the length of the colored part and can be calculated with

x = (circleLength * numChar)/280

styleRing(){

const r = 15;

const circleLength = 2*Math.PI*r; let colored = (circleLength*this.state.numChar)/280;

}

So the length of the gray part will simply be the total length minus the length of the colored part.