Saturday 20 July 2019

Web Tutorial: ReactJS British Insult Generator

Today's web tutorial uses ReactJS again, so if you missed the last one, you might want to go take a gander!

I got this meme on Social Media, and it was touted as a basic formula for British insults. That was pretty cool, all things considered, and I really wanted to use that formula... in conjunction with some program code, of course.


Since we made a random text display program using ReactJS, I figure we can do the same thing here, except using multiple arrays instead of just one. But with a major difference - the script has to run as soon as the page is loaded, and not on the click of a button.

Here's some HTML...

We begin with a very bare-bones ReactJS setup, almost identical to the last one. The only difference right now, is, the div we render content in has an id of insultContainer.
<!DOCTYPE html>
<html>
    <head>
        <title>Insult Generator</title>

        <style>

        </style>

        <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
        <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
    </head>
    <body>
        <div id="insultContainer"></div>

        <script type="text/babel">

        </script>
    </body>
</html>


Let's write the class, Insult. A constructor (with state included) has been added, along with a render() method.
<!DOCTYPE html>
<html>
    <head>
        <title>Insult Generator</title>
        <style>

        </style>

        <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
        <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
    </head>
    <body>
        <div id="insultContainer"></div>

        <script type="text/babel">
            class Insult extends React.Component
            {
                constructor(props)
                {
                      super(props);
                      this.state =
                      {

                      };
                  }

                render()
                {
                    return (

                    );               
                }
            }

            ReactDOM.render(<Insult />, document.getElementById("insultContainer"));
        </script>
    </body>
</html>


Now, the render() method needs to return something. So let's give it something to return. Let's start with four arrays - action, adjective, curse and noun - in accordance to the formula. Each of these arrays will contain the appropriate words. It goes without saying that you should feel free to provide your own lists of words.
            render()
            {
                this.action =
                [
                    "Get out of here",
                    "Go to hell",
                    "Eat shit",
                    "Fall over and die",
                    "Get a life",
                    "Go kill yourself",
                    "Shut your mouth",
                    "Get bent",
                    "Sod off",
                    "Go jump off a bridge"
                ];

                this.adjective =
                [
                    "incomprehensible",
                    "obnoxious",
                    "repugnant",
                    "repulsive",
                    "ignorant",
                    "misshapen",
                    "noisy",
                    "glorious",
                    "defective",
                    "absolute",
                    "vigorous"
                ];

                this.curse =
                [
                    "bitch",
                    "shit",
                    "piss",
                    "dick",
                    "twat",
                    "ass",
                    "cum",
                    "jizz",
                    "fuck"
                ];

                this.noun =
                [
                    "monkey",
                    "dwarf",
                    "box",
                    "house",
                    "garden",
                    "train",
                    "camel",
                    "hamster",
                    "trumpet",
                    "bubble",
                    "waffle",
                    "spoon",
                    "stick",
                    "sandwich",
                    "cruiser",
                    "carriage",
                    "ticket",
                    "collection",
                    "cable",
                    "bagel",
                    "sack",
                    "puppet",
                    "lamp"
                ];

                return (

                );               
            }


Inside the return block, render a paragraph tag with an id of insultContent and the template string for the displayed curse. For now, let's just set the content to the first element of each array.
                    return (
                            <p id="insultContent">
                            {this.action[0]}, you {this.adjective[0]} {this.curse[0]} {this.noun[0]}
                            </p>
                    );


Refresh. You'll see this. Now we need to make the screen change this every few seconds.


For this to happen, we need to add some properties to state. actionIndex will be the integer that points to the current element of the action array. And so on, and so forth, for the other properties. All will default to 0.
                constructor(props)
                {
                      super(props);
                      this.state =
                      {
                          "actionIndex": 0,
                          "adjectiveIndex": 0,
                          "curseIndex": 0,
                          "nounIndex": 0
                      };
                  }


Then we bind the changeInsult() method to the constructor.
                constructor(props)
                {
                      super(props);
                      this.state =
                      {
                          "actionIndex": 0,
                          "adjectiveIndex": 0,
                          "curseIndex": 0,
                          "nounIndex": 0
                      };
                      this.changeInsult = this.changeInsult.bind(this);
                  }


Of course, we'll need to define the changeInsult() method.
                constructor(props)
                {
                      super(props);
                      this.state =
                      {
                          "actionIndex": 0,
                          "adjectiveIndex": 0,
                          "curseIndex": 0,
                          "nounIndex": 0,
                      };
                      this.changeInsult = this.changeInsult.bind(this);
                  }

                changeInsult()
                {

                }


Every, say, three seconds, we call the setState() method
                changeInsult()
                {
                    this.interval = setInterval
                    (
                        () =>
                        {
                            this.setState
                            (
                                {

                                }
                            );
                        },
                        3000
                    );
                }


actionIndex, adjectiveIndex, curseIndex and nounIndex will be set to a random number between 0 and the upper limit of their respective arrays.
                            this.setState
                            (
                                {
                                    "actionIndex": Math.floor((Math.random() * (this.action.length))),
                                    "adjectiveIndex": Math.floor((Math.random() * (this.adjective.length))),
                                    "curseIndex": Math.floor((Math.random() * (this.curse.length))),
                                    "nounIndex": Math.floor((Math.random() * (this.noun.length)))
                                }
                            );


But what's the trigger for this? changeInsult() needs to be run. We use the componentDidMount() method which is roughly the ReactJS equivalent of the onLoad property. Thus, the changeInsult() method will be called when the Insult component is loaded.
                changeInsult()
                {
                    this.interval = setInterval
                    (
                        () =>
                        {
                            this.setState
                            (
                                {
                                    "actionIndex": Math.floor((Math.random() * (this.action.length))),
                                    "adjectiveIndex": Math.floor((Math.random() * (this.adjective.length))),
                                    "curseIndex": Math.floor((Math.random() * (this.curse.length))),
                                    "nounIndex": Math.floor((Math.random() * (this.noun.length)))
                                }
                            );
                        },
                        3000
                    );
                }

                componentDidMount()
                {
                    this.changeInsult();
                }


More on the componentDidMount() method here!

And one last thing... yep, we now need to have proper indexes.
                    return (
                            <p id="insultContent">
                            {this.action[this.state.actionIndex]}, you {this.adjective[this.state.adjectiveIndex]} {this.curse[this.state.curseIndex]} {this.noun[this.state.nounIndex]}
                            </p>
                    ); 


And there you see, your insult changes every three seconds!


That's nice, but...

OK, I get it. The transition is pretty abrupt, huh? Let's pretty it up. Other than having an orange background with white text, the rest you should feel comfortable playing with.
        <style>
            #insultContent
            {
                border-radius: 20px;
                font-size: 20px;
                background-color: rgba(255, 200, 0, 1);
                color: #FFFFFF;
                font-family: verdana;
                font-weight: bold;
                padding: 2em;
                width: 20em;
                margin: 10% auto 0 auto;
                text-align: center;
            }
        </style>


There you go, so far so good!


Now let's let it transition nicely from insult to insult. First, change the style. Set the initial background color to zero opacity. I know, this will make it effectively invisible since the text is white, but bear with me, you'll see where this is going. Also, set the transition property to half of three seconds, which is 1.5.
        <style>
            #insultContent
            {
                border-radius: 20px;
                font-size: 20px;
                background-color: rgba(255, 200, 0, 0);
                color: #FFFFFF;
                font-family: verdana;
                font-weight: bold;
                padding: 2em;
                width: 20em;
                margin: 10% auto 0 auto;
                text-align: center;
                -webkit-transition: 1.5s all;
                transition: 1.5s all;
            }
        </style>


In the changeInsult() method, start off by setting the background color of the insultContent paragraph tag to full opacity.
                changeInsult()
                {
                    this.interval = setInterval
                    (
                        () =>
                        {
                            document.getElementById("insultContent").style.backgroundColor = "rgba(255, 200, 0, 1)";
                            this.setState
                            (
                                {
                                    "actionIndex": Math.floor((Math.random() * (this.action.length))),
                                    "adjectiveIndex": Math.floor((Math.random() * (this.adjective.length))),
                                    "curseIndex": Math.floor((Math.random() * (this.curse.length))),
                                    "nounIndex": Math.floor((Math.random() * (this.noun.length))),
                                }
                            );
                        },
                        3000
                    );
                }


Then, within the first setInterval() function, use a setTimeout() function to set the background color back to zero opacity. The interval for this should be 1.5 seconds. Refresh and try again!
                changeInsult()
                {
                    this.interval = setInterval
                    (
                        () =>
                        {
                            document.getElementById("insultContent").style.backgroundColor = "rgba(255, 200, 0, 1)";
                            this.setState
                            (
                                {
                                    "actionIndex": Math.floor((Math.random() * (this.action.length))),
                                    "adjectiveIndex": Math.floor((Math.random() * (this.adjective.length))),
                                    "curseIndex": Math.floor((Math.random() * (this.curse.length))),
                                    "nounIndex": Math.floor((Math.random() * (this.noun.length))),
                                }
                            );

                            setTimeout
                            (
                                () =>
                                {
                                    document.getElementById("insultContent").style.backgroundColor = "rgba(255, 200, 0, 0)";   
                                },
                                1500
                            );
                        },
                        3000
                    );
                }

A note about the fat arrow notation...

I know usually we use a function declaration for the callback in setInterval() and setTimeout() functions. But since this is ReactJS and we're trying to do things the ECMAScript way, that's what we're going to use.

More about the fat arrow notation.

Adieu for now, you unrepentant wank biscuits!
T___T

No comments:

Post a Comment