Saturday 18 June 2022

Web Tutorial: The Japanese Language Trainer App (Part 1/4)

One of the struggles I have encountered in the past year was with learning a new language - specifically, Japanese. I was trying to learn to read and write Japanese characters when something occurred to me: I could totally create an app to help train me to read and write in Japanese. And I would even do it in ReactJS!

Thus, today we will be walking through the steps of how to create a ReactJS app to randomly display Japanese characters, and for the user to click on the correct pronounciation.

What you will need for this...

As with any app that requires you to set up an app in NodeJS, please g through the instructions here. You should then have an app created, with all the directories and such. I am just using the name "japtrainer" for this app.

We will also use this image below, just as decoration. This should be saved in the img directory of the src directory. That's my recommendation, but you can save that to a different directory as long as you reference it correctly afterwards.

src/img/bg.jpg



Change the title of the app.

public/index.html
<title>J-Trainer</title>


Now we need to start right from this level, in the src directory. Let's clear the App.css file and add a specification for App. The width is 600 pixels, and the margin property sets the div in the middle.

src/App.css
.App {
    width: 600px;
    margin: 0 auto 0 auto;
}


Inside App, we have three divs - styled using CSS classes Top, Middle and Bottom. All of them take up 100% of the parent, but have different heights.

src/App.css
.App {
    width: 600px;
    margin: 0 auto 0 auto;
}

.App .Top {
    width: 100%;
    height: 150px;
}

.App .Middle {
    width: 100%;
    height: 350px;
}

.App .Bottom {
    width: 100%;
    height: 200px;
}


Preemptively, we create the Hide CSS class for cases where stuff needs to be hidden. For this, the visibility property is set to hidden and the cursor property is set to default.

src/App.css
.App .Bottom {
    width: 100%;
    height: 200px;
}

.Hide {
    visibility: hidden;
    cursor: default;
}


Again, pre-emptively, because I'd rather not revisit this file later, we set styling for buttons. I'm going for the round-cornered, orange button, but this is strictly aesthetic and you need to find your own preferences here. If the button is disabled, transparency for background color is set to 50%. There is also the btnSmall class, where the font size is set to be 50% of the default. You will see all this in action soon.

src/App.css
.Hide {
    visibility: hidden;
    cursor: default;
}

button {
    width: 10em;
    height: 2em;
    font-weight: bold;
    color: #FFFFFF;
    background-color: #FF4400;
    text-align: center;
    border: 0px solid #FFFFFF;
    border-radius: 5px;
}

button[disabled] {
    background-color: rgba(255, 200, 0, 0.5);
}

.btnSmall {
    font-size: 0.5em;  
}


Next, clear the file App.js. We are going to rebuild this entirely. Begin by importing React and useState - the latter will be useful when we want to set state. Also, import the CSS file.

src/App.js
import React, { useState } from 'react';
import './App.css';


We follow up by defining the App() function here, and then exporting it as the default.

src/App.js
import React, { useState } from 'react';
import './App.css';

function App() {

}

export default App;


In App(), we return this. This is the main layout for the app, and you will see that the styling for Top, Middle and Bottom have already been defined earlier.

src/App.js
function App() {
  return (
    <div className="App">
      <div className="Top">

      </div>
      <div className="Middle">

      </div>  
      <div className="Bottom">

      </div>       
    </div>
  );
}


Now we are going to set the state for some variables, along with their respective setters.

charset - this is the character set we will be using. It's an array and the default value is null.
started - this is a Boolean value we use to determine if the exercise has started, with default value being false.
remaining - this is an integer, default value 20, which defines how many questions there are in an exercise.
answer - this is a string that is used to reference charset. Default is an empty string.
question - this will be an object taken from the array charset. Default is null.
result - this is an integer that keeps track of how many questions the user got right. By default, it is 0.
usedQuestions - this is an array where question will be stored, so as to ensure there are no repeats. It's an empty array by default.
maxRemaining - this is a constant, and we set it to 20. It will not be changed within this app.

src/App.js
function App() {
  const [charset, setCharset] = useState(null);
  const [started, setStarted] = useState(false);
  const [remaining, setRemaining] = useState(20);
  const [answer, setAnswer] = useState("");
  const [question, setQuestion] = useState(null);
  const [result, setResult] = useState(0);
  const [usedQuestions, setUsedQuestions] = useState([]);
  const maxRemaining = 20;


  return (
    <div className="App">
      <div className="Top">

      </div>
      <div className="Middle">

      </div>  
      <div className="Bottom">

      </div>       
    </div>
  );
}


