See a Styled TodoMVC Application

Story: As a user, I want my application to live in a styled environment.

TodoApp component

Before we start styling our top level component, let's wrap it up in StyleRoot and Style Radium Components.

StyleRoot Component:

Usually wrapped around your top-level App component. StyleRoot wraps its children in a plain div followed by the root style sheet. Radium plugins, like keyframes and media queries, use this style sheet to inject CSS at runtime. Because the style sheet appears after your rendered elements, it is populated correctly during a server render.

StyleRoot transfers all of its props to the rendered div, and is itself wrapped in Radium, so you can pass it inline styles or radiumConfig.

-- StyleRoot Component

Style Component:

The Style component renders an HTML style tag containing a set of CSS rules. Using it, you can define an optional scopeSelector that all selectors in the resulting style element will include. ... (rules prop of the Style component) An object of CSS rules to render. Each key of the rules object is a CSS selector and the value is an object of styles. If rules is empty, the component will render nothing.

-- Style Component

These components will allow us to pass styling to the tags that live outside our components (i.e. html, body...). So, let's make the needed changes:

diff --git a/src/todomvc/components/TodoApp.js b/src/todomvc/components/TodoApp.js
index 1507dbb..ae87fcc 100644
--- a/src/todomvc/components/TodoApp.js
+++ b/src/todomvc/components/TodoApp.js
@@ -4,11 +4,13 @@ import React, {Component} from 'react'
 import PropTypes from 'prop-types'
 import {bindActionCreators} from 'redux'
 import {connect} from 'react-redux'
+import Radium, {StyleRoot, Style} from 'radium'

 import Header from '../components/Header'
 import MainSection from '../components/MainSection'
 import * as actions from '../actions'

+@Radium
 class TodoApp extends Component {
   static propTypes = {
     todos: PropTypes.object.isRequired,
@@ -19,10 +21,13 @@ class TodoApp extends Component {
     const {todos, actions} = this.props;

     return (
-      <div>
-        <Header actions={actions}/>
-        <MainSection todos={todos} actions={actions}/>
-      </div>
+      <StyleRoot>
+        <Style/>
+        <div>
+          <Header actions={actions}/>
+          <MainSection todos={todos} actions={actions}/>
+        </div>
+      </StyleRoot>
     )
   }
 }
(END)

Run the tests and watch some of them fail.

  3 failing

  1) TodoApp component Should render correctly Should be a TodoApp:

      AssertionError: expected 'StyleRoot' to equal 'div'
      + expected - actual

      -StyleRoot
      +div

      at Context.<anonymous> (tests/todomvc/components/TodoApp.spec.js:35:35)

  2) TodoApp component Should render correctly Should have a header:

      AssertionError: expected { Object (root, unrendered, ...) } to have a length of 1 but got 0
      + expected - actual

      -0
      +1

      at Context.<anonymous> (tests/todomvc/components/TodoApp.spec.js:41:52)

  3) TodoApp component Should render correctly Should have a main section:

      AssertionError: expected { Object (root, unrendered, ...) } to have a length of 1 but got 0
      + expected - actual

      -0
      +1

      at Context.<anonymous> (tests/todomvc/components/TodoApp.spec.js:47:57)

It is clear that wrapping our TodoApp in Radium components has broken our tests. We are going to update the code to make the tests pass.

diff --git a/tests/todomvc/components/TodoApp.spec.js b/tests/todomvc/components/TodoApp.spec.js
index 178e9e9..e950657 100644
--- a/tests/todomvc/components/TodoApp.spec.js
+++ b/tests/todomvc/components/TodoApp.spec.js
@@ -31,20 +31,24 @@ describe('TodoApp component', () => {
   describe('Should render correctly', () => {
     it('Should be a TodoApp', () => {
       const {component} = setup();
+      expect(component.name()).to.equal('StyleRoot');

-      expect(component.name()).to.equal('div')
+      const div = component.find('div');
+      expect(div.length).to.equal(1)
     });

     it('Should have a header', () => {
       const {component} = setup();
+      const div = component.find('div');

-      expect(component.children('Header')).to.have.length(1)
+      expect(div.children('Header')).to.have.length(1)
     });

     it('Should have a main section', () => {
       const {component} = setup();
+      const div = component.find('div');

-      expect(component.children('MainSection')).to.have.length(1)
+      expect(div.children('MainSection')).to.have.length(1)
     })
   })
 });
(END)

The tests have not essentially changed. We are just looking for our components a little bit deeper. Run the tests and watch them pass.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5
    Should behave correctly
      ✓ Should not show 'delete completed' button when there are no completed todos
      ✓ Should show 'delete completed' button when there is at least one completed todo
      ✓ Should call deleteCompletedTodos() when the delete completed button is clicked

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew
      ✓ Should not call onSave() on blur if isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  51 passing (159ms)

The next step is to apply the styling to the page our application will live in. We will use rules prop of Style component.

diff --git a/src/todomvc/components/TodoApp.js b/src/todomvc/components/TodoApp.js
index 99dc0cb..e8c7842 100644
--- a/src/todomvc/components/TodoApp.js
+++ b/src/todomvc/components/TodoApp.js
@@ -17,12 +17,80 @@ class TodoApp extends Component {
     actions: PropTypes.object.isRequired
   };

+  styles = {
+    root: {
+      html: {
+        margin: 0,
+        padding: 0
+      },
+
+      body: {
+        fontFamily: '\'Helvetica Neue\', Helvetica, Arial, sans-serif',
+        fontSize: '14px',
+        fontWeight: 300,
+        lineHeight: '1.4em',
+        background: '#f5f5f5',
+        color: '#4d4d4d',
+        minWidth: 230,
+        maxWidth: 666,
+        margin: '0 auto',
+        padding: 0,
+        WebkitFontSmoothing: 'antialiased',
+        MozOsxFontSmoothing: 'grayscale'
+      },
+
+      ':focus': {
+        outline: 0
+      },
+
+      '::-webkit-input-placeholder': {
+
+        fontStyle: 'italic',
+        fontWeight: '300',
+        color: '#e6e6e6'
+      },
+
+      ':-moz-placeholder': {
+        fontStyle: 'italic',
+        fontWeight: '300',
+        color: '#e6e6e6'
+      },
+
+      '::-moz-placeholder': {
+        fontStyle: 'italic',
+        fontWeight: '300',
+        color: '#e6e6e6'
+      },
+
+      ':-ms-input-placeholder': {
+        fontStyle: 'italic',
+        fontWeight: '300',
+        color: '#e6e6e6'
+      },
+
+      mediaQueries: {
+        'screen and (-webkit-min-device-pixel-ratio:0)': {
+          'li .toggle': {
+            background: 'none',
+            height: 40
+          }
+        },
+
+        '(max-width: 430px)': {
+          '.footer': {
+            height: 50,
+          }
+        }
+      }
+    }
+  };
+
   render() {
     const {todos, actions} = this.props;

     return (
       <StyleRoot>
-        <Style/>
+        <Style rules={this.styles.root}/>
         <div>
           <Header actions={actions}/>
           <MainSection todos={todos} actions={actions}/>
(END)

All of our tests are still passing.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5
    Should behave correctly
      ✓ Should not show 'delete completed' button when there are no completed todos
      ✓ Should show 'delete completed' button when there is at least one completed todo
      ✓ Should call deleteCompletedTodos() when the delete completed button is clicked

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew
      ✓ Should not call onSave() on blur if isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  51 passing (154ms)

Commit the changes:

$ git add .
$ git commit -m 'added styling for the page'

Now we are ready to start styling our components

Last updated

Was this helpful?