Skip to main content

17.14 Combining Reducers

BurgerBuilder.js

src\containers\BurgerBuilder\BurgerBuilder.js
import React, { Component, Fragment } from "react";
import { connect } from "react-redux";
import axios from "../../axios-orders";
import BuildControls from "../../components/Burger/BuildControls/BuildControls";
import Burger from "../../components/Burger/Burger";
import OrderSummary from "../../components/Burger/OrderSummary/OrderSummary";
import Modal from "../../components/UI/Modal/Modal";
import Spinner from "../../components/UI/Spinner/Spinner";
import withErrorHandler from "../../hoc/withErrorHandler/withErrorHandler";
import * as burgerBuilderActions from "../../store/actions/index";

class BurgerBuilder extends Component {
// constructor(props) {
// super(props);
// this.state = {...}
// }
state = {
purchasing: false,
};

componentDidMount() {
console.log(this.props);
this.props.onInitIngredients();
}

updatePurchaseState(ingredients) {
const sum = Object.keys(ingredients)
.map((igKey) => {
return ingredients[igKey];
})
.reduce((sum, el) => {
return sum + el;
}, 0);
return sum > 0;
}

purchaseHandler = () => {
this.setState({ purchasing: true });
};

purchaseCancelHandler = () => {
this.setState({ purchasing: false });
};

purchaseContinueHandler = () => {
this.props.history.push("/checkout");
};
render() {
const disabledInfo = {
...this.props.ings,
};

for (let key in disabledInfo) {
disabledInfo[key] = disabledInfo[key] <= 0;
}
let orderSummary = null;

let burger = this.props.error ? (
<p>Ingredients can't be loaded!</p>
) : (
<Spinner />
);

if (this.props.ings) {
burger = (
<Fragment>
<Burger ingredients={this.props.ings} />
<BuildControls
ingredientAdded={this.props.onIgredientAdded}
ingredientRemoved={this.props.onIgredientRemoved}
disabled={disabledInfo}
purchasable={this.updatePurchaseState(this.props.ings)}
ordered={this.purchaseHandler}
price={this.props.price}
/>
</Fragment>
);

orderSummary = (
<OrderSummary
ingredients={this.props.ings}
purchaseCancelled={this.purchaseCancelHandler}
purchaseContinued={this.purchaseContinueHandler}
price={this.props.price}
/>
);
}

return (
<Fragment>
<Modal
show={this.state.purchasing}
modalClosed={this.purchaseCancelHandler}
>
{orderSummary}
</Modal>
{burger}
</Fragment>
);
}
}

const mapStateToProps = (state) => {
return {
ings: state.burgerBuilder.ingredients,
price: state.burgerBuilder.totalPrice,
error: state.burgerBuilder.error,
};
};

const mapDispatchToProps = (dispatch) => {
return {
onIgredientAdded: (ingName) =>
dispatch(burgerBuilderActions.addIngredient(ingName)),
onIgredientRemoved: (ingName) =>
dispatch(burgerBuilderActions.removeIngredient(ingName)),
onInitIngredients: () => dispatch(burgerBuilderActions.initIngredients()),
};
};

export default connect(
mapStateToProps,
mapDispatchToProps
)(withErrorHandler(BurgerBuilder, axios));

ContactData.js

src\containers\Checkout\ContactData\ContactData.js
import React, { Component } from "react";
import { connect } from "react-redux";
import axios from "../../../axios-orders";
import Button from "../../../components/UI/Button/Button";
import Input from "../../../components/UI/Input/Input";
import Spinner from "../../../components/UI/Spinner/Spinner";
import withErrorHandler from "../../../hoc/withErrorHandler/withErrorHandler";
import * as actions from "../../../store/actions/index";
import classes from "./ContactData.module.css";

class ContactData extends Component {
state = {
orderForm: {
name: {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "Your Name",
},
value: "",
validation: {
required: true,
},
valid: false,
touched: false,
},
street: {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "Street",
},
value: "",
validation: {
required: true,
},
valid: false,
touched: false,
},
zipCode: {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "ZIP Code",
},
value: "",
validation: {
required: true,
minLength: 5,
maxLength: 5,
},
valid: false,
touched: false,
},
country: {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "Country",
},
value: "",
validation: {
required: true,
},
valid: false,
touched: false,
},
email: {
elementType: "input",
elementConfig: {
type: "email",
placeholder: "Your e-mail",
},
value: "",
validation: {
required: true,
},
valid: false,
touched: false,
},
deliveryMethod: {
elementType: "select",
elementConfig: {
options: [
{ value: "fastest", displayValue: "Fastest" },
{ value: "cheapest", displayValue: "Cheapest" },
],
},
value: "fastest",
valid: true,
validation: {
required: false,
},
},
},
formIsValid: false,
};

orderHandler = (event) => {
event.preventDefault();

const formData = {};
for (let formElementIdentifier in this.state.orderForm) {
formData[formElementIdentifier] = this.state.orderForm[
formElementIdentifier
].value;
}
const order = {
ingredients: this.props.ings,
price: this.props.price,
orderData: formData,
};
this.props.onOrderBurger(order);
};

