Melody Documentation
  • Why Melody?
  • Quickstart
    • What is Melody?
    • Tutorial
    • Installation
  • Templates
    • Twig
    • Mounting Components
    • Keys and Loops
    • Attributes
    • CSS Classes
    • Inline Styles
    • Refs
    • CSS Modules
  • Melody-Streams
    • Basic concept
    • A Step-by-Step Walkthrough
  • MELODY-STREAMS API
    • createComponent
    • The component function
  • melody-hooks
    • Overview
    • Rules of Hooks
    • Event Handling
    • Separation of Concerns
  • melody-hooks API
    • useState
    • useAtom
    • useEffect
    • useRef
    • useCallback
    • useMemo
    • useReducer
    • usePrevious
  • melody-component
    • Overview
  • melody-hoc
    • Overview
Powered by GitBook
On this page
  • Setting up a demo project
  • Looking at a first component
  • Using melody-streams instead
  • Mounting components
  • Mounting a top-level component

Was this helpful?

  1. Quickstart

Tutorial

A quick introduction to Melody, looking at some code, discussing some basics.

Setting up a demo project

To make it quick and easy to get up and running with Melody, we've prepared a tool to create a new project.

$ npx create-melody-app myapp

npx is a tool that comes bundled with npm since version 5.2.0. It offers a very fast way to temporarily install a command line tool through npm and run it directly.

create-melody-app will respond with a message along these lines:

Create Melody App

Creating a new Melody app named test-app 

Downloading template. This might take a couple of minutes...

✨  Success! Created myapp in /Users/pago/Development/myapp

Inside that directory, you can run these commands:
  yarn
    Installs the project dependencies.
  yarn start
    Starts the development server.
  yarn build
    Bundles the app into static files in the /public folder.

To start, run:
  cd myapp
  yarn
  yarn start

Follow the instructions and switch to the myapp directory.

Looking at a first component

create-melody-app has not been updated to use the new melody-streamsAPI yet and - to be very honest with you - Melody has not been designed to look great in a tutorial. The API shown below scales to incredibly complex cases but when used in a demo context their complexity shines through heavily.

So please don't shy away. A few lines down we will rewrite it using a lighter, more modern, API.

When you open up src/counter/index.js you'll find a basic component, written using the melody-component and melody-hoc API.

// import the component API
import { createComponent, RECEIVE_PROPS } from 'melody-component';
// import a higher order component for dealing with event handling
import { bindEvents } from 'melody-hoc';
// import the tempalte
import template from './index.twig';

// the initial state of the component
const initialState = { count: 0 };

// some actions for increasing or decreasing the counter
const INC = 'INC';
const DEC = 'DEC';

const increaseCounter = () => ({ type: INC });
const decreaseCounter = () => ({ type: DEC });

// a reducer for handling state updates
const stateReducer = (state = initialState, action) => {
  switch(action.type) {
    case RECEIVE_PROPS:
      return {
        ...state,
        ...action.payload
      };
    case INC:
      return {
        ...state,
        count: state.count + 1
      };

    case DEC:
      return {
        ...state,
        count: state.count - 1
      };
  }
  return state;
}

// create a higher order component which binds click event handlers
// to some elements
const withClickHandlers = bindEvents({
  // "incrementButton" is available in the template and can be used
  // as a "ref"
  incrementButton: {
    click(event, {dispatch}) {
      // dispatch an increase counter event to the component
      dispatch(increaseCounter());
    }
  },
  decrementButton: {
    click(event, {dispatch}) {
      dispatch(decreaseCounter());
    }
  }
});

// create the actual component
const component = createComponent(template, stateReducer);

// and enhance it with the higher order component
export default withClickHandlers(component);
<div class="counter">
  <h3>Counter: {{ count }}</h3>
  <button ref="{{ incrementButton }}"> + </button>
  <button ref="{{ decrementButton }}"> - </button>
</div>

One thing you'll likely notice immediately with Melody is that it splits the UI from the state related logic.

Using melody-streams instead

When starting a new project, we advise to consider the melody-streams API instead of melody-component, melody-hoc and melody-redux. melody-streams offers a more direct state handling with less boilerplate. We've learned over time that the Redux approach employed by melody-component delivers very beautiful code when we invest sufficient effort. However, if insufficient care is taken, it can easily become complex and hard to read - the example above is a pretty good proof for that assumption.

To install melody-streams, please execute the following command in your terminal:

$ npm install melody-streams --save

Now start the application by running

$ npm run start

