Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Ionic-React, a Brief Introduction
Michael D. Callaghan
Michael D. Callaghan

Posted on • Edited on • Originally published atwalkingriver.com

     

Ionic-React, a Brief Introduction

Cross-posted fromWalking River Blog

The other day I made a post I titled "Confessions of a Reluctant Ionic-React Fan". That post contained some snippets that got added to the default Ionic SideMenu template, but it neglected to provide any real functionality. In this post, I will attempt to recreate using Ionic-React a subset of one of my existing Ionic v4 apps written in Angular.

This is the second in what I hope to be a series on my experience with Ionic-React.

  1. "Confessions of a Reluctant Ionic-React Fan"
  2. Ionic-React, a Brief Introduction
  3. Ionic-React, Upgrading to Ionic v5
  4. Coming Soon

The Application

The app I want to reproduce is called Bravo! It is a tiny app I wrote to help pass the time when waiting in line, on a road trip, or other times you find yourself with lots of time and little to do. You can download it herefor Android orfor iOS.

Create the Project

The first thing you need to do is ensure you have the latest version of the Ionic CLI. I will assume you are running Node 10 or higher already.

npminstall-g ionic@latest
Enter fullscreen modeExit fullscreen mode

Next, create an Ionic React app. I will use thesidemenu template to get the scaffolding for a collapsible "hamburger" menu.

ionic start bravo-react sidemenu--type=react
Enter fullscreen modeExit fullscreen mode

Fire up the app to see what it looks like.

ionic serve
Enter fullscreen modeExit fullscreen mode

Although the Ionic CLI does not (yet?) support React as thoroughly as it does Angular, this at least should work. If your system's default web browser does not open on its own, simply openhttp://localhost:8100 to see the app. You can leave it open, as Ionic will ensure your app will recompile and reload the browser automatically for you.

It is a standard Ionic sample app with a menu and a split-pane view. This means that when the app window is wide enough, like on a desktop browser, side menu will be visible all the time. When the window is narrower, less than 992 pixels wide, the menu will collapse into a standard hamburger menu. The menu shows two pages, home and list. Clicking either will navigate to the appropriate page. This is enough to get us started.

Instructions

We will remove everything on the home page inside the<IonContent>...</IonContent> and replace it with some general game instructions.

Slider

My instruction cards are implementing using theion-slides component. The current code looks like this, which is what I need to replicate.

ion-sides (Angular)

<ion-slides#sliderpager="true"(ionSlideDidChange)="onSlideChange($event)"><ion-slide>  . . .</ion-slide></ion-slides>
Enter fullscreen modeExit fullscreen mode

There are multipleion-slide objects inside a singleion-slides wrapper. The wrapper has an identifier that I can reference from the Angular component code through a@ViewChild decorator, which is Angular-specific. Only a single option is specified,pager, displaying a series of small gray dots at the bottom of the slider. My slider raises a single event I care about,onIonSlideDidChange. This event fires whenever the slide has changed (duh!).

I was originally going to omit it from this experiment, but realized that responding to HTML events is a pretty basic task. It has also provided me with my first real challenge.

IonSlides (React)

First and foremost, you cannot simply copy and paste the markup from the Angular project's HTML file into a React project's TSX file. If you try, you get a bunch of unknown component errors. Why? The tag names are different from Ionic-Angular and Ionic-React.

After that realization, this is what I managed to get working to start.

<IonSlidespager={true}onIonSlideDidChange={ionSlideChanged}><IonSlide>    01. Placeholder</IonSlide><IonSlide>    02. Placeholder</IonSlide></IonSlides>
Enter fullscreen modeExit fullscreen mode

The good news is that Ionic-React gives you amazing intellisense and code completion out of the box, at least in Visual Studio Code. The Ionic team did a great job in that regard. So it did not take much time for me to figure out how adjust my markup.

onIonSlideDidChange

Where I ran into trouble was in wiring up that event handler. It is not complicated. In Angular, it is a method on my component class.

asynconSlideChange(){this.showSkip=!(awaitthis.slider.isEnd());}
Enter fullscreen modeExit fullscreen mode

