Exercise to build a version of redux for fun and as a learning opportunity. Affectionately also known as faux-dux.
Many thanks to Chris for the inspiration and for pair-programming with me.
An overview of this exercise was given as a 20 minute presentation.
The goal was to have our own implementation of Redux that would update when an user types their name in a text input box. An outline of the presentation follows.
πΆ The first step was to use Create React App for the ease of set-up.
πΆ Then, immediately removed some parts of the Create React App, including the contents of the App component, CSS, and the React svg logo.
πΆ Created the Greeting component with only some text and an input box.
πΆ Created the Provider component, and hooked up Greeting with Provider using React Context v15 only.
-
Notice that the Provider component wraps the App component in App.js.
-
As the parent component, Provider needs to define
getChildContext()
function (which returns an object that is the data we want) and the static propchildContextTypes
(that defines the type of the data we will be passing). -
As the grandchild component, Greeting needs the static prop
contextTypes
to define the data type. Then, the data is available asthis.context.<data>
. Note that this code was eventually removed. -
At this point, if we hardcoded the name 'Kirk' in the Provider's store.name, Greeting now shows the name Kirk in the UI. π
πΆ Create connect.js. As a Higher Order Component, it takes in a component, and returns a new component.
- No lifecycle methods are in connect.js at this point. It takes in the Greeting component, wraps it, and returns returns the wrapped component.
πΆ Now let's add the appReducer.
-
At first, appReducer only takes in a state and an action and returns the state. It literally does nothing but returns the state it was given, and sets the default state to
{name: 'Spock'}
. -
Later, the appReducer is updated to handle the
UPDATE_NAME
action, in addition to the default case.
πΆ Add mapStateToProps and mapDispatchToProps.
-
Update the return statement in connect.js.
-
The return statement for connect.js is updated. The mapStateToProps is spread and calls getState(). The code looks like this:
{...mapStateToProps(this.context.store.getState())}
-
Also,
mapDispatchToProps
now calls the store's dispatch action, like so:{...mapDispatchToProps(this.context.store.dispatch)}
.
-
-
In addition, in connect.js
mapDispatchToProps
defaults to a function that returns an empty object. -
The Greeting component is updated to include
mapStateToProps()
andmapDispatchToProps()
.-
Now
mapStateToProps()
returns an object with the data in the store that we care about (in this case, the name) and adds the name to the Greeting component's props. -
By adding
mapDispatchToProps()
, the store'sdispatch()
function is available to send actions to update the store.
-
πΆ Implement createStore()
in createStore.js
-
Create the class Store in createStore.js
-
Add the
__FAUX_REDUX_INIT__
action type. This will be used by dispatch() in the constructor when the Store is initialized. -
Add
dispatch()
. This function takes an action and then iterates through the reducers in order to create anewState
object. -
Add
getState()
, which returnsthis.state
. -
createStore()
now takes in a reducersObject and returns a new Store. For example,createStore()
is called in App.js with the appReducer object.
πΆ At this point, we have a store and a read-only version of faux-dux. π π
πΆ Add a subscriber.
-
Update connect.js, which is returns a wrapped component.
-
Add
componentDidMount()
, which implements an observer pattern. -
Add
componentWillUnmount()
, which calls this.unsubscribe(). -
Add the new function
handleChange()
, which callsforceUpdate()
.
-
-
Implement the subscribers in createStore.js:
-
Add an array of subscribers to the Store class. This
subscribers
is an array ofhandleChange
functions. -
Add the
subscribe()
function. This takes in thehandleChange()
function. It updates the list of subscribers. It returns an unsubscribe function, which allows the subscriber to unsubscribe when the connect HOC is unmounted. -
Add the
notifySubscribers()
function, which iterates through the array of subscribers. Remember thatsubscribers
is an array ofhandleChange()
functions. So, while iterating,handleChange()
is called on each connected HOC that is subscribed. -
Add
this.notifySubscribers()
to the end of thedispatch()
function. This is so that when the state is updated the subscribers find out about the change.
-
πΆ Now the store updates when the user inputs text! π π π
πΆ In review:
-
The Provider allows the store to be available via context.
-
Connect wraps the Greeting component with an HOC. Connect also gives mapStateToProps and mapDispatchToProps to the Greeting component.
-
At first, the store is initialized with the action
__FAUX_REDUX_INIT__
. Since there is no state yet, the default name is Spock. -
The user types their name into the input box.
handleInputChange()
is called with the new name, and it dispatchesonNameChange()
. -
Then,
dispatch()
calls the appReducer. The reducer takes the new name and returns the new state. -
dispatch()
then sets the Store's state as the new state and callsnotifySubscribers()
. -
All the subscribers are updated with the new state. The component is forced to update, thus rerendering the UI and showing the updated name.