checkValidity(value, rules) {
let isValid = true;
if (rules.required) {
isValid = value.trim() !== "" && isValid;
}

if (rules.minLength) {
isValid = value.length >= rules.minLength && isValid;
}

if (rules.maxLength) {
isValid = value.length <= rules.maxLength && isValid;
}

return isValid;
}

inputChangedHandler = (event, inputIdentifier) => {
const updatedOrderForm = {
...this.state.orderForm,
};
const updatedFormElement = {
...updatedOrderForm[inputIdentifier],
};
updatedFormElement.value = event.target.value;
updatedFormElement.valid = this.checkValidity(
updatedFormElement.value,
updatedFormElement.validation
);
updatedFormElement.touched = true;
updatedOrderForm[inputIdentifier] = updatedFormElement;

let formIsValid = true;
for (let inputIdentifier in updatedOrderForm) {
formIsValid = updatedOrderForm[inputIdentifier].valid && formIsValid;
}
this.setState({ orderForm: updatedOrderForm, formIsValid: formIsValid });
};
render() {
const formElementsArray = [];
for (let key in this.state.orderForm) {
formElementsArray.push({
id: key,
config: this.state.orderForm[key],
});
}
let form = (
<form onSubmit={this.orderHandler}>
{formElementsArray.map((formElement) => (
<Input
key={formElement.id}
elementType={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
invalid={!formElement.config.valid}
shouldValidate={formElement.config.validation}
touched={formElement.config.touched}
changed={(event) => this.inputChangedHandler(event, formElement.id)}
/>
))}
<Button btnType="Success" disabled={!this.state.formIsValid}>
ORDER
</Button>
</form>
);
if (this.props.loading) {
form = <Spinner />;
}
return (
<div className={classes.ContactData}>
<h4>Enter your Contact Data</h4>
{form}
</div>
);
}
}

const mapStateToProps = (state) => {
return {
ings: state.burgerBuilder.ingredients,
price: state.burgerBuilder.totalPrice,
loading: state.order.loading,
};
};

const mapDispatchToProps = (dispatch) => {
return {
onOrderBurger: (orderData) => dispatch(actions.purchaseBurger(orderData)),
};
};

export default connect(
mapStateToProps,
mapDispatchToProps
)(withErrorHandler(ContactData, axios));

Checkout.js

src\containers\Checkout\Checkout.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { Redirect, Route } from "react-router-dom";
import CheckoutSummary from "../../components/Order/CheckoutSummary/CheckoutSummary";
import ContactData from "./ContactData/ContactData";

class Checkout extends Component {
checkoutCancelledHandler = () => {
this.props.history.goBack();
};

checkoutContinuedHandler = () => {
this.props.history.replace("/checkout/contact-data");
};
render() {
let summary = <Redirect to="/" />;
if (this.props.ings) {
summary = (
<div>
<CheckoutSummary
ingredients={this.props.ings}
checkoutCancelled={this.checkoutCancelledHandler}
checkoutContinued={this.checkoutContinuedHandler}
/>
<Route
path={this.props.match.path + "/contact-data"}
component={ContactData}
/>
</div>
);
}

return summary;
}
}

const mapStateToProps = (state) => {
return {
ings: state.burgerBuilder.ingredients,
};
};

export default connect(mapStateToProps)(Checkout);

actions/order.js

src\store\actions\order.js
import axios from "../../axios-orders";
import * as actionTypes from "./actionTypes";

export const purchaseBurgerSuccess = (id, orderData) => {
return {
type: actionTypes.PURCHASE_BURGER_SUCCESS,
orderId: id,
orderData: orderData,
};
};

export const purchaseBurgerFail = (error) => {
return {
type: actionTypes.PURCHASE_BURGER_FAIL,
error: error,
};
};

export const purchaseBurgerStart = () => {
return {
type: actionTypes.PURCHASE_BURGER_START,
};
};

export const purchaseBurger = (orderData) => {
return (dispatch) => {
dispatch(purchaseBurgerStart());
axios
.post("/orders.json", orderData)
.then((response) => {
console.log(response.data);
dispatch(purchaseBurgerSuccess(response.data.name, orderData));
})
.catch((error) => {
dispatch(purchaseBurgerFail(error));
});
};
};

actions/index.js

src\index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import { applyMiddleware, combineReducers, compose, createStore } from "redux";
import thunk from "redux-thunk";
import App from "./App";
import "./index.css";
import registerServiceWorker from "./registerServiceWorker";
import burgerBuilderReducer from "./store/reducers/burgerBuilder";
import orderReducer from "./store/reducers/order";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const rootReducer = combineReducers({
burgerBuilder: burgerBuilderReducer,
order: orderReducer,
});

const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(thunk))
);

const app = (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
ReactDOM.render(app, document.getElementById("root"));
registerServiceWorker();