From b4a238f54f6f4dbdab7027e2e0f5b60f3113b8c2 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sat, 6 Jan 2018 21:04:11 -0500 Subject: [PATCH 01/20] REFACTOR STEP 1: Add React, ReactDOM, babel-standalone UMD scripts for prototyping with React --- index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.html b/index.html index c94c32c..55e4d3f 100644 --- a/index.html +++ b/index.html @@ -20,6 +20,9 @@

To Do List

+ + + From 5b19952674f393003bd5bdb4e8d525bdcbb8d4d2 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sat, 6 Jan 2018 21:43:17 -0500 Subject: [PATCH 02/20] REFACTOR STEP 2: Create our new Add Todo Form component with JSX similar to original mark up --- components/AddTodoInput.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 components/AddTodoInput.js diff --git a/components/AddTodoInput.js b/components/AddTodoInput.js new file mode 100644 index 0000000..6afbcb8 --- /dev/null +++ b/components/AddTodoInput.js @@ -0,0 +1,10 @@ +class AddTodoInput extends React.Component { + render() { + return ( +
+ + +
+ ); + } +} From 7baa831108e3558e881b79de95ba0471db5ae7ac Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sat, 6 Jan 2018 21:52:06 -0500 Subject: [PATCH 03/20] REFACTOR STEP 3: Render new component into the DOM --- components/AddTodoInput.js | 2 ++ index.html | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/AddTodoInput.js b/components/AddTodoInput.js index 6afbcb8..ac07205 100644 --- a/components/AddTodoInput.js +++ b/components/AddTodoInput.js @@ -8,3 +8,5 @@ class AddTodoInput extends React.Component { ); } } + +ReactDOM.render(, document.querySelector('[data-react-component="AddTodoInput"]')); diff --git a/index.html b/index.html index 55e4d3f..3b97db2 100644 --- a/index.html +++ b/index.html @@ -11,10 +11,7 @@

To Do List

-
- - -
+ @@ -23,6 +20,7 @@

To Do List

