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
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);
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
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);
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.
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!'
});
Your application will be started at and you'll be able to look at the counter component shown above and play a little with it.