From 8a6b208eaa6dbaabe5477126b216f36df965bea0 Mon Sep 17 00:00:00 2001 From: Ali Orlando Date: Sun, 7 Jan 2018 16:34:57 -0500 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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 09/11] 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 10/11] 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 11/11] 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