The Header component

We are going to begin adding components, starting with Header. First do an import - we will follow up later from that directory.

src/App.js
import React, { useState } from 'react';
import './App.css';
import Header from './components/Header';

function App() {


Add the Header component into the div styled using Top. The state values we defined earlier (along with their setters) are passed into the component as properties.

src/App.js
return (
<div className="App">
  <div className="Top">
    <Header
      charset={ charset }
      started={ started }
      remaining={ remaining }
      usedQuestions={ usedQuestions }
      maxRemaining={ maxRemaining }
      setStarted={ setStarted }
      setCharset={ setCharset }
      setRemaining={ setRemaining }
      setAnswer={ setAnswer }
      setResult={ setResult }
      setQuestion={ setQuestion }
      setUsedQuestions={ setUsedQuestions }
    />

  </div>
  <div className="Middle">

  </div>  
  <div className="Bottom">

  </div>       
</div>
);


You won't see anything yet. We need to create the Header component. For this, we should already have a components directory in the src directory. In it, we will create another directory, named Header. In there, we should have Header.js, Header.css and index.js. Create all these files.

Do this in index.js so that any export from the Header directory will not need to know what filename to call, since the default is index.js. It's just a good practice.

src/components/Header/index.js
export { default } from './Header';


Import the same things we did for App.js. Import the CSS as well.

src/components/Header/Header.js
import React, { useState } from 'react';
import './Header.css';


Create the Header() function, making sure that props is a parameter, and put the export statement at the end to correspond with the export statement in index.js.

src/components/Header/Header.js
import React, { useState } from 'react';
import './Header.css';

function Header(props) {
 
}

export default Header;


We begin by initializing all the variables from props.

src/components/Header/Header.js
function Header(props) {
    let charset = props.charset;
    let setCharset = props.setCharset;
    let started = props.started;
    let remaining = props.remaining;
    let maxRemaining = props.maxRemaining;
    let usedQuestions = props.usedQuestions;
    let setStarted = props.setStarted;
    let setRemaining = props.setRemaining;
    let setAnswer = props.setAnswer;
    let setResult = props.setResult;
    let setUsedQuestions = props.setUsedQuestions;
    let setQuestion = props.setQuestion;  

}


Depending on the value of started, we return different things.

src/components/Header/Header.js
function Header(props) {
    let charset = props.charset;
    let setCharset = props.setCharset;
    let started = props.started;
    let remaining = props.remaining;
    let maxRemaining = props.maxRemaining;
    let usedQuestions = props.usedQuestions;
    let setStarted = props.setStarted;
    let setRemaining = props.setRemaining;
    let setAnswer = props.setAnswer;
    let setResult = props.setResult;
    let setUsedQuestions = props.setUsedQuestions;
    let setQuestion = props.setQuestion;
    
    if (started) {
        return (
        
        );
    } else {
        return (

        );          
    } 
 
}


If started is true, we have two divs one with the CSS style Title and the other with Controls.

src/components/Header/Header.js
if (started) {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">

            </div>
        </>  
         
    );
} else {
    return (

    );          
}   


In here,  we have a bit of HTML, most notably, a button tag. The text depends on the value of remaining. If remaining is 0, we change the text to "Restart" and if not, that means that there are still questions remaining, so the most obvious option is to "Cancel".

src/components/Header/Header.js
if (started) {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <br />
                    <button>
                        { remaining === 0 ? "RESTART ➤ " : "CANCEL ☓" }
                    </button>
                </div>

            </div>
        </>            
    );
} else {
    return (

    );          
}   


Now, if the exercise has not started, we still have the same divs with the CSS classes of Title and Controls.

src/components/Header/Header.js
if (started) {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <br />
                    <button>
                        { remaining === 0 ? "RESTART ➤ " : "CANCEL ☓" }
                    </button>
                </div>
            </div>
        </>            
    );
} else {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">

            </div>
        </>

    );          
}   


For this, we have two radio buttons, labelled "Hiragana" and "Katakana" respectively. And we have a button with the "BEGIN" text. The disabled attribute is set depending on whether the value of charset is null.