+ From 7d2ab6ef23546d23f59bb9bf2dc2bac5483ecf9c Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sat, 6 Jan 2018 22:27:47 -0500 Subject: [PATCH 04/20] REFACTOR STEP 4.1: Add state to component --- components/AddTodoInput.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/components/AddTodoInput.js b/components/AddTodoInput.js index ac07205..9d3c83b 100644 --- a/components/AddTodoInput.js +++ b/components/AddTodoInput.js @@ -1,8 +1,17 @@ class AddTodoInput extends React.Component { + constructor(props) { + super(props); + this.state = { + text: '' + }; + } + render() { return (
- +
); From 4d0845cb7110998eadbf85aee76e55a65deaccb1 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sat, 6 Jan 2018 22:31:04 -0500 Subject: [PATCH 05/20] REFACTOR STEP 4.2: update state on user input to the text field --- components/AddTodoInput.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/AddTodoInput.js b/components/AddTodoInput.js index 9d3c83b..865e451 100644 --- a/components/AddTodoInput.js +++ b/components/AddTodoInput.js @@ -6,11 +6,16 @@ class AddTodoInput extends React.Component { }; } + handleInput(e) { + this.setState({ text: e.target.value }); + } + render() { return (
From 0334cf52004a0abf798af1d731d21ec12b368bf5 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sat, 6 Jan 2018 22:35:23 -0500 Subject: [PATCH 06/20] REFACTOR STEP 4.3: handle the input submit --- components/AddTodoInput.js | 8 +++++++- index.html | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/components/AddTodoInput.js b/components/AddTodoInput.js index 865e451..83357a0 100644 --- a/components/AddTodoInput.js +++ b/components/AddTodoInput.js @@ -10,9 +10,15 @@ class AddTodoInput extends React.Component { this.setState({ text: e.target.value }); } + handleSubmit(e) { + e.preventDefault(); + addTodo(this.state.text); + this.setState({ text: '' }); + } + render() { return ( -
+ To Do List - + From 008038d441bd2d06ba650e98b95313db80f7b77e Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sat, 6 Jan 2018 22:38:19 -0500 Subject: [PATCH 07/20] REFACTOR STEP 4.4: Remove the jQuery event listener --- jquery/todo.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/jquery/todo.js b/jquery/todo.js index d5a0322..14e1ebe 100644 --- a/jquery/todo.js +++ b/jquery/todo.js @@ -50,14 +50,6 @@ function maybeHideDeleteAll() { // Attach the DOM events once the page has loaded $(document).ready(function() { - // When the form input is submitted, add the todo item - $("#addForm").on('submit', function(e) { - e.preventDefault(); - var input = $("input#todoInput"); - addTodo(input.val()); - input.val(""); - }); - // When the form input is submitted, add the todo item $("#clearCompleted").on('click', function(e) { e.preventDefault(e); From 5e07826e270494aa0575d32cb5aad9717c699268 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 15:55:40 -0500 Subject: [PATCH 08/20] REFACTOR STEP 5: Add new Remove Completed Button component --- components/RemoveCompletedButton.js | 25 +++++++++++++++++++++++++ index.html | 5 ++--- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 components/RemoveCompletedButton.js diff --git a/components/RemoveCompletedButton.js b/components/RemoveCompletedButton.js new file mode 100644 index 0000000..136f305 --- /dev/null +++ b/components/RemoveCompletedButton.js @@ -0,0 +1,25 @@ +class RemoveCompletedButton extends React.Component { + handleClick() { + removeCheckedItems(); + } + + render() { + if(this.props.show) { + return ( + + ); + } + return null; + } +} + +RemoveCompletedButton.defaultProps = { + show: false +}; + +ReactDOM.render( + , + document.querySelector('[data-react-component="RemoveCompletedButton"]') +); diff --git a/index.html b/index.html index d0e51c2..920c01d 100644 --- a/index.html +++ b/index.html @@ -12,9 +12,7 @@

To Do List

- + @@ -22,5 +20,6 @@

To Do List

+ From 12c1fbb69d878c375f16fbb9e494f179ae743718 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 15:56:54 -0500 Subject: [PATCH 09/20] REFACTOR STEP 5.1: Update the jQuery to rerender RemoveCompletedButton on dependent interactions --- jquery/todo.js | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/jquery/todo.js b/jquery/todo.js index 14e1ebe..abb1fa2 100644 --- a/jquery/todo.js +++ b/jquery/todo.js @@ -39,22 +39,17 @@ function toggleComplete(todo) { } } +function removeCheckedItems() { + $('#todos input:checked').closest('li').remove(); + maybeHideDeleteAll(); + +} + function maybeHideDeleteAll() { var completedItems = $('#todos input:checked').length; - if(completedItems > 0) { - $('#clearCompleted').show(); - } else { - $('#clearCompleted').hide(); - } + ReactDOM.render( + React.createElement(RemoveCompletedButton, { show: completedItems > 0 }), + document.querySelector('[data-react-component="RemoveCompletedButton"]') + ); } -// Attach the DOM events once the page has loaded -$(document).ready(function() { - // When the form input is submitted, add the todo item - $("#clearCompleted").on('click', function(e) { - e.preventDefault(e); - $('#todos input:checked').closest('li').remove(); - $(this).hide(); - }); -}); - From 8a6b208eaa6dbaabe5477126b216f36df965bea0 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:34:57 -0500 Subject: [PATCH 10/20] Add UMD scripts for Redux and ReactRedux --- index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.html b/index.html index 920c01d..ec0ed61 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,8 @@

To Do List

+ + From 32621461b6f4cf63ab1f205005ac9c731244879b Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:35:31 -0500 Subject: [PATCH 11/20] Define the initialState for the redux store --- store/index.js | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 store/index.js diff --git a/store/index.js b/store/index.js new file mode 100644 index 0000000..f8d24ad --- /dev/null +++ b/store/index.js @@ -0,0 +1,4 @@ +// The initial state of our store +const initialState = { + hasCompletedItems: false +}; From 4ce6dd8bf61335e9a81434bc20639b00e8577d14 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:36:28 -0500 Subject: [PATCH 12/20] Define the action that describes what happened, wrap it in an action creator for easy reuse --- store/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/store/index.js b/store/index.js index f8d24ad..bf05873 100644 --- a/store/index.js +++ b/store/index.js @@ -2,3 +2,10 @@ const initialState = { hasCompletedItems: false }; + +function setHasCompletedItems(hasCompletedItems = false) { + return { + type: 'SET_HAS_COMPLETED_ITEMS', + hasCompletedItems: true / false + }; +} From 1b8a4c068ea79ddba822a060c7a925b5daccf0ca Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:37:06 -0500 Subject: [PATCH 13/20] Define the pure reducer function for updating the state --- store/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/store/index.js b/store/index.js index bf05873..c122564 100644 --- a/store/index.js +++ b/store/index.js @@ -9,3 +9,10 @@ function setHasCompletedItems(hasCompletedItems = false) { hasCompletedItems: true / false }; } + +function reducer(state = initialState, action) { + if (action.type = 'SET_HAS_COMPLETED_ITEMS') { + return { hasCompletedItems: action.hasCompletedItems }; + } + return state; +} From 97b4de7c55674096d2d2300dd8a41b75cfa60eaa Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:37:37 -0500 Subject: [PATCH 14/20] Use the Redux library to create the store with the reducer --- store/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/store/index.js b/store/index.js index c122564..12bf88a 100644 --- a/store/index.js +++ b/store/index.js @@ -16,3 +16,5 @@ function reducer(state = initialState, action) { } return state; } + +var store = Redux.createStore(reducer); From 3c565f30e99d9e694925395f5b32dfc7abe2e26e Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:39:59 -0500 Subject: [PATCH 15/20] Use the ReactRedux library to connect the RemoveCompletedButton component to the Redux store --- components/RemoveCompletedButton.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/RemoveCompletedButton.js b/components/RemoveCompletedButton.js index 136f305..c43b686 100644 --- a/components/RemoveCompletedButton.js +++ b/components/RemoveCompletedButton.js @@ -1,4 +1,4 @@ -class RemoveCompletedButton extends React.Component { +class UnconnectedRemoveCompletedButton extends React.Component { handleClick() { removeCheckedItems(); } @@ -15,9 +15,12 @@ class RemoveCompletedButton extends React.Component { } } -RemoveCompletedButton.defaultProps = { - show: false -}; +function mapStateToProps(state) { + return { + show: state.hasCompletedItems + }; +} +var RemoveCompletedButton = ReactRedux.connect(mapStateToProps)(UnconnectedRemoveCompletedButton); ReactDOM.render( , From 34eb22731049257da32ebfea8ed7420820c647b2 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:46:44 -0500 Subject: [PATCH 16/20] fix action creator --- store/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/index.js b/store/index.js index 12bf88a..d79c9d8 100644 --- a/store/index.js +++ b/store/index.js @@ -6,7 +6,7 @@ const initialState = { function setHasCompletedItems(hasCompletedItems = false) { return { type: 'SET_HAS_COMPLETED_ITEMS', - hasCompletedItems: true / false + hasCompletedItems: hasCompletedItems }; } From 21455975566399dc73ad1769e20cb3af2609fb75 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:47:52 -0500 Subject: [PATCH 17/20] Include the redux store script --- index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/index.html b/index.html index ec0ed61..f7cadff 100644 --- a/index.html +++ b/index.html @@ -20,6 +20,7 @@

To Do List

+ From a0f6079a812b72f5c792dc3d1689f76b5fcb8b47 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:48:17 -0500 Subject: [PATCH 18/20] Pass the store to the RemoveCompletedButton component for connect --- components/RemoveCompletedButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/RemoveCompletedButton.js b/components/RemoveCompletedButton.js index c43b686..3499eab 100644 --- a/components/RemoveCompletedButton.js +++ b/components/RemoveCompletedButton.js @@ -23,6 +23,6 @@ function mapStateToProps(state) { var RemoveCompletedButton = ReactRedux.connect(mapStateToProps)(UnconnectedRemoveCompletedButton); ReactDOM.render( - , + , document.querySelector('[data-react-component="RemoveCompletedButton"]') ); From 528d660085e995eeed28aceed4b6b18a36792fc1 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:48:57 -0500 Subject: [PATCH 19/20] Change the jQuery to dispatch the setHasCompletedItems action instead of using ReactDOM.render --- jquery/todo.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jquery/todo.js b/jquery/todo.js index abb1fa2..0898686 100644 --- a/jquery/todo.js +++ b/jquery/todo.js @@ -47,9 +47,6 @@ function removeCheckedItems() { function maybeHideDeleteAll() { var completedItems = $('#todos input:checked').length; - ReactDOM.render( - React.createElement(RemoveCompletedButton, { show: completedItems > 0 }), - document.querySelector('[data-react-component="RemoveCompletedButton"]') - ); + store.dispatch(setHasCompletedItems(completedItems > 0)); } From 9adab12bdf0d22e7f3d2f434f6575627a32de81f Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Wed, 10 Jan 2018 10:07:11 -0500 Subject: [PATCH 20/20] Update README --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 457dea1..45a75fb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,45 @@ # jquery-to-react -Example jquery todo list refactor to React +This project is a simple To Do List example of how to go about incrementally refactoring your JavaScript application to the [React](https://reactjs.org/) framework and how you can use [Redux](https://redux.js.org/) to share state between React and your original JavaScript. + +The default branch, 'withRedux' contains all the completed refactor examples by default. The refactoring steps are available for you via tags, or you may choose to view the project diffs by viewing the open pull requests. + +This example was originally presented as part of a talk on Refactoring a Legacy Application with React. + +## Branches + - **master** + + This branch is the starting point - a working To Do list application using only plain JavaScript and some jQuery + + - **refactor** + + This branch includes examples of refactoring the 'add to do item' input and 'Remove Completed' items button to React (without Redux) + + - **withRedux** [default] + + This branch includes all the same content as the refactor branch and updates the 'Remove Completed' items button to use Redux state + + +## Refactoring to React + +The purpose of this example is to show how you can begin to add React components to your front-end. The steps used in the refactor: +1. Start trying out React right away by adding UMD scripts to your HTML file, you may also refer to the example HTML template provide [in the React Docs](https://reactjs.org/docs/try-react.html) +2. Create the new AddTodoInput component by borrowing the markup that was in the DOM as our JSX +3. Render the new component into the DOM using the ReactDOM library +4. Handle the input's onChange and onSubmit using React state in steps: + - Add the initial state to the component + - Handle the onChange of the input, see [React's guide on handling forms](https://reactjs.org/docs/forms.html) + - Handle the onSubmit using React state + - Remove the old jQuery onsubmit event listener +5. Create a new 'Remove Completed' button with a prop to toggle visibility + - Use ReactDOM's render to update the button's prop when jQuery needs to toggle the visibility of the button + +## Using Redux to Manage Shared State + +The purpose of the Redux example is to show an alternative way to handle state in React that needs to be updated by an external library. In the previous section, we used ReactDOM to update the 'show' prop in the button. This shows how you can manage the button's show prop using Redux for application level state. +1. Add the Redux and [React-Redux](https://redux.js.org/docs/basics/UsageWithReact.html) UMD scripts to the HTML page +2. Define the initial state we want to track +3. Define the action object that will describe what happens when the visibility should be toggled +4. Define a pure reducer function that returns the new state when it receives the action defined in step 2 +5. Use the Redux library to create and manage the store +6. Connect the RemoveCompletedButton component to the redux store using the react-redux connect component +7. Dispatch the action to toggle the visibility in our jQuery code