-
Notifications
You must be signed in to change notification settings - Fork 4
Checkout in React guide #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
d47c0aa
346111c
2ba98a7
c4ef972
afe782d
3c5066c
09fccdb
4b02c32
51d3cfe
8734d37
969c0b8
3128607
ae72d20
c8e156c
5792584
c5ccff3
bc19407
a2115a9
06ed540
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # Adding Checkout functionality to your React application | ||
|
|
||
| ``` | ||
| This guide is based on a demo that we ran a while back. Please take note that there might be issues for you when adding this functionality as your implementation probably looks different. This is also the first iteration of the guide and updates will be made in the future. | ||
| If there are typos or issues in this guide, please notify a coach | ||
| ``` | ||
|
|
||
| In this section we are going to add checkout functionality to our react application. | ||
| The prerequisite for this functionality is that we have products on our page that are ready to sell. Please remember as we implement we will omit explaining code that has already been covered. Remember to commit often in case you need to revert back and not lose large sections of the implementation. | ||
|
|
||
| Start with making sure that you have the latest code pulled from GitHub on your development branch and create a new branch called `add_order_functionality` or something that you think is appropriate to the user story. | ||
|
|
||
| As always we will work in a test-driven way. Making sure that we let our acceptance test guide our development. This will ensure us that we are not biting off more than we can chew. But also that we stay in scope. | ||
|
|
||
| If you want to only learn about how to create a checkout functionality in react you can clone down the following repos: | ||
| [API](https://github.com/CraftAcademy/slowfood_api_checkout_boilerplate) & | ||
| [Client](https://github.com/CraftAcademy/slowfood_client_checkout_boilerplate) | ||
|
|
||
| Let's gets started | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| ## Getting started | ||
|
|
||
| Create a new feature file by typing | ||
| `$ touch cypress/integration/userCanAddProductsToOrder.feature.js` | ||
|
|
||
| Add the following tests to the file: | ||
|
|
||
| ```js | ||
| describe("User can add a product to their order", () => { | ||
| before(() => { | ||
| cy.server(); | ||
| cy.route({ | ||
| method: "GET", | ||
| url: "http://localhost:3000/api/products", | ||
| response: "fixture:product_data.json", | ||
| }); | ||
|
|
||
| cy.route({ | ||
| method: "POST", | ||
| url: "http://localhost:3000/api/orders", | ||
| response: { message: "A product has been added to your order" }, | ||
| }); | ||
| }); | ||
|
|
||
| it("user gests a confirmation messsage when adding a a producut to order", () => { | ||
| cy.visit("/"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would have put this line in the |
||
| cy.get("#product-1").within(() => { | ||
| cy.get("button").contains("Add to order").click(); | ||
| }); | ||
| cy.contains("A product has been added to your order"); | ||
| }); | ||
| }); | ||
| ``` | ||
|
|
||
| So what are we expecting to see in the implementation? Well, we need to make sure that we can add a product to an order. This means that we need to have a list of products since before. After that, we need to make sure that there is a button to make this happen. And it will all end with us getting a confirmation message that we have added a product to our order. Simple huh? | ||
|
|
||
| When you run your test now it should all fail and to make it work we have to add the actual implementation code. | ||
|
|
||
| In our case we already have a component that we use to display the products, all we have to do now is to add the button to the iteration and then make sure that we can see the message. | ||
|
|
||
| Let's add the implementation code. | ||
|
|
||
| We need to start with the first step which is to make sure that we have a button available to click on, let's add that where we are mapping over the products, for example `DisplayProductData` | ||
|
|
||
|
faraznaeem marked this conversation as resolved.
|
||
| ```jsx | ||
| dataIndex = ( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rename this to |
||
| <div id="index"> | ||
| {this.state.productData.map((item) => { | ||
| return ( | ||
| <div key={item.id} id={`product-${item.id}`}> | ||
| {`${item.name} ${item.description} ${item.price}`} | ||
| <button onClick={this.addToOrder.bind(this)}>Add to order</button> | ||
| </div> | ||
| ); | ||
| })} | ||
| </div> | ||
| ); | ||
| ``` | ||
|
|
||
| So here we have added a `addToOrder` function that is not defined. Lets go ahead and add that functionality as well. | ||
|
|
||
| Add the following code to the component: | ||
|
|
||
| ```js | ||
| addToOrder() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please refactor so it is an arrow function instead.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this goes for the |
||
| debugger | ||
| } | ||
| ``` | ||
|
|
||
| So why are we adding a debugger here? Well, first we need to make sure that the `addToOrder` button gets invoked. | ||
|
|
||
| Run your tests now. The debugger should kick in and halt the execution, if not then make sure that you have added React developer tools to your chrome browser. | ||
|
|
||
| If we can see that then its time for us to add the real functionality. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rephrase this, perhaps "If we hit the debugger ...." |
||
|
|
||
| But first let's make sure that we are hitting the right button. | ||
|
|
||
| Go to your chrome console where the execution of the code has stopped and run the following command in the console: | ||
|
|
||
| `event.target.parentElement` | ||
|
|
||
| You should probably see something like this in your console: | ||
|
|
||
| ```html | ||
| <div id="product-1"> | ||
| "Pizza" "Crusty and fluffy" "100 SEK" | ||
| <button>Add to order</button> | ||
| </div> | ||
| ``` | ||
|
|
||
| If not then make sure that you are iterating over your products properly. | ||
| So this was our first iteration. But we need to make sure that we are pulling the information of the product as a dataset rather than through an id, and the reason for that is that it will be easier for us to manage. But it will also help us use it because the values in the product are sent back to us in the form of an object. | ||
|
|
||
| Add the dataset to the parent div: | ||
| `data-id={item.id} data-price={item.price}` | ||
|
|
||
| Like this: | ||
|
|
||
|
faraznaeem marked this conversation as resolved.
|
||
| ```js | ||
| return ( | ||
| <div | ||
| key={item.id} | ||
| id={`product-${item.id}`} | ||
| data-id={item.id} | ||
| data-price={item.price} | ||
| > | ||
| {`${item.name} ${item.description} ${item.price}`} | ||
| <button onClick={this.addToOrder.bind(this)}>Add to order</button> | ||
| </div> | ||
| ); | ||
| ``` | ||
|
|
||
| Now if you run your test, the debugger should kick in, run the following command in the console `event.target.parentElement.dataset`. | ||
| You should see the same information as before, but the information about the product will be returned to us in the form of an object. This is good news for us because we can use this in our `addToOrder` function. Time to add the rest of the implementation code to the `addToOrder` function. | ||
|
|
||
| ```js | ||
| async addToOrder(event) { | ||
| let id = event.target.parentElement.dataset.id | ||
| let result = await axios.post('http://localhost:3000/api/orders', { id: id } ) | ||
| } | ||
|
faraznaeem marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| So what did we do here? First, we made the method asynchronous because we need to wait for a response from our API, we passed in the event as an argument to the function and we also added `axios` to handle that call to the API. the last thing that we added was a post request that we save in the variable `result` | ||
|
|
||
| Make sure that you add `axios` and also import it to the top of the file if you haven't already. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| ## Displaying messages and the Backend | ||
|
|
||
| The next thing we are going to focus on is to get the message to be displayed. | ||
| Let's start with adding the message to the state: | ||
|
|
||
| ```js | ||
| state = { | ||
| productData: [], | ||
| message: {}, | ||
| }; | ||
| ``` | ||
|
|
||
| Next, we need to display the message below the button. | ||
|
|
||
| ```js | ||
| [....] | ||
|
faraznaeem marked this conversation as resolved.
|
||
| <button onClick={this.addToOrder.bind(this)}>Add to order</button> | ||
| {parseInt(this.state.message.id) === item.id && | ||
| <p class='message'>{this.state.message.message}</p>} | ||
| ``` | ||
|
|
||
| Finally, we need to set the new state in the `addToOrder` function | ||
|
|
||
| ```js | ||
| async addToOrder(event) { | ||
| let id = event.target.parentElement.dataset.id | ||
| let result = await axios.post('http://localhost:3000/api/orders', { id: id } ) | ||
| this.setState({message: {id: id, message: result.data.message}}) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need to set the |
||
| } | ||
| ``` | ||
|
|
||
| If we run our tests now everything should go green. | ||
|
faraznaeem marked this conversation as resolved.
|
||
|
|
||
| Let's move over to the backend and work on our API. We will start with creating a spec. Make sure that you create a new branch to work on. | ||
|
|
||
| ```bash | ||
| $ touch spec/requests/api/client_can_create_new_order_spec.rb | ||
| ``` | ||
|
|
||
| Add the following test to the spec file | ||
|
|
||
| ```ruby | ||
| RSpec.describe Api::OrdersController, type: :request do | ||
| let!(:product_1) { create(:product, name: 'Pizza') } | ||
| let!(:product_2) { create(:product, name: 'Kebab') } | ||
|
|
||
| it 'responds with success message' do | ||
| post '/api/orders', params: {id: product_1.id } | ||
|
|
||
| expect(JSON.parse(response.body)['message']).to eq 'The product has been added to your order' | ||
| end | ||
| end | ||
| ``` | ||
|
|
||
| Remember that the request spec acts like our feature test but for APIs. This will allow us to see what requests we are sending out. | ||
|
|
||
| Run the test and make sure that you are getting the correct error messages. | ||
|
|
||
| We will probably get an error message that states that we have an uninitialized constant `Api::OrdersController`. | ||
|
|
||
| We will fix this by generating a new orders controller. | ||
|
|
||
| `$ rails generate controller Api::Orders create` | ||
|
|
||
| Run the test again and you will get a new error message. | ||
|
|
||
| The error message probably has to do with our routes and that there are none present. Let's fix that. | ||
|
|
||
| ```ruby | ||
| Rails.application.routes.draw do | ||
| namespace :api do | ||
| resources :products, only: [:index] | ||
| resources :orders, only: [:create] | ||
| end | ||
| end | ||
| ``` | ||
|
|
||
| Our problem now is that we are not getting the right response. But this is expected. | ||
|
|
||
| To fix this we need to add the order to the create action in our Orders controller. | ||
|
|
||
| ```ruby | ||
| class Api::OrdersController < ApplicationController | ||
| def create | ||
| order = Order.create | ||
| order.order_items.create(product_id: params[:product_id]) | ||
| render json: { message: 'The product has been added to your order', order_id: order.id } | ||
| end | ||
| end | ||
| ``` | ||
| We are getting a new error: | ||
| ```bash | ||
| NameError: uninitialized constant Api::OrdersController::Order | ||
| ``` | ||
| To fix this we need to create a new model | ||
| Cool. let's move over to the model. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| ## The orders model | ||
|
|
||
| The next step we need to take is to generate an Order model to have something to pull out from the database. | ||
|
|
||
| `$ rails generate model order` | ||
|
|
||
| We also need to associate the `order` to the `order_items` that we have, and the `products`. | ||
|
|
||
| `$ rails generate model OrderItems order:references product:references` | ||
|
|
||
| Go ahead and go through the files that are generated and make sure that there are no problems. | ||
|
|
||
| We will now add specs to test the associations we created between the models. | ||
| In the newly generated `order_items_spec.rb` go ahead and add two specs. | ||
| You will need more than that but for this guide, we will limit ourselves to these specs. | ||
|
|
||
| ```ruby | ||
| require 'rails_helper' | ||
|
|
||
| RSpec.describe OrderItem, type: :model do | ||
| it { is_expected.to belong_to :order } | ||
| it { is_expected.to belong_to :product } | ||
| end | ||
| ``` | ||
|
|
||
| `order_spec.rb` | ||
|
|
||
| ```ruby | ||
| require 'rails_helper' | ||
|
|
||
| RSpec.describe Order, type: :model do | ||
| it {is_expected.to have_many :order_items} | ||
| end | ||
| ``` | ||
|
|
||
| We need to make sure that the associations are present as well. | ||
|
|
||
| `order_item.rb` | ||
|
|
||
| ```ruby | ||
| class OrderItem < ApplicationRecord | ||
| belongs_to :order | ||
| belongs_to :product | ||
| end | ||
| ``` | ||
|
|
||
| `order.rb` | ||
|
|
||
| ```ruby | ||
| class Order < ApplicationRecord | ||
| has_many :order_items | ||
| end | ||
| ``` | ||
|
|
||
| Run your migrations and then run your specs. Everything should be green like Bruce Banners alter ego. | ||
|
|
||
|  | ||
|
|
||
|
faraznaeem marked this conversation as resolved.
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would put this paragraph in italic or something because of the format can be kind of annoying to read with a code block