src/components/Header/Header.js
if (started) {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <br />
                    <button>
                        { remaining === 0 ? "RESTART ➤ " : "CANCEL ☓" }
                    </button>
                </div>
            </div>
        </>            
    );
} else {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <input type="radio" name="charset"  /> Hiragana <br />
                    <input type="radio" name="charset" /> Katakana <br />
                    <br />
                    <button disabled={ charset === null ? true : false }>
                        BEGIN ➤
                    </button>
                </div>

            </div>
        </>
    );          
}   


Now for the CSS. Title takes up full width and 80 pixels height. We float it left and center the text. We also give it a solid grey border and use the image for its background. I've tweaked the letter-spacing property for prettiness.

src/components/Header/Header.css
.Title {
    width: 100%;
    height: 80px;
    float: left;
    text-align: center;
    border-bottom: 1px solid #DDDDDD;
    background: url(../../img/bg.jpg) bottom center no-repeat;
    letter-spacing: 1.2em;
}


For Controls, it's all really a bit of layout that doesn't affect functionality, so feel free to adjust.

src/components/Header/Header.css
.Title {
    width: 100%;
    height: 80px;
    float: left;
    text-align: center;
    border-bottom: 1px solid #DDDDDD;
    background: url(../../img/bg.jpg) bottom center no-repeat;
    letter-spacing: 1.2em;
}

.Controls {
    width: 100%;
    height: 70px;
    float: left;
}

.Controls div{
    width: 50%;
    margin-left: auto;
    margin-right: auto;
    text-align: center;
}


Finally, let's see Header in action. Looks good, right?




Now we will need to add actions to the controls. We have BtnStart_click() which sets started to true, along with resetting the variables remaining, result, usedQuestions and question to their default values. BtnEnd_click() is the Cancel operation, and it sets started to false, along with resetting the variables charset, answer, result, usedQuestions and question to their default values.

src/components/Header/Header.js
function Header(props) {
    let charset = props.charset;
    let setCharset = props.setCharset;
    let started = props.started;
    let remaining = props.remaining;
    let maxRemaining = props.maxRemaining;
    let usedQuestions = props.usedQuestions;
    let setStarted = props.setStarted;
    let setRemaining = props.setRemaining;
    let setAnswer = props.setAnswer;
    let setResult = props.setResult;
    let setUsedQuestions = props.setUsedQuestions;
    let setQuestion = props.setQuestion;

    const BtnStart_click = ()=> {
        setStarted(true);
        setRemaining(maxRemaining);
        setResult(0);
        setUsedQuestions([]);
        setQuestion(null);
    };


    const BtnEnd_click = ()=> {
        setStarted(false);
        setCharset(null);
        setAnswer("");
        setResult(0);
        setUsedQuestions([]);
        setQuestion(null);
    };

    
    if (started) {


And here we add the events on these buttons.

src/components/Header/Header.js
if (started) {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <br />
                    <button onClick={ ()=>{BtnEnd_click();}}>
                        { remaining === 0 ? "RESTART ➤ " : "CANCEL ☓" }
                    </button>
                </div>
            </div>
        </>            
    );
} else {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <input type="radio" name="charset"  /> Hiragana <br />
                    <input type="radio" name="charset" /> Katakana <br />
                    <br />
                    <button onClick={ ()=>{BtnStart_click();}} disabled={ charset === null ? true : false }>
                        BEGIN ➤
                    </button>
                </div>
            </div>
        </>
    );          
}   


However, since charset is still null, the Begin button will still be disabled. The user is supposed to set the appropriate value for charset by selecting either "Hiragana" or "Katakana". To do this, we make sure clicking on either one triggers the SetCurrentCharset() function with either "hiragana" or "katakana" passed in as an argument.

src/components/Header/Header.js
<input type="radio" name="charset" onClick={()=>{SetCurrentCharset('hiragana');}}  /> Hiragana <br />
<input type="radio" name="charset" onClick={()=>{SetCurrentCharset('katakana');}} /> Katakana <br />


Then we define SetCurrentCharset(). It sets charset using its setter, which in turns calls GetCharset(), passing in name as an argument.

src/components/Header/Header.js
const BtnEnd_click = ()=> {
    setStarted(false);
    setCharset(null);
    setAnswer("");
    setResult(0);
    setUsedQuestions([]);
    setQuestion(null);
};

