Writing New Cards
Tags: react, typescript, testing, tutorial
Published: 2020-01-17 04:48:48

In this post we will make it possible for the user to write new cards. We will make a new scene called `Writing` where the user can write new cards. In the next post we will make it possible for the user to save the cards that they write to the browsers localStorage, so the cards can persist between sessions. 

##User Stories

* 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.

* The user deletes a card.

* The user changes an existing card and saves their changes.

##Features
The features from the user stories:

* a component that lets the user write new cards
* inputs for question, subject, and answer
* the component can load existing cards
* a button to create a new card that clears the writing component
* a button to save a card into the deck of cards
* a button to delete the current card

In addition to these features, for `Writing` to change existing cards we'll need a way to select cards. The `Selector` component will let the user select cards. We'll write the `Selector` in a later post.

#Writing
In this post we will make `Writing` work. We will change the `CardContext` so that it can handle actions dispatched from `Writing`. Handling actions is how the `CardContext` will add the cards that the user writes to the array of `cards` that the app uses. After we write the test for `Writing` being able to save cards, we will go change the `CardContext` so that saving works. Then we will go back to `Writing` and make the `Save` button work. Same for the new card action.

Handling actions is also how the `CardContext` will delete cards. After we write the test for `Writing` being able to delete cards, we will go change the `CardContext` so that deleting works. Then we will go back to `Writing` and make the `Delete` button work.


##Tests for Writing 
>File: src/scenes/Writing/index.test.tsx
>Will Match: [src/scenes/Writing/complete/test-1.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-1.tsx)

In the last post we didn't write tests for `Writing` because we only made a placeholder component. We made the placeholder because we wanted to make `NavBar` so the user could choose what scene to show. We made the placeholder so that we could see `NavBar` working. Now it is time to make the real `Writing` component. So now it is time to write the tests for `Writing`.

##How to Decide What to Test For
We don't have to test for everything. We want to test for the parts that matter. Think about what we just described the Writing component doing. Creating a new card. Changing a card. Saving changes. Deleting a card. You want to write tests that tell you that these important features work.

Now think about what you know about card objects. Remember the structure of each card: 
```javascript
//File: src/types.ts

//defines the flashcard objects that the app stores and displays
export interface Card {
    //the answer to the question
    answer: string,

    //the question prompt
    question: string,

    //the subject of the question and answer
    subject: string
}
```
##Choose the Components
The user will need a place to enter the answer, the question, and the subject of the card. It is really a [Form](https://react.semantic-ui.com/collections/form/) for the user to fill. So we will use the Semantic UI React Form component.

The subject is probably short, so use an [Input](https://react.semantic-ui.com/elements/input/) for that. The question and the answer can be longer, so use [TextAreas](https://react.semantic-ui.com/addons/text-area/) for those. 

The Input and both TextAreas will have headers so the user knows what they are, but we aren't going to write tests for the headers because they aren't important to how the page functions. Remember from earlier in the App, Semantic UI React TextAreas need to be inside of a [Form](https://react.semantic-ui.com/collections/form/) to look right.

You'll need to give the user a [Button](https://react.semantic-ui.com/elements/button/) to save their card once they've written it. You'll also need to give them a button to create a new card. Let's add a delete button too, so the user can get rid of cards they don't want.

##Write a comment for each test you plan to make:

```javascript
//there's an input where the user can enter the subject of the card
//There's a textarea where the user can enter the question prompt of the card
//there's a textarea where the user can enter the answer to the question
//there's a button to save the card
//when you enter a subject, question, and answer and click the save button a new card is created
//there's a button to create a new card
//when you click the new button the writing component clears its inputs
//there's a button to delete the current card
//when you click the delete button the card matching the question in the question textarea is deleted from the array cards
```

Ok, let's get started writing some code. Write your imports at the top of the test file.
```javascript
import React, { useContext } from 'react';
import { render, cleanup, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { CardProvider, CardContext, initialState } from '../../services/CardContext';
import { CardState } from '../../types';
import Writing from './index';
```

Invoke afterEach
```javascript
afterEach(cleanup);
```

##Helper Component: Displays Last Card
Sometimes we'll want to know if the contents of the cards array has changed. If we add a card or delete a card we want cards to change. But `Writing` only displays the current card. Let's make a helper component that just displays the last card in the cards array. When we want to know if the cards array has changed, we'll render this component and look at what's in it.

```javascript
//displays last card in the cards array
const LastCard = () => {
    const { cards } = useContext(CardContext);
    //gets the question from the last card in the array
    const lastCard = cards[cards.length - 1].question;
  
    return <div data-testid='lastCard'>{lastCard}</div>
};
```

##Helper Function: Render Writing inside CardContext
Write a helper function to render Writing inside of the CardContext. It takes two optional parameters. 

The first paramater is `testState`. `testState` is a `CardState` object, so we can pass in specific values instead of the default `initialState`. 

The second parameter is `child`. `child` accepts JSX elements, so we can pass our LastCard display component in and render it when we want to.

```javascript
const renderWriting = (
    testState?: CardState, 
    child?: JSX.Element 
    ) => render(
        <CardProvider testState={testState}>
            <Writing />
            {child}
        </CardProvider>);
```

##Writing Test 1: Has Subject Input
>File: src/scenes/Writing/index.test.tsx
>Will Match: [src/scenes/Writing/complete/test-1.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-1.tsx)

```javascript
it('has an input to write the subject in', () => {
    const { getByTestId } = renderWriting();
    const subject = getByTestId('subject');
    expect(subject).toBeInTheDocument();
});
```

##Pass Writing Test 1: Has Subject Input
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-2.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-2.tsx)

First, add the imports. 

We are going to use many of the React Hooks to make the form work. [useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback) is a hook that we haven't seen before. Sometimes the way `useEffect` and the setState function from `useState` interact can cause infinite loops. The `useCallBack` hook prevents that. We'll use `useCallBack` to make `useEffect` and `useState` work together to clear out the form when the user switches cards.

```javascript
import React, { 
    useCallback, 
    useContext, 
    useEffect, 
    useState,
} from 'react';

import { 
    Button,
    Container,
    Form,
    Header,
    Input,
    TextArea
} from 'semantic-ui-react';
import { CardContext } from '../../services/CardContext';
import { CardActionTypes } from '../../types';
```

We'll put the `Input` in a `Form`. Give `Inputs` inside a `Form` a name so that you can collect the contents when the user submits the form. The name of this input is 'subject', which is the same as the testId. But the name doesn't have to be the same as the testId, they are completely separate. 

```javascript
const Writing = () =>
    <Form>
        <Header as='h3'>Subject</Header> 
        <Input data-testid='subject' name='subject'/>
    </Form>
```

![Input Pass](https://thepracticaldev.s3.amazonaws.com/i/sct1fcmn36vhq6fqrc15.png)

##Writing Test 2: Has Question TextArea
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/test-2.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-2.tsx)


```javascript
//There's a textarea where the user can enter the question prompt of the card
it('has a textarea to write the question in', () => {
    const { getByTestId } = renderWriting();
    const question = getByTestId('question');
    expect(question).toBeInTheDocument();
});
```

##Pass Writing Test 2: Has Question TextArea
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-3.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-3.tsx)

```javascript
const Writing = () => 
    <Form>
        <Header as='h3'>Subject</Header> 
        <Input data-testid='subject' name='subject'/>
        <Header as='h3' content='Question'/> 
        <TextArea data-testid='question' name='question'/>
    </Form>
```

![TextArea Pass](https://thepracticaldev.s3.amazonaws.com/i/pikvmage128mmnmhsytp.png)

##Writing Test 3: Has Answer TextArea
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/test-3.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-3.tsx)

```javascript
//there's a textarea where the user can enter the answer to the question
it('has a textarea to write the answer in', () => {
    const { getByTestId } = renderWriting();
    const answer = getByTestId('answer');
    expect(answer).toBeInTheDocument();
});
```

##Pass Writing Test 3: Has Question TextArea
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-4.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-4.tsx)

```javascript
const Writing = () => 
    <Form>
        <Header as='h3'>Subject</Header> 
        <Input data-testid='subject' name='subject'/>
        <Header as='h3' content='Question'/> 
        <TextArea data-testid='question' name='question'/>
        <Header as='h3' content='Answer'/> 
        <TextArea data-testid='answer' name='answer'/>
    </Form>
```

![Answer Pass](https://thepracticaldev.s3.amazonaws.com/i/b92ooryh4yuga8gkakw0.png)

##Writing Test 4: Has Save Button
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/test-4.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-4.tsx)

```javascript
//there's a button to save the card
it('has a save button', () => {
    const { getByText } = renderWriting();
    const save = getByText(/save/i);
    expect(save).toBeInTheDocument();
});
```
##Pass Writing Test 4: Has Save Button
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-5.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-5.tsx)

```javascript
    <Form>
        <Header as='h3'>Subject</Header> 
        <Input data-testid='subject' name='subject'/>
        <Header as='h3' content='Question'/> 
        <TextArea data-testid='question' name='question'/>
        <Header as='h3' content='Answer'/> 
        <TextArea data-testid='answer' name='answer'/>
        <Button content='Save'/>
    </Form>
```

![Save Button Pass](https://thepracticaldev.s3.amazonaws.com/i/7xkcfvmpoc17hdghxkse.png)

Run the app, select `Edit Flashcards` and you will see `Writing` on screen.
 
![Writing Component](https://thepracticaldev.s3.amazonaws.com/i/mo7uzpxr7t5b4alpav99.png)
Now it looks good.

#Saving Cards
Now it is time to make saving cards work. When a card is saved, it will be added to the array `cards` in the `CardContext`. To make saving work, we will
* Make the new test for Writing
* Add save to CardActionTypes in types.ts 
* Write the onSubmit function for the Form in Writing
* Make a new test for handling save in the CardContext reducer
* Add a new case 'save' to the CardContext reducer

##Writing Test 5: Saving
>File: src/scenes/Writing/index.test.tsx
>Will Match: [src/scenes/Writing/complete/test-5.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-5.tsx)

To test if saving works, we need to find the `Input` and `TextAreas` and put example text in them. Then we'll find the save button and click it. After that, we check the `textContent` of the `LastCard` helper component and expect it to match the example text.

```javascript
//when you enter a subject, question, and answer and click the save button a new card is created
it('adds a card when you save', () => {
    //the LastCard component just displays the question from the last card in cardContext
    //if we add a card and it shows up in last card, we'll know saving works
    const { getByTestId, getByText } = renderWriting(undefined, <LastCard/>);

    //the strings that we will set the input values to
    const newSubject = 'Test Subject';
    const newQuestion = 'Test Question';
    const newAnswer = 'Test Answer';
    
    //We are using a Semantic UI React Input component
    //this renders as an input inside a div => <div><input></div>
    //so targeting 'subject' will target the outside div, while we want the actual input
    //subject has a children property, which is an array of the child nodes
    //children[0] is the input
    const subject = getByTestId('subject');
    const subjectInput = subject.children[0];
    fireEvent.change(subjectInput, { target: { value: newSubject } });
    expect(subjectInput).toHaveValue(newSubject);

    //The TextArea component doesn't have the same quirk
    //question and answer use TextAreas instead of Input
    const question = getByTestId('question');
    fireEvent.change(question, { target: { value: newQuestion } });
    expect(question).toHaveValue(newQuestion);

    const answer = getByTestId('answer');
    fireEvent.change(answer, { target: { value: newAnswer } });
    expect(answer).toHaveValue(newAnswer);

    const save = getByText(/save/i);
    fireEvent.click(save);

    const lastCard = getByTestId('lastCard');
    expect(lastCard).toHaveTextContent(newQuestion);
});
```
![Save Fails](https://thepracticaldev.s3.amazonaws.com/i/knt1vfo4vg2lulgyovc2.png)

Saving doesn't work yet. We need to add the function that collects the data from the `Form`. We need to dispatch a `save` action to `CardContext`. And we also need to write the case in the `CardContext` `reducer` that will handle the `save` action.

##Types: Add Save to CardActionType
>File: src/types.ts
>Will Match: [src/complete/types-6.ts](https://github.com/jacobwicks/flashcard/blob/master/src/complete/types-6.ts)

Add `save` to `CardActionTypes`. Add a `save` action to `CardAction`. The `save` action takes three strings: answer, question, and subject.
```javascript
//the types of action that the reducer in CardContext will handle
export enum CardActionTypes {
    next = 'next',
    save = 'save'
};

export type CardAction =    
    //moves to the next card
    | { type: CardActionTypes.next }
    
    //saves a card
    | { type: CardActionTypes.save, answer: string, question: string, subject: string }
```

##Pass Writing Test 5: Saving
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-6.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-6.tsx)

Add the function to collect data from the `Form`. When a form is submitted, the form emits and event that you can get the value of the inputs from. The data type of the form submission event is `React.FormEvent<HTMLFormElement>`. 

First we prevent the default Form handling by calling the `preventDefault` method of the form event. Then we make a new FormData object from the event.

After we turn the event into a FormData object, we can get the values of the inputs from it using the `get` method and the name of the input. We named our inputs 'answer,' 'subject,' and 'question' so those are the names we'll `get` out of the form event and assign to variables. 

Once we have assigned the input values to variables, we can do whatever we need to with them. We'll dispatch them as a `save` action to the `CardContext`. Later we will write the code for `CardContext` to handle a `save` action, and then dispatching a `save` action will result in a new card being added to the array `cards` in the `CardContext`.

```javascript
const Writing = () => {
    const { dispatch } = useContext(CardContext);

    return (
    <Form onClick={(e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        const card = new FormData(e.target as HTMLFormElement);
        const answer = card.get('answer') as string;
        const question = card.get('question') as string;
        const subject = card.get('subject') as string;

        dispatch({
            type: CardActionTypes.save,
            answer,
            question,
            subject
        });
    }}>
```

This still won't pass the test named 'adds a card when you save.' We need to add a `save` case to the `CardContext` reducer so it can handle the `save` action.

##CardContext Tests 1-2: Handling Save in the CardContext Reducer
>File: src/services/CardContext/index.test.tsx
>Will Match: [src/services/CardContext/complete/test-8.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/services/CardContext/complete/test-8.tsx)

We'll write our tests inside the 'CardContext reducer' describe block.
Write a quote for each test we are going to write. `save` will add a new card to the context. `save` can also save changes to a card. If a card with the question from the `save` action already exists, `save` will overwrite that card.

```javascript
    //save new card
    //save changes to existing card
```

To test the `reducer`, we need to create an action. Then we pass the state and the action to the `reducer` and look at the results.

In this test we use two new array methods. `Array.findIndex` and `Array.filter`.

[Array.findIndex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) accepts a function and returns a number. It will iterate over each element in the array and pass the element to the function. If it finds an element that returns true from the function, `findIndex` will return the index of that element. If it does not find an element that returns true from the function, then it will return -1.

We use `findIndex` to make sure that the `cards` array from `initialState` does not already contain the example text.

[Array.filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) accepts a function and returns a new array. It will iterate over each element in the array and pass the element to the function. If the element returns true from the function, then it will be added to the new array. If the element does not return true from the function, it will be 'filtered out' and will not be added to the new array. 

We use `filter` to check that the `cards` array has a card with the example text after the `reducer` handles the `save` action. We filter out all cards that don't have the example text. We check the `length` property of the resulting array, and expect that it's equal to 1. The `length` should be equal to 1 because the array should only contain the card that was just added.
 
```javascript
 //save new card
    it('save action with new question saves new card', () => {
        const answer = 'Example Answer';
        const question = 'Example Question';
        const subject = 'Example Subject';

        //declare CardAction with type of 'save'
        const saveAction: CardAction = { 
            type: CardActionTypes.save,
            question,
            answer,
            subject
        };

        //before the action is processed initialState should not have a card with that question
        expect(initialState.cards.findIndex(card => card.question === question)).toEqual(-1);


        //pass initialState and saveAction to the reducer 
        const { cards } = reducer(initialState, saveAction);
        //after the save action is processed, should have one card with that question
        expect(cards.filter(card => card.question === question).length).toEqual(1);

        //array destructuring to get the card out of the filtered array
        const [ card ] = cards.filter(card => card.question === question);

        //the saved card should have the answer from the save action
        expect(card.answer).toEqual(answer);

        //the saved card should have the subject from the save action
        expect(card.subject).toEqual(subject);
   });
```

To test saving changes to an existing card, we create `existingState`, a `cardState` with a `cards` array that includes our example card. Then we create a `save` action and send the state and the action to the `reducer`. We use `filter` to check that the `cards` array still just has one copy of the card. We expect the contents of the card to have changed.

```javascript
    //save changes to existing card
    it('save action with existing question saves changes to existing card', () => {
        const answer = 'Example Answer';
        const question = 'Example Question';
        const subject = 'Example Subject';

        const existingCard = {
            answer,
            question,
            subject
        };
       
        const existingState = {
            ...initialState,
            cards: [
                ...initialState.cards, 
                existingCard
            ]};

        const newAnswer = 'New Answer';
        const newSubject = 'New Subject';

        //declare CardAction with type of 'save'
        const saveAction: CardAction = { 
            type: CardActionTypes.save,
            question,
            answer: newAnswer,
            subject: newSubject
        };

        //the state should have one card with that question
        expect(existingState.cards.filter(card => card.question === question).length).toEqual(1);

        //pass initialState and saveAction to the reducer 
        const { cards } = reducer(initialState, saveAction);

        //Ater processing the action, we should still only have one card with that question
        expect(cards.filter(card => card.question === question).length).toEqual(1);

        //array destructuring to get the card out of the filtered array
        const [ card ] = cards.filter(card => card.question === question);

        //answer should have changed
        expect(card.answer).toEqual(newAnswer);
        //subject should have changed
        expect(card.subject).toEqual(newSubject);
    });
```
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/c8u1ofoa90y7pq0r223h.png)

##Pass CardContext Tests 1-2: Handling Save in the CardContext Reducer
>File: src/services/CardContext/index.tsx
>Will Match: [src/services/CardContext/complete/index-5.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/services/CardContext/complete/index-5.tsx)

Add a new case 'save' to the `CardContext` `reducer`. Add `save` to the switch statement. I like to keep the cases in alphabetical order. Except for default, which has to go at the bottom of the switch statement.

To make saving work, we use `findIndex` to get the index of the card in the `cards` array. We create a card object using the values received from the action, and put it into the `cards` array.

###Create a New Cards Array
When you write a reducer, you don't want to change the existing state object. You want to create a new state object and return it. If you just grab a reference to the cards array from state and start adding or deleting cards from it, you could cause some difficult to track down bugs. So instead of doing that, you want to make a copy of the array, then change the copy.

In the `save` case, we create a new array using `Array.filter`. Then we work with that array. In the `delete` case that we'll write later, we'll use the spread operator to create a new array.

```javascript
    const newCards = cards.filter(v => !!v.question);
``` 

This line of code is doing a couple of things. `cards.filter` creates a new array. `!!` is the cast to boolean operator. So it casts any value to true or false. 

The function `v => !!v.question` means that any card with a question that is ['falsy'](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) will be filtered out of the array. I wrote this in here to clear out some example cards that I had written that didn't have questions, which caused some problems with the app. I have left it in here as an example of how you can prevent poorly formed objects from reaching your components and causing a crash.

```javascript
      case 'save' :{
        const { cards } = state;
        const { answer, question, subject, } = action;
        
        //get the index of the card with this question
        //if there is no existing card with that question
        //index will be -1
        const index = cards
        .findIndex(card => card.question === question);

        //A card object with the values received in the action
        const card = {
            answer,
            question,
            subject
        } as Card;

        //create a new array of cards
        //filter out 'invalid' cards that don't have a question
        const newCards = cards.filter(v => !!v.question);

        //if the question already exists in the array
        if (index > -1) {
            //assign the card object to the index 
            newCards[index] = card;
        } else {
            //if the question does not already exist in the array
            //add the card object to the array
            newCards.push(card);
        }

        //return new context
        return {
            ...state,
            cards: newCards
        }
    }
```

![Reducer Passes](https://thepracticaldev.s3.amazonaws.com/i/pk5unsfhggh03i89ia6k.png)

Look at the code above. Do you understand how it works? Does it prevent adding a card with no question? How would you rewrite it to make adding a card with no question impossible? 

Do you think it is actually possible for the user to use the `Writing` component to add a card with no question? Or would the question always at least be an empty string?
 
##Run the Tests For Writing
Use Jest commands to run the tests for `Writing`. 
![Writing Passing Save](https://thepracticaldev.s3.amazonaws.com/i/apedjh1hfn5elovjzxns.png)
They pass!

#Loading the Current Card into Writing
We want the `Input` and `TextArea`s in the `Form` to automatically load the values of the current card. To do that, we will make them into controlled components. Remember that controlled components are components that take their values as a prop that is held in state. When the value of a controlled component is changed, it invokes a function to handle the change. The `useState` hook will let us make the `Input` and `TextArea`s into controlled components.

##Writing Test 6: Loads Current Card
>File: src/scenes/Writing/index.test.tsx
>Will Match: [src/scenes/Writing/complete/test-6.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-6.tsx)

Write a test for loading the current card. We'll write the same `withoutLineBreaks` function that we've written before. Pull a reference to the current card from `initialState`.  

There is always a danger of introducing errors into your tests when you use references to objects instead of using hardcoded values. Especially when you reference objects that are imported from other code.

What assertion would you add to this test to make sure that you know if the variable `card` is undefined? How about assertions that would warn you if it was missing the question, subject, or answer? 

```javascript
//when you load writing, it loads the current card
it('loads the current card', () => {
    //the question and answer may have linebreaks
    //but the linebreaks don't render inside the components
    //this function strips the linebreaks out of a string 
    //so we can compare the string to text content that was rendered
    const withoutLineBreaks = (string: string) => string.replace(/\s{2,}/g, " ")

    //we'll test with the first card
    const card = initialState.cards[initialState.current];
    const { getByTestId } = renderWriting();
    
    //a textarea
    const answer = getByTestId('answer');
    expect(answer).toHaveTextContent(withoutLineBreaks(card.answer));

    //a textarea
    const question = getByTestId('question');
    expect(question).toHaveTextContent(withoutLineBreaks(card.question));

    // semantic-ui-react Input. It renders an input inside of a div
    //so we need the first child of the div
    //and because it's an input, we test value not textcontent
    const subject = getByTestId('subject').children[0];
    expect(subject).toHaveValue(card.subject);
});
```

##Pass Writing Test 6: Loads Current Card
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-7.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-7.tsx)

The `useState` hook lets us store the value of the cards. Notice the starting value of the `useState` hooks is an expression using the ternary operator. If `card` evaluates to *true*, then the starting value will be a property of the `card` object. If `card` evaluates to *false*, the starting value will be an empty string.
 
```javascript
const Writing = () => {
    const { cards, current, dispatch } = useContext(CardContext);
   
    //a reference to the current card object
    const card = cards[current];

    //useState hooks to store the value of the three input fields
    const [question, setQuestion ] = useState(card ? card.question : '')
    const [answer, setAnswer ] = useState(card ? card.answer : '')
    const [subject, setSubject ] = useState(card ? card.subject : '');

    return (
```
Make the `Input` and the `TextAreas` into controlled components. Notice the onChange function is different for `Inputs` and `TextAreas`. 

In the onChange function for `question`, you can see that we use Object Destructuring on the second argument and get the property 'value' out of it. Then we call the setQuestion function with value. There's an exclamation point after value but before the call to the `toString` method.

```javascript
onChange={(e, { value }) => setQuestion(value!.toString())}
```

The exclamation point is the TypeScript [non null assertion operator](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html). The non null assertion operator tells TypeScript that even though the value could technically be null, we are sure that the value will not be null. This prevents TypeScript from giving you an error message telling you that you are trying to use a value that could possibly be null in a place where null will cause an error.

```javascript
        <Header as='h3'>Subject</Header> 
        <Input data-testid='subject' name='subject'
            onChange={(e, { value }) => setSubject(value)}
            value={subject}/>
        <Header as='h3' content='Question'/> 
        <TextArea data-testid='question' name='question'
             onChange={(e, { value }) => setQuestion(value!.toString())}
             value={question}/>
        <Header as='h3' content='Answer'/> 
        <TextArea data-testid='answer' name='answer'
            onChange={(e, { value }) => setAnswer(value!.toString())}
            value={answer}/>
        <Button content='Save'/>
    </Form>
)};
```

![Writing Loads Current Card](https://thepracticaldev.s3.amazonaws.com/i/oy5vqeni9oz9m0kh3tr4.png)

#New Card
We need a button that lets the user write a new card. The way the new card button will work is it will dispatch a `new` action to the `CardContext`. The `CardContext` `reducer` will handle the `new` action and set `current` to -1. When current is -1, `Writing` will will try to find the current card. The current card will evaluate to false, and all the controlled components in the `Writing` `Form` will be cleared out.

##Writing Test 7: Has a New Card Button
>File: src/scenes/Writing/index.test.tsx
>Will Match: [src/scenes/Writing/complete/test-7.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-7.tsx)

Make a describe block named 'the new card button.' Test for an element with the text 'new.' Use the `getByText` method.
```javascript
describe('the new card button', () => {
    //there's a button to create a new card
    it('has a new button', () => {
        const { getByText } = renderWriting();
        const newButton = getByText(/new/i);
        expect(newButton).toBeInTheDocument();
    });
    
    //when you click the new button the writing component clears its inputs
});
```

##Pass Writing Test 7: Has a New Card Button
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-8.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-8.tsx)

Wrap the form in a container. Notice that the container has a [style](https://reactjs.org/docs/dom-elements.html#style) prop. The style prop lets us apply css styles to React components. This `Container` is 200 pixels away from the left edge of the screen. This gives us space for the `Selector` component that we'll write later.

Put the `New Card` button inside the `Container`.
```javascript
        <Container style={{position: 'absolute', left: 200}}>
            <Button content='New Card'/>
            <Form 
                onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
//Rest of The Form goes here
            </Form>
        </Container>
```

![Has a New Card Button](https://thepracticaldev.s3.amazonaws.com/i/najd3akccy5i84larx9c.png)

##Writing Test 8: New Card Button Clears Inputs
>File: src/scenes/Writing/index.test.tsx
>Will Match: [src/scenes/Writing/complete/test-8.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-8.tsx)

When the user clicks 'New Card' we want to give them an empty `Writing` component to work in. Write this test inside the new card describe block. We expect the textContent of the `TextArea`s to be falsy. We expect the `Input` not to have value. This is due to the difference in the way the components work.

```javascript
 //when you click the new button the writing component clears its inputs
    it('when you click the new card button the writing component clears its inputs', () => {
        const { getByText, getByTestId } = renderWriting();

        const answer = getByTestId('answer');
        expect(answer.textContent).toBeTruthy();

        const question = getByTestId('question');
        expect(question.textContent).toBeTruthy();

        const subject = getByTestId('subject').children[0];
        expect(subject).toHaveValue();

        const newButton = getByText(/new/i);
        fireEvent.click(newButton);

        expect(answer.textContent).toBeFalsy();
        expect(question.textContent).toBeFalsy();
        expect(subject).not.toHaveValue();
    })
```

##Types: Add New to CardActionType
>File: src/types.ts
>Will Match: [src/complete/types-7.ts](https://github.com/jacobwicks/flashcard/blob/master/src/complete/types-7.ts)

Add 'new' to CardActionTypes. Add a 'new' action to CardAction.
```javascript
//the types of action that the reducer in CardContext will handle
export enum CardActionTypes {
    new = 'new',
    next = 'next',
    save = 'save'
};

export type CardAction =
    //clears the writing component
    | { type: CardActionTypes.new }
    
    //moves to the next card
    | { type: CardActionTypes.next }
    
    //saves a card
    | { type: CardActionTypes.save, answer: string, question: string, subject: string }
```

##Work on Passing Writing Test 8: New Card Button Clears Inputs
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-9.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-9.tsx)

Add the Function to Dispatch the New Action to the New Card Button
```javascript
   <Button content='New Card' onClick={() => dispatch({type: CardActionTypes.new})}/>
```

##CardContext Test 3: Handling 'New' Action in the CardContext Reducer
>File: src/services/CardContext/index.test.tsx
>Will Match: [src/services/CardContext/complete/test-9.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/services/CardContext/complete/test-9.tsx)

We'll write our test inside the 'CardContext reducer' describe block.

Write a comment for the test we are going to write. New will just set current to -1, which won't return a valid card from cards.

```javascript
    //new action returns current === -1
```

Write the test.
```javascript
    //new action returns current === -1
       it('new sets current to -1', () => {
        //declare CardAction with type of 'new'
        const newAction: CardAction = { type: CardActionTypes.new };


        //create a new CardState with current === 0
        const zeroState = {
            ...initialState,
            current: 0
        };

        //pass initialState and newAction to the reducer 
        expect(reducer(zeroState, newAction).current).toEqual(-1);
    });
```

##Pass CardContext Test 3: Handling 'New' Action in the CardContext Reducer
>File: src/services/CardContext/index.tsx
>Will Match: [src/services/CardContext/complete/index-6.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/services/CardContext/complete/index-6.tsx)

This is the simplest case we'll write. Add it to the switch statement inside the `reducer`.

```javascript
case 'new': {
            return {
                ...state,
                current: -1
            }
          }
```

![Case New Passes](https://thepracticaldev.s3.amazonaws.com/i/je47zkj8nqv5ujaztx5t.png)

Ok, now we are ready to make `Writing` clear out its inputs when the `New Card` button is clicked.

##Pass Writing Test 8: New Card Button Clears Inputs
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-10.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-10.tsx)

```javascript
    //a function that sets all the states to empty strings    
    const clearAll = useCallback(
        () => {
            setQuestion('');
            setAnswer('');
            setSubject('');
    }, [
        setQuestion,
        setAnswer,
        setSubject
    ]);

    //a useEffect hook to set the state to the current card
    useEffect(() => {
        if (!!card) {
            const { question, answer, subject } = card;
            setQuestion(question);
            setAnswer(answer);
            setSubject(subject);
        } else {
            clearAll();
        };
    }, [
        card,
        clearAll 
    ]);

return (
```
Now writing will clear its inputs when the New Card button is clicked.
![Writing Pass New](https://thepracticaldev.s3.amazonaws.com/i/tpgkoiq0mtgfvchvv1sx.png)

Run the app. Try it out. Open the Writing scene. Click 'New Card.' The inputs will clear. But what happens if you click back to Answering from a new card? 

![It Crashes](https://thepracticaldev.s3.amazonaws.com/i/w9zouv9sinrlo4226ndo.gif)

It crashes! Let's fix that.
 
##Fix the Crash When Switching From New Card to Answering
Answering uses Object Destructuring to get the question out of the card at the current index in cards. But the `new` action sets current to -1, and -1 isn't a valid index. `cards[-1]` is undefined, and you can't use Object Destructuring on an undefined value. 

####How would you fix this problem?

We could rewrite `Answering` to do something else if the current index does not return a valid card. We could display an error message, or a loading screen. But what we are going to do is change the `NavBar`. We'll make the `NavBar` dispatch a `next` action to `CardContext` if the user tries to navigate to `Answering` when current is -1. `CardContext` will process the `next` action and return a valid index for a card.

##NavBar Test 1: Clicking Answer When Current Index is -1 Dispatches Next 
>File: src/components/NavBar/index.test.tsx
>Will Match: [src/components/NavBar/test-6.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/components/NavBar/complete/test-6.tsx)

For this test, we'll use jest.fn() to make a mock dispatch function. Remember that using jest.fn() allows us to see whether dispatch has been called, and what the arguments were.

`negativeState` is a `CardState` with current set to negative 1. Add in the mock dispatch function.

find the `Answering` button and click it. Then expect the mock dispatch function to have been called with a `next` action.

```javascript
it('clicking answer when current index is -1 dispatches next action', () => {
    const dispatch = jest.fn();
    
    const negativeState = {
        ...initialState,
        current: -1,
        dispatch
    };

    const { getByText } = render(    
        <CardContext.Provider value={negativeState}>
            <NavBar 
                showScene={SceneTypes.answering} 
                setShowScene={(scene: SceneTypes) => undefined}/>
        </CardContext.Provider>)

    const answering = getByText(/answer/i);
    fireEvent.click(answering);

    expect(dispatch).toHaveBeenCalledWith({type: CardActionTypes.next})
});
```

![NavBar Fail](https://thepracticaldev.s3.amazonaws.com/i/3hpzi31g4jvjemghk8vu.png)

##Pass NavBar Test 1: Clicking Answer When Current Index is -1 Dispatches Next 
>File: src/components/NavBar/index.tsx
>Will Match: [src/components/NavBar/index-6.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/components/NavBar/complete/index-6.tsx)

Import `useContext`.
```javascript
import React, { useContext } from 'react';
```
Import `CardContext` and `CardActionTypes`.
```javascript
import { CardContext }  from '../../services/CardContext';
import { CardActionTypes } from '../../types';
```

Get current and dispatch from the `CardContext`.
Change the onClick function for the 'Answer Flashcards' `Menu.Item`. Make it dispatch a `next` action if `current` is -1.

```javascript
const NavBar = ({
    setShowScene,
    showScene
}:{
    setShowScene: (scene: SceneTypes) => void,
    showScene: SceneTypes
}) => {
    const { current, dispatch } = useContext(CardContext);
    return(
        <Menu data-testid='menu'>
        <Menu.Item header content='Flashcard App'/>
        <Menu.Item content='Answer Flashcards' 
            active={showScene === SceneTypes.answering}
            onClick={() => {
                current === -1 && dispatch({type: CardActionTypes.next});
                setShowScene(SceneTypes.answering)
            }}
        />
        <Menu.Item content='Edit Flashcards'
            active={showScene === SceneTypes.writing}
            onClick={() => setShowScene(SceneTypes.writing)}
        />
    </Menu>
)};
```

![NavBar Pass](https://thepracticaldev.s3.amazonaws.com/i/xw6jnl5qahdphp6ufjzi.png)
Now the app won't crash anymore when you switch from Writing a new card back to Answering.

#Deleting Cards
Now it is time to make deleting cards work. To make deleting work, we will
* Make the new test for the deleting cards button in `Writing`
* Add delete to `CardActionTypes` in types.ts 
* Write the onSubmit function for the `Form` in `Writing`
* Make a new test for handling `delete` in the `CardContext` `reducer`
* Add a new case 'delete' to the `CardContext` `reducer`

##Writing Test 9: Has a Delete Card Button
>File: src/scenes/Writing/index.test.tsx
>Will Match: [src/scenes/Writing/complete/test-9.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-9.tsx)

Make a describe block 'the delete card button.'
```javascript
describe('the delete card button', () => {
    //there's a button to delete the current card
    it('has a delete button', () => {
        const { getByText } = renderWriting();
        const deleteButton = getByText(/delete/i);
        expect(deleteButton).toBeInTheDocument();
    });
    
    //when you click the delete button the card matching the question in the question textarea is deleted from the array cards
});
```

##Pass Writing Test 9: Has a Delete Card Button
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-11.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-11.tsx)

```javascript
    <Button content='New Card' onClick={() => dispatch({type: CardActionTypes.new})}/>
            <Button content='Delete this Card'/>
            <Form
```
![Delete Button Pass](https://thepracticaldev.s3.amazonaws.com/i/uoe9cuu6ten3w4gf6i3e.png)


##Writing Test 10: Clicking Delete Card Button Deletes Current Card
>File: src/scenes/Writing/index.test.tsx
>Will Match: [src/scenes/Writing/complete/test-10.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/test-10.tsx)

We use the helper component `LastCard` to test if the card gets removed from the `cards` array.
```javascript
   //when you click the delete button the card matching the question in the question textarea is deleted from the array cards
    it('clicking delete removes the selected question', () => {
        const lastIndex = initialState.cards.length - 1;
        const lastState = {
            ...initialState,
            current: lastIndex
        };
        const lastQuestion = initialState.cards[lastIndex].question;

        const { getByTestId, getByText } = renderWriting(lastState, <LastCard />);
        
        const lastCard = getByTestId('lastCard');
        expect(lastCard).toHaveTextContent(lastQuestion);

        //call this deleteButton, delete is a reserved word
        const deleteButton = getByText(/delete/i);
        fireEvent.click(deleteButton);

        expect(lastCard).not.toHaveTextContent(lastQuestion);
    });
```

##Types.ts: Add Delete to CardActionType
>File: src/types.ts
>Will Match: [src/complete/types-8.ts](https://github.com/jacobwicks/flashcard/blob/master/src/complete/types-8.ts)

Add 'delete' to `CardActionTypes`. Add a `delete` action to `CardAction`. The `delete` action takes a question string. When we handle the action in the `CardContext` `reducer` we'll use the question to find the card in the cards array.

```javascript
//the types of action that the reducer in CardContext will handle
export enum CardActionTypes {
    delete = 'delete',
    new = 'new',
    next = 'next',
    save = 'save'
};

export type CardAction =
    //deletes the card with matching question
    | { type: CardActionTypes.delete, question: string }

    //clears the writing component
    | { type: CardActionTypes.new }    
    
    //moves to the next card
    | { type: CardActionTypes.next }
    
    //saves a card
    | { type: CardActionTypes.save, answer: string, question: string, subject: string }
```

###Add the Function to Dispatch the 'Delete' Action to the Delete Card Button
>File: src/scenes/Writing/index.tsx
>Will Match: [src/scenes/Writing/complete/index-12.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/scenes/Writing/complete/index-12.tsx)

```javascript
 <Button content='Delete this Card' onClick={() => dispatch({type: CardActionTypes.delete, question})}/>
```

##CardContext Test 4: CardContext Reducer Handles Delete Action
>File: src/services/CardContext/index.test.tsx
>Will Match: [src/services/CardContext/complete/test-9.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/services/CardContext/complete/test-9.tsx)

We'll write the test inside the 'CardContext reducer' describe block.
Write a quote for each test we are going to write. Delete will remove the card with the matching question from the array cards.

Write the test. Use `findIndex` to check the `cards` array for a card with the deleted question. When `findIndex` doesn't find anything, it returns -1.

```javascript
//delete removes card with matching question
    it('delete removes the card with matching question', () => {
        const { question } = initialState.cards[initialState.current];

        const deleteAction: CardAction = { 
            type: CardActionTypes.delete,
            question
        };

        const { cards } = reducer(initialState, deleteAction);
        
        //it's gone
        expect(cards.findIndex(card => card.question === question)).toEqual(-1);
    });
```

##Pass CardContext Test 4: CardContext Reducer Handles Delete Action
>File: src/services/CardContext/index.tsx
>Will Match: [src/services/CardContext/complete/index-7.tsx](https://github.com/jacobwicks/flashcard/blob/master/src/services/CardContext/complete/index-7.tsx)

Add a new case 'delete' to the `CardContext` `reducer`. Add `delete` to the switch statement. I like to keep the cases in alphabetical order. Except for default, which has to go at the bottom.

```javascript
 case 'delete': {
            let { cards, current } = state;
            //the question is the unique identifier of a card
            const { question } = action;
            
            ///creating a new array of cards by spreading the current array of cards
            const newCards = [...cards];
            
            //finds the index of the target card
            const index = newCards.findIndex(card => card.question === question);
            
            //splice removes the target card from the array
            newCards.splice(index, 1);

            //current tells the components what card to display
            //decrement current
            current = current -1;

            //don't pass -1 as current
            if(current < 0) current = 0;

            //spread the old state
            //add the new value of current
            //and return the newCards array as the value of cards
            return {
                ...state,
                current,
                cards: newCards
            }
        }
```
CardContext passes the test.

![CardContext Delete Passes](https://thepracticaldev.s3.amazonaws.com/i/fq356vvxlpov91p7p8v0.png)

The delete button in Writing works too!

![Writing Delete Button Passes](https://thepracticaldev.s3.amazonaws.com/i/q8g041577xtmy2fmnza8.png)

Great! Now what happens when you delete all the cards and click back to the Answering screen? How would you fix it?

####Next Post: Saving and Loading
In the next post we will write the code to save and load cards to the browser's localStorage. In the post after that we will write the Selector that lets the user choose which card to look at.