It simply controls the visibility of a "Skip" link in the header, which I will show shortly. This link should be visible unless the slider is showing the last slide, indicated byisEnd(). That function is asynchronous, so it must be awaited.

Getting this to work in React has been a challenge, and I am sure that someone will point out that my ultimate solution is not the "best practice." That is fine, as I am more than willing to learn.

Here is what eventually worked for me.

let[showSkip,setSkip]=React.useState(true);constionSlideChanged=(event:CustomEvent)=>{consttarget:any=event.target;setSkip(!target.swiper.isEnd);}
Enter fullscreen modeExit fullscreen mode

The first line, which is what took me so long to get right, "returns a stateful value, and a function to update it." This is a basic React hook, and provides a way to set and alter the component state. I want React to understand that this variable means something to the UI, and this is how that is done. I tell React to give me some state by callingReact.useState(true). Thetrue parameter is the initial state value. That function returns two values, the state variable itself, and a function I can call to update it. In this instance, I now have a local variable that controls the visibility of my Skip button.

The functionionSlideChanged is called whenever the slide is changed. This function needs to set the value ofshowSkip based on whether or not the final slide is showing. Unlike the Angular version, I do not seem to have a strongly-typed way to reach into the ion-slides component. Instead, I need to retrieve the target of the HTML event (In React, theIonSlides component), and then find its internalswiper field (that took a while to discover), and check itsisEnd value. It is here that the Angular code wins hands-down.

onIonSlideDidChange (Alternative)

Kevin Clark commented on Twitter that there are a couple of ways to get strong typing in this function. His first suggestion, that I useevent.detail, did not work for me. It always had anull value. His second suggestion, however, worked perfectly. The new version of the function is here:

asyncfunctionionSlideChanged(event:CustomEvent){consttarget=event.targetasHTMLIonSlidesElement;setSkip(!awaittarget.isEnd());}
Enter fullscreen modeExit fullscreen mode

Now it looks almost exactly like the Angular version. I simply needed to coerce theevent.target to be anHTMLIonSlidesElement. Once I did that, I could await a call to itsisEnd() function, and use that value.

The Angular version is slightly more concise, because I already had a strongly-typed reference to the slides element in my component code:

@ViewChild('slider')slider:IonSlides;
Enter fullscreen modeExit fullscreen mode

Toolbar and Buttons

Now let us look at that Skip button and how it is shown or hidden. The Angular code for the entire header looks like this:

<ion-headerno-shadow><ion-toolbarcolor="dark"><ion-title>Bravo!</ion-title><ion-buttonsslot="end"*ngIf="showSkip"><ion-buttonrouterDirection="root"routerLink="/game"color="light">Skip</ion-button></ion-buttons></ion-toolbar></ion-header>
Enter fullscreen modeExit fullscreen mode

It is missing a menu button, but that was an early design decision. In the React version, I will leave it visible. Most users will expect it, so it makes sense to have it. The header is specified as not having a shadow. It consists of a dark toolbar, containing the application title and single button, the Skip button.

The Skip button is light colored, and declares itself to be a navigation link to the "game" page, which is not yet implemented. Notice that theion-buttons component contains an*ngIf to display or not, depending on the value of theshowSkip variable. This concept is not replicated in React, so this was my second challenge.

Skip Button, First Attempt

After a few web searches and much trial and error, I ended up creating the button as its own component, defined as a constant inside the HomePage component. It looks like this.

constskipButton=()=>{if(showSkip){return(<IonButtonrouterDirection="forward"routerLink="/game"color="light">Skip</IonButton>);}else{return(<span></span>);}}
Enter fullscreen modeExit fullscreen mode

It is simple enough to understand, even if not nearly as concise as an*ngIf expression. That is not inside the toolbar, though, right? To insert it into my header, I needed to include it in the markup in the appropriate place. This is my complete header markup.

<IonHeader><IonToolbar><IonButtonsslot="start"><IonMenuButton/></IonButtons><IonTitle>Home</IonTitle><IonButtonsslot="end">      {skipButton()}</IonButtons></IonToolbar></IonHeader>
Enter fullscreen modeExit fullscreen mode

