Skip to content

Latest commit

 

History

History
135 lines (105 loc) · 4.93 KB

components.md

File metadata and controls

135 lines (105 loc) · 4.93 KB

Components

A basic counter

Example code

A Pux component is a module that exports four parts:

  • A type that represents actions taken by the user.
  • A type that represents the component's state.
  • An update function which produces a new state from actions.
  • A view function which produces HTML from the current state.
data Action = Increment | Decrement

type State = Int

update :: Action -> State -> State

view :: State -> Html Action

We'll explain each of these parts by building an application out of a simple counter component. The counter will display a count, along with buttons to increment and decrement it.

First, we define a type for the state of our application. In a complex web app our state would be modeled using a record type, but because our counter component only needs to keep the current count we can simply create a type alias for Int:

type State = Int

Every appliction or component must provide an update function, which produces a new state from actions taken by the user. In our case, we need to define actions that represent incrementing and decrementing the counter:

data Action = Increment | Decrement

Using a union type clearly describes the available actions, and if necessary can also encapsulate data associated with an action. We'll see how actions can carry data in a later section.

Next, we define an update function which produces a new state in response to user actions. The logic for our counter is very simple:

update :: Action -> State -> State
update Increment count = count + 1
update Decrement count = count - 1

You'll notice that the update function above is defined twice, for both Increment and Decrement. This is called pattern matching, and can be used as a simpler alternative to case expressions.

The update function is analogous to foldl, which receives the current action taken by the user, along with the previous state and returns a new state. If you're wondering about effects (such as data fetching or logging), that is covered in a later section Fetching data.

Now that the business logic is defined, we need a way to view the component state. Pux provides an Html type for constructing views:

import Prelude (const)
import Pux.Html (Html, div, span, button, text)
import Pux.Html.Events (onClick)

view :: State -> Html Action
view count =
  div
    []
    [ button [ onClick (const Increment) ] [ text "Increment" ]
    , span [] [ text (show count) ]
    , button [ onClick (const Decrement) ] [ text "Decrement" ]
    ]

Html a is the type that represents the virtual DOM tree (React elements), and is parameterized by an action type a, which represent the actions a view may send to the input channel. Those actions are fed into the update function we defined earlier to produce a new counter state.

Each element constructor takes an array of attributes and an array of children as properties. Even childless elements like img have this type, because it makes mapping over html or other manipulations much simpler.

onClick, along with other event handlers from Pux.Html.Events, are used to send actions to the update function. You'll also notice the use of const, which creates a function that ignores its second argument and returns the first. This is because onClick does not take an action, but a function that receives a MouseClick event and returns an action. This is useful if you want to include event data as part of an action. But our counter component doesn't need the MouseClick event, so const is used to ignore it.

Rendering to the DOM

Our counter component is finished and ready to be rendered. This is the job of the start function. It wires together the initial state with the update and view functions to create an application that can be rendered to the DOM. An application is a record that consists of an html and state signal. For now, we just need to feed the app.html signal into the DOM using renderToDOM:

import Prelude (bind)
import Pux (start, fromSimple, renderToDOM)
import Counter (update, view)

main = do
  app <- start
    { initialState: 0
    , update: fromSimple update
    , view: view
    , inputs: [] }

  renderToDOM "#app" app.html

fromSimple is used on the update function because start expects an update function that returns an EffModel, containing state and effects. The counter's update function is simpler, so we use fromSimple to create the type of the more advanced update function with effects. This is covered in a later section, Fetching data.