Views
Now we are ready to start working towards the beef of the application: different pages (or views).
IndexView
We will begin by creating a file called IndexView.tsx
(remember that 'x' in the end of the file type means that it contains jsx) inside a folder called index
inside the components
-folder:
All of our pages will be inside folders named after the page, in this case Index and the view will be named
[Pagename]View.tsx
import * as React from 'react';
import Todo from '../../common/Todo';
import TodoComponent from '../../components/TodoComponent';
import Button from '../../components/Button';
import Loader from '../../components/Loader';
interface IIndexState {
title: string;
todos: Todo[];
loading: boolean;
}
interface IIndexDispatch {
setTitle(n: string): void;
saveTodo(): void;
setDone(i: number): void;
}
type IIndexProps = IIndexState & IIndexDispatch;
const IndexView: React.StatelessComponent<IIndexProps> = ({ title, todos, loading, setTitle, saveTodo, setDone }) => (
<main className="index">
{loading && <Loader />}
<h1 className="index__header">Todo app</h1>
<form className="index__form" onSubmit={e => e.preventDefault()}>
<label className="index__form__label" htmlFor="newtodo">Add a new todo:</label>
<input
className="index__form__input"
name="newtodo"
type="text"
autoFocus
value={title}
onChange={e => setTitle(e.target.value)}
/>
<Button click={saveTodo} text="Add" />
</form>
<br />
<section className="index__todo-container">
{todos.map(t => <TodoComponent todo={t} setDone={setDone} key={t.id} />)}
</section>
</main>
);
export default IndexView;
The first interface we declare, called IIndexState
import Todo from '../../common/Todo';
interface IIndexState {
title: string;
todos: Todo[];
loading: boolean;
}
is an interface
that holds the "state"-values for our View
, i.e. the stuff that defines what is shown to the user, in this case a title (the title of the current Todo
being created), a list of Todo
s (the current Todo
s) and a boolean that indicates whether or not some kind of long-taking call is currently running.
The second interface
we declare, called IIndexDispatch
interface IIndexDispatch {
setTitle(n: string): void;
saveTodo(): void;
setDone(i: number): void;
}
is an interface
that holds all the "dispatch"-functions for our View
, i.e. all the functionality that our users can trigger, in this case a function (setTitle(n: string)
) to change the current title
(that takes a string as an argument), a function (saveTodo()
) to save the new Todo
and a function (setDone(i: number)
) to set an existing Todo
as done (that takes the number of the Todo
as argument).
Having
void
as a return type allows us to not care about the return type (which we don't need here) without having an implicit any
Together IIndexState
and IIndexDispatch
form the type IIndexProps
type IIndexProps = IIndexState & IIndexDispatch;
which is the definition of all the props our IndexView
will need to function.
The
IIndexState & IIndexDispatch
part defines that the type calledIIndexProps
is an intersection of those two interfaces, meaning that to fulfill the type, the object needs to have all values present in both of the aforementioned interfaces.
And now we get to the beef of it all, starting with declaring the actual IndexView
import * as React from 'react';
const IndexView: React.StatelessComponent<IIndexProps> = ({ title, todos, loading, setTitle, saveTodo, setDone }) => (
);
in which we declare a constant called IndexView
which is of the type React.StatelessComponent<IIndexProps>
. React.StatelessComponent<T>
is a type of React Component that does not have an internal state, only props and can be declared as a function that returns jsx
, where the type of props received is put inside the angle brackets (where T
is now).
These kinds of declarations are called type arguments or generics, which allow you to define a function without knowing beforehand what the type of the argument or return value is.
After declaring the const we define IndexView
to be a function, that fulfills the render function, i.e. takes in an object containing the declared properties (using object destructuring so we don't have to write something.title
, instead of title
) and returns jsx
encapsulated in the brackets.
For the actual content inside the brackets you can think of it as "HTML on steroids", beginning, as is semantically correct with a main
tag
<main className="index">
</main>
which will hold all the content of our IndexView
(and here we begin the introduction of BEM-naming).
Next is our first tag that is different with different props
import Loader from '../../components/Loader';
// ...
<main className="index">
{loading && <Loader />}
</main>
which means, that depending if the prop loading
is true (using logical operator-shorthand that returns the right hand-side if the both sides evaluate as true), we want to render the Loader
-component we defined earlier in the Components section.
Next up we define the title and a form to add a new Todo
<main className="index">
// ...
<h1 className="index__header">Todo app</h1>
<form className="index__form" onSubmit={e => e.preventDefault()}>
<label className="index__form__label" htmlFor="newtodo">Add a new todo:</label>
<input
className="index__form__input"
name="newtodo"
type="text"
autoFocus
value={title}
onChange={e => setTitle(e.target.value)}
/>
<Button click={saveTodo} text="Add" />
</form>
</main>
where we first define the title (in this case a static string Todo app
). Next we define a form to create new Todos
which introduces us to a lot of nice features of React, but first we do a little "hack" as we want to enable the user to press the enter
-key when submitting a new Todo
but we don't want to send the form to a new page, we set the value of onSubmit
(the handler for a form's submit method in React) as e => e.preventDefault()
where e
is the event received and it's function .preventDefault()
will (as its name implies) prevent the default functionality (in this case send the form data to the current url, causing a reload).
Next we define a label for the input
-field utilizing React's htmlFor
-value which is an alias for the for-attribute, except it uses the name
-field instead of id
for matching. Then we define the actual input
-field for inputting a Todo
, which is a simple text input
, but we also make it autofocus (if you open the page, you can start typing into it directly) and make it a controlled component, meaning that whenever the value of title
changes, the input
will also update. We also add the onChange
-listener to actually set the new title
the user has typed in. Finally we create a simple Button
(we defined earlier in components) to submit the form.
Finally we want to of course show all the Todo
s created
<main className="index">
// ...
<section className="index__todo-container">
{todos.map(t => <TodoComponent todo={t} setDone={setDone} key={t.id} />)}
</section>
</main>
where we first encapsulate it into a semantic tag called section
. Then we want to create a new TodoComponent
(as we defined in components) for every Todo
in our current state, which can be achieved by a simple map
(I highly recommend getting to know all the major Array-functions). Here you want to also remember to add a property for our TodoComponent
it isn't expecting to receive: key
which React uses to distinguish between tags in a list (it has to be unique inside the list).