Your application will be started at http://localhost:8080/ and you'll be able to look at the counter component shown above and play a little with it.

Now that we've explored the demo application a little bit - and there's not too much to explore anyway - we can get to work. As mentioned before, the melody-streams API offers a better way to implement less complex components such as our counter component.

To convert our component, we need to adapt the code to look like this:

import { createState, createComponent } from 'melody-streams';
import template from './index.twig';

// define the component function
function Counter({ props }) {
    // createState takes an initial state
    // and returns a stream of values and a mutator function
    const [count, setCount] = createState(props.value.count);
    // return the final state that will be available in the template
    return {
        count,
        increaseCounter: () => setCount(prev => prev + 1),
        decreaseCounter: () => setCount(prev => prev - 1)
    };
}

// combine the component function with the template and get a component back
export default createComponent(Counter, template);
<div class="counter">
  <h3>Counter: {{ count }}</h3>
  <button onclick="{{ increaseCounter }}"> + </button>
  <button onclick="{{ decreaseCounter }}"> - </button>
</div>

When using melody-streams, we declare a function which takes an observable of props of the component, which are provided by its parent, and returns an observable state. The latest value of that state stream is then available within the template.

Also note that we've replaced the usage of refs with more plain onclick event handlers. When using melody-streams, that is often sufficient when you want to use createState.

Mounting components

The concept of embedding a component within another component is called mounting in Melody and is done by using a special Twig statement: mount.

src/home/index.twig
<div class="home">
    <div class="logo">
        <svg xmlns="http://www.w3.org/2000/svg" width="270" height="270" viewBox="0 0 270 270">
            <circle cx="135" cy="135" r="135" fill="#272361"></circle>
            <path d="M146.14 87.94a45 45 0 0 0-53.24 53.23L48.08 186a9 9 0 1 0 3.38 3l42.79-42.79a45 45 0 0 0 9.34 16l-24.05 24.03a9 9 0 1 0 3.19 3.18l24.05-24.05a45 45 0 0 0 16 9.34l-11.43 11.43a9 9 0 1 0 3.26 3.1l13.18-13.18a45 45 0 0 0 18.35-88.12z" fill="#6eceb2"></path>
            <circle cx="152.46" cy="100.5" r="13.5" fill="#fff"></circle>
            <circle cx="150.46" cy="102.5" r="4.5" fill="#272361"></circle>
            <circle cx="173.96" cy="117" r="9" fill="#fff"></circle>
            <circle cx="171.96" cy="117" r="3" fill="#272361"></circle>
            <path d="M140.2 34.39a4.45 4.45 0 0 1 0 6.36 4.62 4.62 0 0 1-6.47 0 4.45 4.45 0 0 1 0-6.36 4.62 4.62 0 0 1 6.47 0zM73.94 62.38a4.45 4.45 0 0 1 0 6.36 4.62 4.62 0 0 1-6.47 0 4.45 4.45 0 0 1 0-6.36 4.62 4.62 0 0 1 6.47 0zM206.45 62.38a4.45 4.45 0 0 1 0 6.36 4.62 4.62 0 0 1-6.47 0 4.45 4.45 0 0 1 0-6.36 4.62 4.62 0 0 1 6.47 0zM234.45 128.64a4.45 4.45 0 0 1 0 6.36 4.62 4.62 0 0 1-6.47 0 4.45 4.45 0 0 1 0-6.36 4.62 4.62 0 0 1 6.47 0zM200 201.62a4.45 4.45 0 0 1 0-6.36 4.62 4.62 0 0 1 6.47 0 4.45 4.45 0 0 1 0 6.36 4.62 4.62 0 0 1-6.47 0z" fill="#6eceb2"></path>
        </svg>
    </div>
    <h1 class="title">
        {{ message }}
    </h1>
    {# This is the mount tag #}
    {% mount '../counter' with { count: 5 } %}
</div>

The mount statement is overloaded to be useful in a few more cases, but the usage above is likely how most of your usages will look. Later chapters will explore additional usages.

Mounting a top-level component

Now we're only missing a look at how our root component is mounted into the DOM. A "root" or "top-level" component is what we call a Melody component that has no parent component but is injected into the DOM directly.

src/index.js
import { render } from 'melody-streams';
import home from './home';

const documentRoot = document.getElementById('root');
render(documentRoot, home, {
    message: 'Welcome to Melody!'
});
PreviousWhat is Melody?NextInstallation

Last updated 5 years ago

Was this helpful?