You can see the call to theskipButton() function inside the<IonButtons> tag.

This is a little more abstraction and encapsulation than I would prefer for a simple "show/hide button" construct. But I was unable to find a better solution.

A Better Approach

Thanks toEly Lucas, who commented on my pull request, I was able to eliminate theskipButton() function entirely by using what he suggested would be "kinda like [Angular's] ngIf." Instead of a separate function, we can simply evaluate theskipButton value directly inside the<IonButtons> element, as follows.

<IonButtonsslot="end">  {showSkip&&<IonButtonrouterDirection="forward"routerLink="/game"color="light">Skip</IonButton>}</IonButtons>
Enter fullscreen modeExit fullscreen mode

The entire<IonButton> definition is included with the markup where it is used. However ifshowSkip is not "truthy," JavaScript's short-circuiting of the conditional expression will prevent the right side of the&& from being evaluated. Thus, ifshowSkip isfalse, nothing will be displayed. Now the only reason to have a separate definition for theskipButton would be to use it in more than one place on the page. This is much better.

IonCard

The next thing I want to do is flesh out the instructions themselves. In the Angular version, I use an<ion-card> that looks like this.

<ion-card><divclass="concert bg"></div><ion-card-headerclass="item item-header item-text-wrap"><ion-card-title>Welcome to Bravo!</ion-card-title></ion-card-header><ion-card-contentclass="item item-body"><p>      This is an 'on-the-go' party game for those long waits in       theme park lines or long road trips.</p><p>      It is a card game for you and small group of friends.       When you start, you will be given a word or a category      and one of the following subject areas:       Song Lyrics, Song Title, Movie Quote, Movie Title,       TV Show Quote, TV Show Title , or Book Title</p><p>      If you play it in public, you may just find people around       you offering their own song suggestions.</p></ion-card-content></ion-card>
Enter fullscreen modeExit fullscreen mode

I will forgo any discussion about the custom CSS classes for now and simply concentrate on the markup. It is a typical card component, with a header and some content. Each card is the only child component of each of theion-slide components. I will provide the entire content below, but for now I want to concentrate on what one of them looks like when converted to React.

<IonCard><divclassName="concert bg"></div><IonCardHeaderclass="item item-header item-text-wrap"><IonCardTitle>Welcome to Bravo!</IonCardTitle></IonCardHeader><IonCardContentclass="item item-body"><p>      This is an 'on-the-go' party game for those long waits      in theme park lines or long road trips.</p><p>      This is a card game for you and small group of friends. When you start,       you will be given a word or a category and one of the       following subject areas: Song Lyrics , Song Title ,       Movie Quote , Movie Title , TV Show Quote, TV Show Title , or Book Title</p><p>      If you play it in public, you may just find people around you       offering their own song suggestions.</p></IonCardContent></IonCard>
Enter fullscreen modeExit fullscreen mode

For some reason I do not understand, this code will not compile if thediv on the second line contains aclass attribute. Instead, I had to replaceclass withclassName. Then it compiled. Other than that minor glitch, this was reasonably straightforward.

For completeness, here is my entire<IonSlides> definition, in case you are following along:

<IonSlidespager={true}onIonSlideDidChange={ionSlideChanged}><IonSlide><IonCard><divclassName="concert bg"></div><IonCardHeaderclass="item item-header item-text-wrap"><IonCardTitle>Welcome to Bravo!</IonCardTitle></IonCardHeader><IonCardContentclass="item item-body"><p>This is an 'on-the-go' party game for those long waits in theme park lines or long road trips.</p><p>This is a card game for you and small group of friends. When you start, you will be given a word or a          category          and one of the following subject areas: Song Lyrics , Song Title , Movie Quote , Movie Title , TV Show Quote, TV Show Title , or Book Title</p><p>If you play it in public, you may just find people around you offering their own song suggestions.</p></IonCardContent></IonCard></IonSlide><IonSlide><IonCard><divclassName="song bg"></div><IonCardHeader><IonCardTitle>Song Lyrics</IonCardTitle></IonCardHeader><IonCardContentclass="item item-body"><p>On these cards, it's up to each player to come up with and sing the lyrics from a song, containing the          word          (or          subject) at the top of the card.</p><p>          You can repeat a song from another card, but everyone working on the same card should use a unique song          for          this card.</p><p>          Sing loudly enough to be heard; in fact, the louder the better.</p></IonCardContent></IonCard></IonSlide><IonSlide><IonCard><divclassName="song bg"></div><IonCardHeader><IonCardTitle>Song Title</IonCardTitle></IonCardHeader><IonCardContent><p>On these cards, you don't sing, but simply state the title of a song containing the word (or about the          subject)          at the top of the card.</p><p>          You can repeat a song from another card, but everyone working on the same card should use a unique song          for          this card.</p><p>          This one should be easier than singing, but maybe not.</p></IonCardContent></IonCard></IonSlide><IonSlide><IonCard><divclassName="movie bg"></div><IonCardHeader><IonCardTitle>Movie or TV Quote</IonCardTitle></IonCardHeader><IonCardContent><p>"Go ahead, make my day." In your best actor's voice, quote a line from a          movie or TV show containing the word (or about the subject) at the top of the card.</p><p>          As with the other cards, don't repeat the same quote on a single card.</p><p>          This is your chance to ham it up in front of strangers, so don't waste it.</p></IonCardContent></IonCard></IonSlide><IonSlide><IonCard><divclassName="movie bg"></div><IonCardHeader><IonCardTitle>Movie or TV Show Title</IonCardTitle></IonCardHeader><IonCardContent><p>Show your Hollywood trivia smarts. Name a movie or TV show title containing          the word (or about the subject) at the top of the card.</p><p>          As with the other cards, don't repeat the same title on a single card.</p><p>          Keep your wits about you, because this one is harder than it might seem.</p></IonCardContent></IonCard></IonSlide><IonSlide><IonCard><divclassName="book bg"></div><IonCardHeader><IonCardTitle>Book Title</IonCardTitle></IonCardHeader><IonCardContent><p>Perhaps literary novels (or comic books) are your thing. Name a book title          containing the word (or about the subject) at the top of the card.</p><p>          As with the other cards, don't repeat the same book on a single card.</p><p>          Some estimates put the number of books ever published at almost 135 Million!    You should have plenty of titles from which to choose.</p></IonCardContent></IonCard></IonSlide><IonSlide><IonCard><divclassName="concert bg"></div><IonCardHeader><IonCardTitle>Conclusion</IonCardTitle></IonCardHeader><IonCardContent><p>Assign each player (or team) a color. There are six, so you shouldn't run out.</p><p>          When someone gets an answer right, meaning they've managed to convince the rest of the players, tap that          person's color at          the bottom of the screen to award them a point.</p><p>          You get to decide when to draw the next card. There are no hard and fast rules. It's just for fun after all.          That's really all          there is to it. Go forth and have fun!</p><IonToolbar><IonButtonsslot="end"><IonButtonrouterDirection="root"routerLink="/game">              Continue<IonIconname="arrow-forward"></IonIcon></IonButton></IonButtons></IonToolbar></IonCardContent></IonCard></IonSlide></IonSlides>
Enter fullscreen modeExit fullscreen mode

Conclusion

That is enough for a first pass. I got the scaffolding and one page configured and functional, though it is not anything near complete. So far, there are benefits to the React version, though some things do seem harder. I did not even touch upon unit testing the component code, which I find frustrating but manageable in Angular.

These are some of the things that still need to be done.

  • Game Page
  • Game Card
  • Footer Bar, Also Black
  • Score Boxes

In my next post, I will try to get the UI working for the game page, even if there are no mechanics yet.

The repository for this code is publicly hosted on GitHub, so you can follow my progress or even issue pull requests if you wish.
https://github.com/walkingriver/bravo-react

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I help build cool web apps | Author of http://DontSayThatAtWork.com, https://AngularAdvocate.com, and more at Amazon https://amazon.com/author/mcallaghan | #LDS
  • Location
    Orlando, Florida
  • Work
    "Lead" Software Engineer at Disney Parks Experiences and Products Technology
  • Joined

More fromMichael D. Callaghan

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp