Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for NavBar
jacobwicks
jacobwicks

Posted on

     

NavBar

In this post we will make theNavBar. In the next post we will make theWriting component, where the user can write new cards. TheNavBar will let the user switch betweenAnswering cards andWriting cards.

User Story

  • The user thinks of a new card. The user opens the card editor. The user clicks the button to create a new card. The user writes in the card subject, question prompt, and an answer to the question. The user saves their new card.

This user story has a lot of things going on. To make this user story possible we will need to make new component where the user can write cards. TheWriting component will be a new 'scene' in the application. We will also need to give the user a way to get to the Writing scene.

Let's make a NavBar (Navigation Bar) component to give the user a way to choose between the two scenes. The two scenes will be theWriting scene and theAnswering scene. TheNavBar will give the user a button to go to theWriting scene. TheNavBar will also give the user a button to go to the Answering scene.

We have not made theNavBar and theWriting scene yet. TheApp just shows theAnswering scene all the time. TheNavBar will go inside theApp. TheWriting scene will also go inside theApp. TheApp will keep track of what to show the user. TheNavBar will tell theApp when the user wants to see a different scene.

In this post, we will

  • Make a placeholder for the Writing component
  • Write a typescript enum for the different scenes
  • Change theApp component to keep track of what scene to show the user
  • Make theNavBar component
  • Show theNavBar component to the user

By the end of this post theNavBar component will show up on the screen and let the user choose between looking at the Answering component and the Writing component. In the next post we will actually make the real Writing component.

Here's the NavBar in action:
NavBar2

Placeholder for the Writing component

File: src/scenes/Writing/index.tsx
Will Match:src/scenes/Writing/complete/index-1.tsx

We haven't madeWriting yet. But we need to have something to show on the screen when we selectWriting. So we are going to make a placeholder component. This will just be a div with the word 'writing' in it. Because this is a placeholder we aren't going to take the time to write tests first.

TheWriting component is one of our 'scenes.' So its folder is src/scenes/Writing.

importReactfrom'react';constWriting=()=><div>Writing</div>exportdefaultWriting;
Enter fullscreen modeExit fullscreen mode

That's it!

Make the sceneTypes type

File: src/types.ts
Will Match:src/complete/types-5.ts

Add a new enum named 'SceneTypes' insrc/types.ts:

//defines the scenes that the user can navigate toexportenumSceneTypes{//where the user answers questionsanswering="answering",//where the user writes questionswriting="writing"};
Enter fullscreen modeExit fullscreen mode

Making the App Keep Track of the Scenes

Right now theApp just shows theAnswering scene all the time. But to make the user story possible we need to let the user choose theWriting scene. We need to keep track of what scene the user is looking at. We are going to keep track of what scene the user is looking at inside theApp component. We'll keep track of what scene the user is looking at withuseState.

Features

  • There is a NavBar

Choose Components

We'll use the customNavBar that we'll write later in this post

Decide What to Test

Let's test whether theNavBar shows up.

App Test 1: Has the NavBar

File: src/App.test.tsx
Will Match:src/complete/test-5.tsx

Add a test that checks for theNavBar. TheNavBar will have aHeader with the text 'Flashcard App.'

//shows the NavBarit('shows the NavBar',()=>{const{getByText}=render(<App/>);//the navbar has a header with the words "Flashcard App" in itconstnavBar=getByText(/flashcard app/i);//if we find the header text, we know the NavBar is showing upexpect(navBar).toBeInTheDocument();});
Enter fullscreen modeExit fullscreen mode

Pass App Test 1: Has the NavBar

File: src/App.tsx
Will Match:src/complete/app-5.tsx

TheApp component will keep track of which scene to show. We will use theuseState() hook from React to keep track of which scene to show. TheNavBar component will let the user choose the scene. TheApp won't pass the test for showing theNavBar until later in this post, after we have written theNavBar and imported it into theApp.

Import theuseState hook from React.

importReact,{useState}from'react';
Enter fullscreen modeExit fullscreen mode

Import theSceneTypes enum from types.

import{SceneTypes}from'./types/';
Enter fullscreen modeExit fullscreen mode

Import theWriting component.

importWritingfrom'./scenes/Writing';
Enter fullscreen modeExit fullscreen mode

We haven't made theNavBar yet, so we won't import it. After we make theNavBar, we will come back to theApp and add theNavBar to it.

Change theApp to this:

constApp:React.FC=()=>{const[showScene,setShowScene]=useState(SceneTypes.answering);return(<CardProvider><StatsProvider>{showScene===SceneTypes.answering&&<Answering/>}{showScene===SceneTypes.writing&&<Writing/>}</StatsProvider></CardProvider>)};
Enter fullscreen modeExit fullscreen mode

Here's why the code for theApp component looks so different now.

Curly Brackets andreturn

Before these changes the App function just returned JSX. The App had a 'concise body.' A function with a concise body only has an expression that gives the return value. But now we have added an expression before the expression that gives the return value. The new expression sets upuseState to track what scene to show. Because we have added an expression besides the return value to the function, we have to add curly brackets so the compiler knows to look for expressions and not just a return value. This is called a function with a 'block body.'

return()

This is the return method of your function. This tells the function to return the value inside the parentheses. The parentheses are not required. But if you don't have the parentheses, you have to start your JSX on the same line. So it would look like this:

//this would workreturn<CardProvider><StatsProvider>{showScene===SceneTypes.answering&&<Answering/>}{showScene===SceneTypes.writing&&<Writing/>}</StatsProvider></CardProvider>;
Enter fullscreen modeExit fullscreen mode

But if you don't have parentheses, starting your JSX return value on the next line will not work.

//this won't workreturn<CardProvider><StatsProvider>{showScene===SceneTypes.answering&&<Answering/>}{showScene===SceneTypes.writing&&<Writing/>}</StatsProvider></CardProvider>;
Enter fullscreen modeExit fullscreen mode

I think it is easier to read with the return value starting on the next line. So I put parentheses around the return value.

UseState

TheuseState hook gives us a place to keep a variable, and a function to change it.

const[showScene,setShowScene]=useState(SceneTypes.answering);
Enter fullscreen modeExit fullscreen mode

useState(SceneTypes.answering) is the call to theuseState hook.SceneTypes.answering is the starting value. TypeScript can figure out from this that the type of the variableshowScene will beSceneTypes. You can also explicitly declare that you are using a type. Explicit declaration of a type onuseState looks like this:

useState<SceneTypes>(SceneTypes.answering);
Enter fullscreen modeExit fullscreen mode

const [showScene, setShowScene] is the declaration of two const variables,showScene andsetShowScene.

showScene is a variable of typeSceneTypes. SoshowScene will either beSceneTypes.answering orSceneTypes.writing. Remember when we wrote the enumSceneTypes earlier?SceneTypes.answering is the string 'answering' andSceneTypes.writing is the string 'writing'. The variableshowScene can only equal one of those two strings.

setShowScene is a function. It takes one argument. The argument thatsetShowScene takes is of the typeSceneTypes. So you can only invokesetShowScene withSceneTypes.answering orSceneTypes.writing. After you invokesetShowScene, the value ofshowScene will be set to the value that you passed tosetShowScene.

We will pass the functionsetShowScene to theNavBar. Nothing callssetShowScene yet. But after we make theNavBar, we will import it into theApp. Then we will pass thesetShowScene function to theNavBar. TheNavbar will usesetShowScene to change the value ofshowScene in App. When the value ofshowScene changes, App will change what scene it shows to the user.

Conditional Rendering of Answering and Writing

Conditional Rendering is how you tell React that if some condition is true, you want to show this component to the user. Rendering a component means showing it to the user.

{showScene===SceneTypes.answering&&<Answering/>}
Enter fullscreen modeExit fullscreen mode

{}: The curly brackets tell the compiler that this is an expression. The compiler will evaluate the expression to figure out what value it has before rendering it to the screen.

showScene === SceneTypes.answering: this is an expression that will return a boolean value. It will returntrue or it will returnfalse.

&&:This is the logical AND operator. It tells the compiler that if the condition to the left of it is true, it should evaluate and return the expression to the right.

&& <Answering/>: The logical && operator followed by the JSX for theAnswering component means 'if the condition to the left of&& is true, show theAnswering component on the screen.'

There is one conditional rendering expression for each scene.

{showScene===SceneTypes.answering&&<Answering/>}{showScene===SceneTypes.writing&&<Writing/>}
Enter fullscreen modeExit fullscreen mode

This code means ifshowScene is 'answering' show theAnswering component, and ifshowScene is 'writing' show the Writing component.

You are done with theApp for now. TheApp won't pass the test for theNavBar until later in this post, after we have written theNavBar and imported it into theApp.

TheNavBar

Now we are ready to make theNavBar. Once we have written theNavBar, we will import it into theApp so it shows up on screen and lets the user choose which scene they want to see.

Features

  • The user can click a button to go to theWriting scene
  • The user can click a button to go to theAnswering scene

Choose Components

TheNavBar is a menu, so we will use theMenu component from Semantic UI React.

Decide What to Test

  • menu
  • header
  • button loadsAnswering
  • button loadsWriting

Write the tests

File: src/components/NavBar/index.test.tsx
Will Match:src/components/NavBar/complete/test-1.tsx

Write a comment for each test.

//has a menu component//has a header//has a menu item button that loads the answering scene//clicking answer invokes setShowScene//has a menu item button that loads the writing scene//clicking edit invokes setShowScene
Enter fullscreen modeExit fullscreen mode

Imports andafterEach.

importReactfrom'react';import{render,cleanup,fireEvent}from'@testing-library/react';import'@testing-library/jest-dom/extend-expect';importNavBarfrom'./index';import{SceneTypes}from'../../types';afterEach(cleanup);
Enter fullscreen modeExit fullscreen mode

Write a helper function to render theNavBar. The helper function takes an optional prop functionsetShowScene. We'll use this prop to make sure that theNavBar calls the functionsetShowScene when the user clicks the buttons.

constrenderNavBar=(setShowScene?:(scene:SceneTypes)=>void)=>render(<NavBarshowScene={SceneTypes.answering}setShowScene={setShowScene?setShowScene:(scene:SceneTypes)=>undefined}/>);
Enter fullscreen modeExit fullscreen mode

NavBar Test 1: Has a Menu

File: src/components/NavBar/index.tsx
Will Match:src/components/NavBar/complete/index-1.tsx

NavBar takes two props.setShowScene is a function that accepts aSceneType as a parameter.showScene is theSceneType that is currently being shown.

Clicking the Menu Items will invokesetShowScene with the appropriateSceneType.

importReactfrom'react';import{Menu}from'semantic-ui-react';import{SceneTypes}from'../../types';constNavBar=({setShowScene,showScene}:{setShowScene:(scene:SceneTypes)=>void,showScene:SceneTypes})=><Menudata-testid='menu'/>exportdefaultNavBar;
Enter fullscreen modeExit fullscreen mode

NowNavBar has a menu.

Has a Menu

NavBar Test 2: Has a Header

File: src/components/NavBar/index.test.tsx
Will Match:src/components/NavBar/complete/test-2.tsx

If this weren't a tutorial, and you were designing theNavBar yourself, maybe you wouldn't test ifNavBar has a header. You might decide that the header on the NavBar is not an important enough feature to test. The reason we are testing for the header is that theApp's test checks for theNavBar by finding its header. So we want to be sure when we testNavBar that it has a header, so that when we add it to theApp the tests will pass.

//has a headerit('has a header',()=>{const{getByText}=renderNavBar();constheader=getByText(/flashcard app/i);expect(header).toBeInTheDocument();});
Enter fullscreen modeExit fullscreen mode

Pass NavBar Test 2: Has a Header

File: src/components/NavBar/index.tsx
Will Match:src/components/NavBar/complete/index-2.tsx

Add theMenu.Item header.

<Menudata-testid='menu'><Menu.Itemheadercontent='Flashcard App'/></Menu>
Enter fullscreen modeExit fullscreen mode

Header Passes

NavBar Test 3: Answering Button

File: src/components/NavBar/index.test.tsx
Will Match:src/components/NavBar/complete/test-3.tsx

//has a menu item button that loads the answering sceneit('has a button to get you to the answering scene',()=>{const{getByText}=renderNavBar();constanswering=getByText(/answer/i)expect(answering).toBeInTheDocument();});
Enter fullscreen modeExit fullscreen mode

Pass NavBar Test 3: Answering Button

File: src/components/NavBar/index.tsx
Will Match:src/components/NavBar/complete/index-3.tsx

Theactive prop will highlight theMenu Item when the expression evaluates totrue. ThisMenu Item will be active when theshowScene prop isSceneTypes.answering.

<Menudata-testid='menu'><Menu.Itemheadercontent='Flashcard App'/><Menu.Itemcontent='Answer Flashcards'active={showScene===SceneTypes.answering}/></Menu>
Enter fullscreen modeExit fullscreen mode

Answer Button

NavBar Test 4: Clicking Answering Button

File: src/components/NavBar/index.test.tsx
Will Match:src/components/NavBar/complete/test-4.tsx

//clicking answer invokes setShowSceneit('clicking answer invokes setShowScene',()=>{constsetShowScene=jest.fn();const{getByText}=renderNavBar(setShowScene);constanswering=getByText(/answer/i)fireEvent.click(answering);expect(setShowScene).toHaveBeenLastCalledWith(SceneTypes.answering);});
Enter fullscreen modeExit fullscreen mode

Pass NavBar Test 4: Clicking Answering Button

File: src/components/NavBar/index.tsx
Will Match:src/components/NavBar/complete/index-4.tsx

Add the onClick function to theAnswering button.

<Menu.Itemcontent='Answer Flashcards'active={showScene===SceneTypes.answering}onClick={()=>setShowScene(SceneTypes.answering)}/>
Enter fullscreen modeExit fullscreen mode

onClick

NavBar Tests 5-6: Writing Button

File: src/components/NavBar/index.test.tsx
Will Match:src/components/NavBar/complete/test-5.tsx

//has a menu item button that loads the writing sceneit('has a button to get you to the writing scene',()=>{const{getByText}=renderNavBar();constwriting=getByText(/edit/i)expect(writing).toBeInTheDocument();});//clicking edit invokes setShowSceneit('clicking edit invokes setShowScene',()=>{constsetShowScene=jest.fn();const{getByText}=renderNavBar(setShowScene);constwriting=getByText(/edit/i)fireEvent.click(writing);expect(setShowScene).toHaveBeenLastCalledWith(SceneTypes.writing);});
Enter fullscreen modeExit fullscreen mode

Pass NavBar Tests 5-6: Writing Button

File: src/components/NavBar/index.tsx
Will Match:src/components/NavBar/complete/index-5.tsx

<Menudata-testid='menu'><Menu.Itemheadercontent='Flashcard App'/><Menu.Itemcontent='Answer Flashcards'active={showScene===SceneTypes.answering}onClick={()=>setShowScene(SceneTypes.answering)}/><Menu.Itemcontent='Edit Flashcards'active={showScene===SceneTypes.writing}onClick={()=>setShowScene(SceneTypes.writing)}/></Menu>
Enter fullscreen modeExit fullscreen mode

Alt Text

Ok, now we have aNavBar that passes all the tests! Let's import it into theApp and show it to the user.

Import NavBar into App

File: src/App.tsx
Will Match:src/complete/app-6.tsx

Now let's import theNavBar into theApp. This will makeApp pass the tests we wrote earlier. It will also make theNavBar show up on screen. Once the user can see theNavBar, they will be able to switch between the two scenes. The user will be able to look at theAnswering scene. The user will also be able to look at theWriting scene. TheWriting scene that the user can see will be the placeholder that you wrote earlier in this post. In the next post we will make the actualWriting component.

importNavBarfrom'./components/NavBar';
Enter fullscreen modeExit fullscreen mode

Add theNavBar component into theApp.

//rest of app component stays the samereturn(<CardProvider><StatsProvider>//add the NavBar here<NavBarsetShowScene={setShowScene}showScene={showScene}/>{showScene===SceneTypes.answering&&<Answering/>}{showScene===SceneTypes.writing&&<Writing/>}</StatsProvider></CardProvider>)};
Enter fullscreen modeExit fullscreen mode

Save the App. Most of the tests will pass, but the snapshot test will fail because you have changed what shows up on the screen. Update the snapshot by pressing 'u'. Now all tests should pass.

Run the app withnpm start. You will see theAnswering scene with theNavBar above it.

NavBar1

Click on 'Edit Flashcards'. You will see the placeholderWriting scene.

NavBar2
Great job!

Next Post

In the next post we will make the actualWriting component.

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

Read next

nigelsilonero profile image

How to Backup a Drupal Site in 2025?

{η!б€£ $!£¤η€я¤}• -

anna_golubkova profile image

How to Migrate to Drupal From Another Cms in 2025?

Anna Golubkova -

kartikmehta8 profile image

Developing Hyper-Personalized AI Assistants

Kartik Mehta -

gilles_hamelink_ea9ff7d93 profile image

"Mastering Misinformation: Enhancing LLMs with Innovative Techniques"

Gilles Hamelink -

Legal Aid Lawyer turned fullstack developer. Passionate about improving the user experience by developing efficient and beautiful apps.
  • Location
    Seattle
  • Work
    Developer at a2j Tech
  • Joined

More fromjacobwicks

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