Creating Jumbles (part 3: form n’ storage)
If you are coming here directly and have not yet viewed part 1 and part 2 you should. In part 1 I go through the structure of the app and why the structure is the way it is. Also I talk about the app it self, what it should do and how it should look. And in part 2 I add some views, routes and something I call platform that allows the application to connect with a server. However, If you are here on purpose; lets get to it…
What I want to do is allow our users to create a new Jumble and to do this we need a view that will allow them to enter the information needed. So, lets start with the easy parts and create the New view.
Like before we add a folder under our jumbles views called new and in there we add our index.js and our New.js for our form component. Our tree should then look like this:
bjorgvin@computer jumbles $ tree -I 'node_modules'
Now we crate our New component
And we export it from the index.js
Now that we have the view for our form we need to wire it up with the rest of the application. This is simple since we simple add a route to the form in our src/view/jumbles/routes.js like so:
If we run our application now we can navigate to /jumbles/new and our new form view will render. However, our users have no way of knowing where to go in order to create a new Jumble so we do this in two ways. One is to create a link to our new form in our src/routes.js file so it will be visible to our users at all times
And, of course, if our users have no jumbles we need to add a reference to the form in our overview at the time we tell them no jumbles are available. We do this simply be adding a Link to the form in our Overview component
Now that our users are told there are no jumbles yet they are also presented with a way to create one. To see this work I comment out the dummy jumbles in src/platform/jumbles/sagas.js
Now we have our form component all wired up its time to create the form itself.
Since we are using redux it makes sense going for redux-form when creating forms in our application. We add the package using yarn
yarn add redux-form
In order to allow our New component to hold a form we need to wrap it with a Higher Order Component, HOC for short, provided by redux-form and this is done in our src/view/jumbles/index.js file where we simply name our form as it should be named in the application state
Now lets build our form by adding it to the New component
We can see how we are throwing an exception when we submit with an empty jumble name. This is because we want our users to dictate the jumble name but the jumble it self needs to be generated. So, the form is not really like I would like to have it, is it?
We don’t want our user to enter a long string for the jumble. We need to provide a way for our users to generate the jumble. This we do with a custom field component. Here is our New component with a custom field component
We have two custom field components used to handle the name of the jumble and the actual jumble.
Our renderField component will render a field showing both a label, input and validation errors that might come up during submit.
Our jumbleField component renders a label showing the generated jumble and a button that regenerates a jumble if the user is not happy with the one already provided. The button has an event handler onGenerate that is bound to this in our constructor to make it available.
However, when we run the application there is no jumble generated and nothing happens when we press the regenerate button. When we look at the onGenerate method we are using an action change from redux-form that we expect to be in our props. We have not yet provided this action so we need to go over to our src/view/jumbles/new/index.js and connect our New component to the application state and add this action
And now when we run our application we get a generated jumble and we can press the regenerate button to get a new jumble if we like.
When we render our jumble it is simply a 100 character string but we would like to customise how the string is presented. If you remember the image in the beginning it was supposed to be a colourful grid. We are not getting into the details of how to apply custom styling yet but we would like to create the component now and render it in the form instead of the raw string.
We also would like to render a jumble the same way in our Detail view; so to share a component like this we create it in a place where its logical for all views to get it from. Lets put it in the root of the jumble view folder so our structure should now look like this
bjorgvin@computer jumbles $ tree -I 'node_modules'
│ ├── index.js
│ └── New.js
The Jumble component will be a pure component that is not connected to the application state so it will take the information it needs through props. To begin with it will simply take three props to render out name and jumble. So, our first version of src/view/jumbles/Jumble.js will be simple
We should use that in our form so we simply use it in the jumbleField in our New form
We should also use that in our Detail view to have some consistency in our view of a jumble
Now we are prepared to render our Jumble any way we like and it will be reflected in both our New form and our Detail view. Now all we need is to save the jumble
When our user presses save we should store the jumble and show the jumble using our Detail component. We do this by first creating an action that we can dispatch to save the jumble. So we go to our platform/jumbles/actions.js and add two action creators there
Now we can make the saveJumble action available to our New component through the connect method in src/view/jumbles/new/index.js file
And finally we can access the saveJumble method in our onSubmit method inside the New component. Now this is a little tricky since in our onSubmit method we are not able to get the saveJumble from the props. So we get the saveJumble method inside our render method and provide an anonymous function to the handleSubmit method in our form like this
Nothing happens when we click the save button since we are not listening for the saveJumble action anywhere. We can change this by adding a watcher to our src/platform/jumbles/sagas.js
Now we press the save button and simply get a log in our console. We can add a simple constant, called jumbleList, holding a list of jumbles acting as our in memory storage for now and we can also yield the saveJumbleResolved when we are done storing the new jumble and our src/platform/jumbles/sagas.js change a bit
Now that we click on the save button the new jumble will be added to the list and if we navigate to /jumbles we will see a list of the jumbles we have created.
Now that we are saving the jumble created we need to go into some detail with user experience and rendering of our views.
This is all good and well but when we click the save button we would like to be navigated to the Detail and to do that we need to do some minor changes to the structure of the application. First of, we would like to push a route to the history so the application will navigate to the specific route, namely ‘/jumbles/:id’ where id is the id for the jumble that was just created.
But we don’t have access to the history in our sagas so we need to change that by adding the history we have in our src/index.js to a file that can be used in more places. So, we create a src/history.js file holding the history for all to use
And we use that in our src/index.js instead of the direct history object like we did before
Now we have a shared history so we can import that in our saga to navigate to the newly create jumble when it has been created. This is simple to do in our src/platform/jumbles/sagas.js using the call function from redux-saga/effects
Like you can see we are adding a yield to the call function and pushing a new route to the history. This will navigate to the newly created jumble.
I am not a fan of letting the platform do navigation since it is shared between views and maybe we would like to do something else in another view using the platform
So, since I’m not a fan of this approach I would rather do this differently and we first remove our history import and the call to push from our sagas.js.
Then we make the new jumble id accessible through the reducers select methods by adding a simple selector and making sure that when saving we have no newId but when saving has been resolved we have the newId set in our state
Well, how do we navigate in our New component. This is done using a higher-order component (HOC) from react-router called withRouter. We use this in our src/view/jumbles/new/index.js to wrap our New component so it has access to history through its’ props. Also, we make sure we have access to newId through the selector created in the reducer before
Now we can make minor changes to our render method so we can navigate to the newly created jumble just like the saga did before.
What if it takes some time to save the jumble? We should show some indication that the jumble is being saved and we do so by adding some state for the New component to use when . So we add some indication to the reducer in our platform
And we add access to the selector in our src/view/jumbles/new/index.js so we can see if a jumble is being saved in our New component.
And now we can access the saving indicator through the props in our New component
To test this we can add delay in our saga when we are saving the jumble
Now when we click the save button we will be shown saving… for 5 seconds before we navigate to the newly created jumble.
To make our jumble list persistent we can, for now, simply store the jumbles in localStorage. This will not give you persistent between devices or browsers but it is good enough for this project. For extra points you can add a server that will persist your jumbles if you like :)
So, lets open up our trusted src/platform/jumbles/sagas.js and read our jumbleList from localStorage when we load and make sure we save the jumbleList when we add a jumble
Now we have an application that will allow you to generate, view and store jumbles.
We still have some work to do in our Details view since there is no way yet to find a password in a jumble. If you remember how jumbles work then we still need to allow the user to enter a pin that will show the password in a jumble and a button to copy the password. This we do in the Detail view.
First we need a module that allows us to copy text to the clipboard. We will go with clipboard-js that provides a simple method called copy that will take a string to be copied to the clipboard.
yarn add clipboard-js
Now that we have the module as part of our application we can use it in our Jumble component since that is the component that will reveal the password
We added a method called copy that takes in password to be copied to the clipboard. The method we bind to this in our constructor so it can be used as an event handler in our markup. Finally we add an input of type button with an onClick event that is handled by our copy method.
Now when we view a Jumble we have a copy button that will copy the whole jumble to the clipboard. However, we only need to copy the password and to reveal the password the user must enter the 4 digit pin.
We can provide the Jumble component with a 4 digit pin or a first, second, third and fourth digit. This will give the Jumble component a possibility to render the visualisation of the password in any way it sees fit. Lets take in the digits, through props, one by one to provide Jumble component a better control and lets not show the copy button unless we have a password to copy
As you can see it is fairly simple how we display the password. We take our digits and when we have all of them we split our jumble into 3 parts where the middle part is the password. We also do a little trick here if you enter a pin like 3412 the 34 is larger number then 12 so we take the characters from 12 to 34 in reverse order but if you enter 1234 we provide the characters from 12 to 34 like before but in the correct order.
But we have still got some work to do in our Details view since it needs to provide the digits. Lets do that now
We can’t forget that the user might want to remove a jumble. To make that possible we go to the platform and add actions
Then we go to the sagas to handle the actions
And finally we add information to the state in the reducer and selectors for views to access information on jumbles being deleted
And now we are ready to add a way to delete jumbles to the views. Lets start with the Overview so our users can delete jumbles in the list of jumbles. First we add the action to the src/view/jumbles/overview/index.js
Now that the action is available through props in the Overview we are able to create a button for the user to click in order to delete a jumble. This we do in our src/view/jumbles/overview/Overview.js
Now, when the user clicks delete for a specific jumble, the jumbles id is added to a list of jumbles being deleted. When this happens we render only text showing the name and disabled delete button so it can not be clicked again. However, other delete buttons are enabled so the user can delete multiple jumbles at once.
Now the application is working like we would expect
Well, our application is almost ready but we still have our Home view that has nothing to do with jumbles and now it’s time to get rid of that. We already have a landing page in our Overview. To remove that we need to change a few files.
First remove the Link to /home in our src/routes.js file and change the global redirect to /jumbles instead of /home and of course remove the reference to the src/view/home/routes.js as well. This will make our root routes file look like this
We can not forget our platform since we have both sagas and reducers imported from home in platform. So we remove the import and reference in our src/platform/sagas.js making it clean and only jumble related
Then we do the same in src/platform/reducers.js making it clean as well
Now we can remove view/home and platform/home folders to be rid of the Home view for good and our structure should now be clean and only jumble related
bjorgvin@computer jumbles (develop)*$ tree -I 'node_modules'
│ ├── reducers.js
│ ├── sagas.js
│ └── jumbles
│ ├── actions.js
│ ├── reducer.js
│ └── sagas.js
│ ├── Detail.js
│ └── index.js
│ ├── New.js
│ └── index.js
Now our application is complete and the structure is final. We can now go ahead and style our views and components any way we choose.
The next face of the development would be look and feel. Introducing some styles and maybe some animation or whatever makes the user feel good about using our application. This will be addressed in our fourth and final part of this guide called Look n’ Feel; lets go…