const SetCurrentCharset = (name)=> {
    setCharset(GetCharset(name));
}


if (started) {


This function needs to be defined. Why we're going about it in such a roundabout way, is because it will be used later on during testing, and we want to make life easier for ourselves. So add this import statement.

src/components/Header/Header.js
import React, { useState } from 'react';
import './Header.css';

import GetCharset from '../../utils/GetCharset';

function Header(props) {


We will create the subdirectory, utils, inside the directory src. Then inside the utils directory, we create GetCharset.js.

In this file, we define GetCharset() as a function that has a parameter, setName. After that, we export it.

src/utils/GetCharset.js
const GetCharset = (setName) => {

}

export default GetCharset;    


In the function, we define alphabet as an empty array by default. If setName is "hiragana" or "katakana", we will change the value of alphabet.

src/utils/GetCharset.js
const GetCharset = (setName) => {
    let alphabet = [];

    if (setName === "hiragana") {
        alphabet = [

        ];
    }

    if (setName === "katakana") {
        alphabet = [

        ];      
    }

    return alphabet;

}

export default GetCharset;    


In the case of "hiragana", I have added in the first five elements. Each element is an object that has the properties char and romaji. char stores the Japanese character in that alphabet, and romaji is that character's pronounciation.

src/utils/GetCharset.js
const GetCharset = (setName) => {
    let alphabet = [];

    if (setName === "hiragana") {
        alphabet = [
            {"char": "あ", "romaji": "a"},
            {"char": "い", "romaji": "i"},
            {"char": "う", "romaji": "u"},
            {"char": "え", "romaji": "e"},
            {"char": "お", "romaji": "o"},

        ];
    }

    if (setName === "katakana") {
        alphabet = [

        ];      
    }

    return alphabet;
}

export default GetCharset;    


Here's the entire set!

src/utils/GetCharset.js
const GetCharset = (setName) => {
    let alphabet = [];

    if (setName === "hiragana") {
        alphabet = [
            {"char": "あ", "romaji": "a"},
            {"char": "い", "romaji": "i"},
            {"char": "う", "romaji": "u"},
            {"char": "え", "romaji": "e"},
            {"char": "お", "romaji": "o"},
            {"char": "か", "romaji": "ka"},
            {"char": "き", "romaji": "ki"},
            {"char": "く", "romaji": "ku"},
            {"char": "け", "romaji": "ke"},
            {"char": "こ", "romaji": "ko"},
            {"char": "さ", "romaji": "sa"},
            {"char": "し", "romaji": "shi"},
            {"char": "す", "romaji": "su"},
            {"char": "せ", "romaji": "se"},
            {"char": "そ", "romaji": "so"},
            {"char": "た", "romaji": "ta"},
            {"char": "ち", "romaji": "chi"},
            {"char": "つ", "romaji": "tsu"},
            {"char": "て", "romaji": "te"},
            {"char": "と", "romaji": "to"},
            {"char": "な", "romaji": "na"},
            {"char": "に", "romaji": "ni"},
            {"char": "ぬ", "romaji": "nu"},
            {"char": "ね", "romaji": "ne"},
            {"char": "の", "romaji": "no"},
            {"char": "は", "romaji": "ha"},
            {"char": "ひ", "romaji": "hi"},
            {"char": "ふ", "romaji": "fu"},
            {"char": "へ", "romaji": "he"},
            {"char": "ほ", "romaji": "ho"},
            {"char": "ま", "romaji": "ma"},
            {"char": "み", "romaji": "mi"},
            {"char": "む", "romaji": "mu"},
            {"char": "め", "romaji": "me"},
            {"char": "も", "romaji": "mo"},        
            {"char": "や", "romaji": "ya"},
            {"char": "ゆ", "romaji": "yu"},
            {"char": "よ", "romaji": "yo"},
            {"char": "ら", "romaji": "ra"},
            {"char": "り", "romaji": "ri"},
            {"char": "る", "romaji": "ru"},
            {"char": "れ", "romaji": "re"},
            {"char": "ろ", "romaji": "ro"},
            {"char": "わ", "romaji": "wa"},
            {"char": "を", "romaji": "wo"},
            {"char": "ん", "romaji": "n"},
            {"char": "が", "romaji": "ga"},
            {"char": "ぎ", "romaji": "gi"},
            {"char": "ぐ", "romaji": "gu"},
            {"char": "げ", "romaji": "ge"},
            {"char": "ご", "romaji": "go"},
            {"char": "ざ", "romaji": "za"},
            {"char": "じ", "romaji": "ji"},
            {"char": "ず", "romaji": "zu"},
            {"char": "ぜ", "romaji": "ze"},
            {"char": "ぞ", "romaji": "zo"},
            {"char": "だ", "romaji": "da"},
            {"char": "ぢ", "romaji": "ji"},
            {"char": "づ", "romaji": "zu"},
            {"char": "で", "romaji": "de"},
            {"char": "ど", "romaji": "do"},
            {"char": "ば", "romaji": "ba"},
            {"char": "び", "romaji": "bi"},
            {"char": "ぶ", "romaji": "bu"},
            {"char": "べ", "romaji": "be"},
            {"char": "ぼ", "romaji": "bo"},
            {"char": "ぱ", "romaji": "pa"},
            {"char": "ぴ", "romaji": "pi"},
            {"char": "ぷ", "romaji": "pu"},
            {"char": "ぺ", "romaji": "pe"},
            {"char": "ぽ", "romaji": "po"}

        ];
    }

    if (setName === "katakana") {
        alphabet = [
            {"char": "ア", "romaji": "a"},
            {"char": "イ", "romaji": "i"},
            {"char": "ウ", "romaji": "u"},
            {"char": "エ", "romaji": "e"},
            {"char": "オ", "romaji": "o"},    
            {"char": "カ", "romaji": "ka"},
            {"char": "キ", "romaji": "ki"},
            {"char": "ク", "romaji": "ku"},
            {"char": "ケ", "romaji": "ke"},
            {"char": "コ", "romaji": "ko"},
            {"char": "サ", "romaji": "sa"},
            {"char": "シ", "romaji": "shi"},
            {"char": "ス", "romaji": "su"},
            {"char": "セ", "romaji": "se"},
            {"char": "ソ", "romaji": "so"},
            {"char": "タ", "romaji": "ta"},
            {"char": "チ", "romaji": "chi"},
            {"char": "ツ", "romaji": "tsu"},
            {"char": "テ", "romaji": "te"},
            {"char": "ト", "romaji": "to"},
            {"char": "ナ", "romaji": "na"},
            {"char": "ニ", "romaji": "ni"},
            {"char": "ヌ", "romaji": "nu"},
            {"char": "ネ", "romaji": "ne"},
            {"char": "ノ", "romaji": "no"},            
            {"char": "ハ", "romaji": "ha"},
            {"char": "ヒ", "romaji": "hi"},
            {"char": "フ", "romaji": "fu"},
            {"char": "ヘ", "romaji": "he"},
            {"char": "ホ", "romaji": "ho"},
            {"char": "マ", "romaji": "ma"},
            {"char": "ミ", "romaji": "mi"},
            {"char": "ム", "romaji": "mu"},
            {"char": "メ", "romaji": "me"},
            {"char": "モ", "romaji": "mo"},        
            {"char": "ヤ", "romaji": "ya"},
            {"char": "ユ", "romaji": "yu"},
            {"char": "ヨ", "romaji": "yo"},
            {"char": "ラ", "romaji": "ra"},
            {"char": "リ", "romaji": "ri"},
            {"char": "ル", "romaji": "ru"},
            {"char": "レ", "romaji": "re"},
            {"char": "ロ", "romaji": "ro"},
            {"char": "ワ", "romaji": "wa"},
            {"char": "ヲ", "romaji": "wo"},
            {"char": "ン", "romaji": "n"},
            {"char": "ガ", "romaji": "ga"},
            {"char": "ギ", "romaji": "gi"},
            {"char": "グ", "romaji": "gu"},
            {"char": "ゲ", "romaji": "ge"},
            {"char": "ゴ", "romaji": "go"},
            {"char": "ザ", "romaji": "za"},
            {"char": "ジ", "romaji": "ji"},
            {"char": "ズ", "romaji": "zu"},
            {"char": "ぜ", "romaji": "ze"},
            {"char": "ゾ", "romaji": "zo"},
            {"char": "ダ", "romaji": "da"},
            {"char": "デ", "romaji": "de"},
            {"char": "ド", "romaji": "do"},
            {"char": "バ", "romaji": "ba"},
            {"char": "ビ", "romaji": "bi"},
            {"char": "ブ", "romaji": "bu"},
            {"char": "べ", "romaji": "be"},
            {"char": "ボ", "romaji": "bo"},
            {"char": "パ", "romaji": "pa"},
            {"char": "ピ", "romaji": "pi"},
            {"char": "プ", "romaji": "pu"},
            {"char": "ペ", "romaji": "pe"},
            {"char": "ポ", "romaji": "po"}

        ];      
    }

    return alphabet;
}

export default GetCharset;    


So now if you select either "Hiragana" or "Katakana", the button should be enabled.




And then if you click "BEGIN", you should get a "CANCEL" button. And if you click "CANCEL", it brings you right to the beginning.




The Display component

Now we will expand on what we just did, with the Display component. We begin with an additional import statement in App.js.

src/App.js
import React, { useState } from 'react';
import './App.css';
import Header from './components/Header';
import Display from './components/Display';

function App() {


We will then add the component in the div that is styled using the CSS class Middle.

src/App.js
<div className="Top">
    <Header
      charset={ charset }
      started={ started }
      remaining={ remaining }
      usedQuestions={ usedQuestions }
      maxRemaining={ maxRemaining }
      setStarted={ setStarted }
      setCharset={ setCharset }
      setRemaining={ setRemaining }
      setAnswer={ setAnswer }
      setResult={ setResult }
      setQuestion={ setQuestion }
      setUsedQuestions={ setUsedQuestions }
    />
</div>
<div className="Middle">
    <Display
      charset={ charset }
      started={ started }
      remaining={ remaining }
      maxRemaining={ maxRemaining }
      answer={ answer }
      question={ question }
      usedQuestions={ usedQuestions }
      result={ result }
      setStarted={ setStarted }
      setRemaining={ setRemaining }
      setAnswer={ setAnswer }
      setQuestion={ setQuestion }
      setUsedQuestions={ setUsedQuestions }
    />

</div>


As before, we create the Display directory inside components, and create Display.js, Display.css and index.js. Leave Display.css alone; for now, it's enough that the file exists. But we will have to do ths for index.js.

src/components/Display/index.js
export { default } from './Display';


For Display.js, we import the CSS file, define the function Display(), and have the export statement at the end.

src/components/Display/Display.js
import './Display.css';

function Display(props) {

}

export default Display;


Declare and initialize the variables according to the properties that were passed down.

src/components/Display/Display.js
function Display(props) {
    let charset = props.charset;
    let started = props.started;
    let remaining = props.remaining;
    let maxRemaining = props.maxRemaining;
    let answer = props.answer;
    let question = props.question;
    let result = props.result;
    let usedQuestions = props.usedQuestions;
    let setRemaining = props.setRemaining;
    let setAnswer = props.setAnswer;
    let setQuestion = props.setQuestion;
    let setUsedQuestions = props.setUsedQuestions;

}


By default, we return this. These are just app instructions. See how the CSS class btnSmall is used!

src/components/Display/Display.js
function Display(props) {
    let charset = props.charset;
    let started = props.started;
    let remaining = props.remaining;
    let maxRemaining = props.maxRemaining;
    let answer = props.answer;
    let question = props.question;
    let result = props.result;
    let usedQuestions = props.usedQuestions;
    let setRemaining = props.setRemaining;
    let setAnswer = props.setAnswer;
    let setQuestion = props.setQuestion;
    let setUsedQuestions = props.setUsedQuestions;
    
    return (
        <>
            <br />
            <h3>Welcome to J-Trainer!</h3>
            <ol>
                <li>Select your character set.</li>
                <li>Click <button className="btnSmall">BEGIN ➤ </button> to start.</li>
                <li>For every character that is displayed, select the correct pronounciation. Note that some characters may have the same pronounciation as others.</li>
                <li>Click the <button className="btnSmall">REMAINING ➤ </button> button to continue after the results are displayed.</li>
            </ol>
        </>            
    );

}


This is the default view for the Display component. No matter what you click right now, this is what you will see.




Of course, there will be more done here. There is way more to the Display component than has been covered here, and we will get to it!

Next

We will focus on the keyboard and display.

No comments:

Post a Comment