How would I go about setting up this modal to toggle? - reactjs

I'm trying to get an editModal and a detailsModal to open on click. I set up actions and reducers for the modal so it can be stored in the global state and it works, but currently the modals are set to open automatically and I cannot close them. I believe it's something wrong with my logic, but it seems like it would be rather straightforward. Does anyone have any tips on how to complete this?
modalReducer.js
import { OPEN_MODAL } from "../actions/types";
const initialState = {
modal: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case OPEN_MODAL:
return {
...state,
modal: false,
};
default:
return state;
}
}
ClientEditModal.js
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input,
} from "reactstrap";
import { connect } from "react-redux";
import { editClient } from "../actions/clientActions";
import { openModal } from "../actions/modalActions";
import PropTypes from "prop-types";
class ClientEditModal extends Component {
componentDidMount() {
this.props.editClient();
this.props.openModal();
}
toggle = () => {
this.setState({
modal: !this.props.modal,
});
};
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
onSubmit = (e) => {
e.preventDefault();
// Close modal
this.toggle();
};
displayClient = (clients, _id) => {
return (
<FormGroup key={_id} timeout={500} classNames="fade">
<Label for="name"> Name </Label>
<Input
type="text"
name="name"
id="client"
value={clients.name}
onChange={this.onChange}
></Input>
<Label for="email"> Email </Label>
<Input
type="text"
name="email"
id="client"
value={clients.email}
onChange={this.onChange}
></Input>
<Label for="number"> Number </Label>
<Input
type="text"
name="number"
id="number"
value={clients.number}
onChange={this.onChange}
></Input>
<Button color="dark" style={{ marginTop: "2rem" }} block>
Submit Client Edit
</Button>
</FormGroup>
);
};
render() {
const { clients } = this.props.client;
return (
// Split button into separate component
<div>
<Button
color="dark"
style={{ marginBottom: "2rem", marginLeft: "1rem" }}
onClick={this.toggle}
>
Edit
</Button>
<Modal
isOpen={this.props.modal}
toggle={this.toggle}
style={{ padding: "50px" }}
>
<ModalHeader toggle={this.toggle}> Edit</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
{clients.map(this.displayClient)}
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
ClientEditModal.propTypes = {
editClient: PropTypes.func.isRequired,
client: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
client: state.client,
modal: state.modal,
});
export default connect(mapStateToProps, { editClient, openModal })(
ClientEditModal
);
modalActions.js
import { OPEN_MODAL } from "./types";
export const openModal = () => {
return {
type: OPEN_MODAL,
};
};

The main issue is - your toggle function, !this.props.modal will invert the value of modal and the component always holds the value of true.
So, one option is to maintain another action for closing modal.
See the updated code snippets below.
Reducer
import { OPEN_MODAL } from "../actions/types";
const initialState = {
modal: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case OPEN_MODAL:
return {
...state,
modal: false,
};
case CLOSE_MODAL:
return {
...state,
modal: true,
};
default:
return state;
}
}
Actions
import { OPEN_MODAL } from "./types";
export const openModal = () => {
return {
type: OPEN_MODAL,
};
};
export const closeModal = () => {
return {
type: CLOSE_MODAL,
};
};
ClientModal.js
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input,
} from "reactstrap";
import { connect } from "react-redux";
import { editClient } from "../actions/clientActions";
import { openModal } from "../actions/modalActions";
import PropTypes from "prop-types";
class ClientEditModal extends Component {
componentDidMount() {
this.props.editClient();
this.props.openModal();
}
toggle = () => {
// this.setState({ //<---- setstate is not required as once the state in the store is updated, then the component is re-rendered automatically
// modal: !this.props.modal, //<---- once toggle is executed - it is always true
//});
this.props.closeModal()
};
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
onSubmit = (e) => {
e.preventDefault();
// Close modal
this.toggle();
};
displayClient = (clients, _id) => {
return (
<FormGroup key={_id} timeout={500} classNames="fade">
<Label for="name"> Name </Label>
<Input
type="text"
name="name"
id="client"
value={clients.name}
onChange={this.onChange}
></Input>
<Label for="email"> Email </Label>
<Input
type="text"
name="email"
id="client"
value={clients.email}
onChange={this.onChange}
></Input>
<Label for="number"> Number </Label>
<Input
type="text"
name="number"
id="number"
value={clients.number}
onChange={this.onChange}
></Input>
<Button color="dark" style={{ marginTop: "2rem" }} block>
Submit Client Edit
</Button>
</FormGroup>
);
};
render() {
const { clients } = this.props.client;
return (
// Split button into separate component
<div>
<Button
color="dark"
style={{ marginBottom: "2rem", marginLeft: "1rem" }}
onClick={this.toggle}
>
Edit
</Button>
<Modal
isOpen={this.props.modal}
toggle={this.toggle}
style={{ padding: "50px" }}
>
<ModalHeader toggle={this.toggle}> Edit</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
{clients.map(this.displayClient)}
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
ClientEditModal.propTypes = {
editClient: PropTypes.func.isRequired,
client: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
client: state.client,
modal: state.modal,
});
export default connect(mapStateToProps, { editClient, openModal, closeModal })(
ClientEditModal
);

Related

onClick call component with props

Following is Navigation link with onclick event. I couldn't figure out how to call ItemModal component when navigation with onclick event is called. I have tried with following code, it's not working.
<strong onClick={() => <ItemModal modal={true} isAuthenticated={true} />}> Add Item </strong>
Below is ItemModal components with reactstrap modal to add new items.
ItemModal.js
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input,
} from "reactstrap";
import { connect } from "react-redux";
import { addItem } from "../actions/itemActions";
import PropTypes from "prop-types";
class ItemModal extends Component {
state = {
modal: false,
name: "",
};
static propTypes = {
isAuthenticated: PropTypes.bool,
};
toggle = () => {
this.setState({
modal: !this.state.modal,
});
};
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
onSubmit = (e) => {
e.preventDefault();
const newItem = {
name: this.state.name,
};
// Add item via addItem action
this.props.addItem(newItem);
//Close Modal
this.toggle();
};
render() {
return (
<div>
{this.props.isAuthenticated ? (
<Button
color="dark"
style={{ marginBottom: "2rem" }}
onClick={this.toggle}
>
Add Item
</Button>
) : (
<h4 className="mb-3 ml-4"> Please log in to manage items</h4>
)}
<Modal isOpen={this.state.modal} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}> Add to Shopping List</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label for="item">Item</Label>
<Input
type="text"
name="name"
id="item"
placeholder="Add shopping item"
onChange={this.onChange}
/>
<Button color="dark" style={{ marginTop: "2rem" }} block>
Add Item
</Button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
const mapStateToProps = (state) => ({
item: state.item,
isAuthenticated: state.auth.isAuthenticated,
});
export default connect(mapStateToProps, { addItem })(ItemModal);
How I can call component on click so that reactstrap model (ItemModal) is opened?

State is not passing correctly with redux

Hey i have two form that i want to keep there state seperatly, i added redux and added reducer to keep the state change, but my problem is when i change on form state it's add to the other form state his state also, i want to keep each state seperately
like you see in picture the replace property have been added to the newProjectForm when it only need to be at the roof form.
I have the same reducer on forms because i only need to keep track only onInputChange.
This are my two form:
First Form:
import React from 'react'
import {Form, FormGroup, ControlLabel, FormControl, HelpBlock, Button, ButtonToolbar,
Toggle } from 'rsuite';
import InputPicker from '../input-picker';
import './form.css'
import {onInputChanged} from '../../redux/actions/form.actions';
import { connect } from 'react-redux';
//onChange = {props.onInputChanged} formValue = {props.newProjectForm}
const options = ["yes","no"];
function NewProjectForm(props){
return (
<Form className='form' layout="horizontal" >
<h1 className='title'>New Project</h1>
<br/>
<FormGroup>
<ControlLabel>Address</ControlLabel>
<FormControl name="address" />
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>Affilate</ControlLabel>
<InputPicker name="affilate" data ={options} onChange={props.onInputChanged} style={{width:'300px'}}/>
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>Size</ControlLabel>
<FormControl name="size" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Bedroom Amount</ControlLabel>
<FormControl name="bedrooms" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Bathrooms amount</ControlLabel>
<FormControl name="bathrooms" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Stories</ControlLabel>
<FormControl name="stories" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Has Gas</ControlLabel>
<Toggle name="gas"/>
</FormGroup>
<FormGroup>
<ButtonToolbar>
<Button appearance="primary">Save</Button>
<Button appearance="default">Discard</Button>
</ButtonToolbar>
</FormGroup>
</Form>
);
}
const mapStateToProps = (state) => {
return {
newProjectForm: state.newProjectForm
}
};
const mapDispatchToProps = (dispatch) => {
return {
onInputChanged: (event) => dispatch(onInputChanged(event))
}
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(NewProjectForm);
Second Form:
import React from 'react'
import {Form, FormGroup, ControlLabel, FormControl, HelpBlock, Button, ButtonToolbar} from 'rsuite';
import './form.css'
import {onInputChanged} from '../../redux/actions/form.actions';
import { connect } from 'react-redux';
import InputPicker from '../input-picker';
function RoofForm(props){
//replace all-roof type description color
const options = ["yes","no"];
// console.log(props);
//onChange={props.onInputChanged}
return (
<Form className='form' layout="horizontal" onChange = {props.onInputChanged} formValue = {props.roofForm}>
<h1 className='title'>Roof</h1>
<br/>
<FormGroup>
<ControlLabel>Replace ?</ControlLabel>
<InputPicker name="replace" style={{ width: 300 }} data={options} onChange={props.onInputChanged}/>
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>All Roof?</ControlLabel>
<InputPicker name="all-roof" style={{ width: 300 }} data={options} onChange={props.onInputChanged}/>
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
{props.roofForm['all-roof'] === 'yes' && (<div><FormGroup>
<ControlLabel>Type</ControlLabel>
<InputPicker name="type" style={{ width: 300 }} />
</FormGroup>
<FormGroup>
<ControlLabel>Description</ControlLabel>
<InputPicker name="description" style={{ width: 300 }} />
</FormGroup>
{props.roofForm.type === 'shingles' && <FormGroup>
<ControlLabel>Color</ControlLabel>
<InputPicker name="color" style={{ width: 300 }} />
</FormGroup>}
</div>)
}
<FormGroup>
<ControlLabel>Rain diverter</ControlLabel>
<FormControl name="rain-diverter" type="number" style={{ width: 300 }} />
</FormGroup>
<FormGroup>
<ControlLabel>Drip edge</ControlLabel>
<FormControl name="drip-edge" type="number" style={{ width: 300 }}/>
</FormGroup>
<FormGroup>
<ButtonToolbar>
<Button appearance="primary">Save</Button>
<Button appearance="default">Discard</Button>
</ButtonToolbar>
</FormGroup>
</Form>
);
}
const mapStateToProps = (state) => {
return {
roofForm: state.roofForm
}
};
const mapDispatchToProps = (dispatch) => {
return {
onInputChanged: (event) => dispatch(onInputChanged(event))
}
};
export default connect(
mapStateToProps,
mapDispatchToProps)
(RoofForm);
and this is my redux setup:
Form Actions:
export const ON_INPUT_CHANGED = 'ON_INPUT_CHANGED';
export function onInputChanged(event) {
console.log(event);
return(
{
type: ON_INPUT_CHANGED,
payload: event
}
)
}
and this is my reducer:
import {ON_INPUT_CHANGED} from '../actions/form.actions';
function formReducerWrapper(defaultState){
return (state=defaultState, action) => {
switch(action.type){
case ON_INPUT_CHANGED:
state = {
...state,
...action.payload
}
break;
default:
return state;
}
return state;
}
}
const newProjectDefault = {
affilate: {label:"", value: ""}
}
const roofDefault ={
replace:{label:"",value:""},
"all-roof":{label:"",value:""},
type:{label:"",value:""},
description: {label:"",value:""},
color:{label:"",value:""}
}
export const newProjectReducer = formReducerWrapper(newProjectDefault);
export const roofReducer = formReducerWrapper(roofDefault);
Thanks in Advance:)
It looks like you are trying to merge the input change event directly into redux state. That is not how it works.
You can access the HTML input element that changed from the change event using event.target and then read the latest value event.target.value.
If you want the form data to stay separate, then you need to dispatch different information for each form. For example:
dispatch({ type: 'input-changed', form: 'newProject', field: 'affiliate', value: event.target.value });
In your reducers, it should skip any events for a different form.
if (event.form !== 'newProject') return state;
If it becomes tedious there are helper packages like redux-form to help structure the application to avoid repetitive code. It also works fine to store form data in local state instead of putting it into redux.

Can't get jest test to fire button onclick

I am completely new to both react and testing and am a bit stumped.
I was just wondering if someone could tell me why my test fails. I assume I making a basic mistake in how this should work.
I am trying to test a log in page. At the moment I am just trying to get my test to fire an onclick from a button and check that that a function has been called.
The log in component code can be seen below.
import React, { Component, Fragment } from "react";
import { Redirect } from "react-router-dom";
// Resources
//import logo from "assets/img/white-logo.png";
//import "./Login.css";
// Material UI
import {
withStyles,
MuiThemeProvider,
createMuiTheme
} from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
import Person from "#material-ui/icons/Person";
import InputAdornment from "#material-ui/core/InputAdornment";
// Custom Components
import Loading from "components/Loading/Loading.jsx";
// bootstrat 1.0
import { Alert, Row } from "react-bootstrap";
// MUI Icons
import LockOpen from "#material-ui/icons/LockOpen";
// remove
import axios from "axios";
// API
import api2 from "../../helpers/api2";
const styles = theme => ({
icon: {
color: "#fff"
},
cssUnderline: {
color: "#fff",
borderBottom: "#fff",
borderBottomColor: "#fff",
"&:after": {
borderBottomColor: "#fff",
borderBottom: "#fff"
},
"&:before": {
borderBottomColor: "#fff",
borderBottom: "#fff"
}
}
});
const theme = createMuiTheme({
palette: {
primary: { main: "#fff" }
}
});
class Login extends Component {
constructor(props, context) {
super(props, context);
this.state = {
username: "",
password: "",
isAuthenticated: false,
error: false,
toggle: true,
// loading
loading: false
};
}
openLoading = () => {
this.setState({ loading: true });
};
stopLoading = () => {
this.setState({ loading: false });
};
toggleMode = () => {
this.setState({ toggle: !this.state.toggle });
};
handleReset = e => {
const { username } = this.state;
this.openLoading();
api2
.post("auth/admin/forgotPassword", { email: username })
.then(resp => {
this.stopLoading();
console.log(resp);
})
.catch(error => {
this.stopLoading();
console.error(error);
});
};
handleSubmit = event => {
event.preventDefault();
localStorage.clear();
const cred = {
username: this.state.username,
password: this.state.password
};
api2
.post("auth/admin", cred)
.then(resp => {
console.log(resp);
localStorage.setItem("api_key", resp.data.api_key);
localStorage.setItem("username", cred.username);
return this.setState({ isAuthenticated: true });
})
.catch(error => {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log("Error", error.message);
}
console.log(error.config);
return this.setState({ error: true });
});
};
handleInputChange = event => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
};
forgotPassword = () => {
console.log("object");
};
render() {
const { error } = this.state;
const { isAuthenticated } = this.state;
const { classes } = this.props;
if (isAuthenticated) {
return <Redirect to="/home/dashboard" />;
}
return (
<div className="login-page">
<video autoPlay muted loop id="myVideo">
<source
src=""
type="video/mp4"
/>
</video>
<div className="videoOver" />
<div className="midl">
<Row className="d-flex justify-content-center">
<img src={''} className="Login-logo" alt="logo" />
</Row>
<br />
<Row className="d-flex justify-content-center">
{error && (
<Alert style={{ color: "#fff" }}>
The username/password entered is incorrect. Try again!
</Alert>
)}
</Row>
<MuiThemeProvider theme={theme}>
<Row className="d-flex justify-content-center">
<TextField
id="input-username"
name="username"
type="text"
label="username"
value={this.state.username}
onChange={this.handleInputChange}
InputProps={{
className: classes.icon,
startAdornment: (
<InputAdornment position="start">
<Person className={classes.icon} />
</InputAdornment>
)
}}
/>
</Row>
{this.state.toggle ? (
<Fragment>
<br />
<Row className="d-flex justify-content-center">
<TextField
id="input-password"
name="password"
type="password"
label="pasword"
value={this.state.password}
onChange={this.handleInputChange}
className={classes.cssUnderline}
InputProps={{
className: classes.icon,
startAdornment: (
<InputAdornment position="start">
<LockOpen className={classes.icon} />
</InputAdornment>
)
}}
/>
</Row>
</Fragment>
) : (
""
)}
</MuiThemeProvider>
<br />
<Row className="d-flex justify-content-center">
{this.state.toggle ? (
<Button
className="button login-button"
data-testid='submit'
type="submit"
variant="contained"
color="primary"
onClick={this.handleSubmit}
name = "logIn"
>
Login
</Button>
) : (
<Button
className="button login-button"
type="submit"
variant="contained"
color="primary"
onClick={this.handleReset}
>
Reset
</Button>
)}
</Row>
<Row className="d-flex justify-content-center">
<p onClick={this.toggleMode} className="text-link">
{this.state.toggle ? "Forgot password?" : "Login"}
</p>
</Row>
</div>
<Loading open={this.state.loading} onClose={this.handleClose} />
</div>
);
}
}
export default withStyles(styles)(Login);
My current test which fails.
import React from 'react'
import {render, fireEvent, getByTestId} from 'react-testing-library'
import Login from './login'
describe('<MyComponent />', () => {
it('Function should be called once', () => {
const functionCalled = jest.fn()
const {getByTestId} = render(<Login handleSubmit={functionCalled} />)
const button = getByTestId('submit');
fireEvent.click(button);
expect(functionCalled).toHaveBeenCalledTimes(1)
});
});
I'm also fairly new to React Testing Library, but it looks like your button is using this.handleSubmit, so passing your mock function as a prop won't do anything. If you were to pass handleSubmit in from some container, then I believe your tests, as you currently have them, would pass.

Redux not reaching the action

Here is the login.js file
import {Form, Icon, Input, Button} from 'antd';
import React from 'react'
import axios from 'axios'
import {connect} from 'react-redux'
import {loading} from '../actions/authenticationActions'
class Login extends React.Component {
handleSubmit = (e) => {
e.preventDefault();
return this.props.onloading;
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div className={'ant-col-12 ant-col-offset-6'} style={{ display: 'inline-flex', justifyContent: 'center', alignItems: 'center', height: "100vh"}}>
<Form onSubmit={this.handleSubmit} className="login-form" style={{width: "300px"}}>
<Form.Item>
{getFieldDecorator('userName', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" />
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="Password" />
)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button" style={{width: "100%"}}>
Log in
</Button>
</Form.Item>
</Form>
</div>
);
}
}
const WrappedLogin = Form.create({ name: 'normal_login' })(Login);
const mapActionsToProps = {
onloading: loading
};
export default connect(null, mapActionsToProps)(WrappedLogin);
and here is the Actions file:
import {LOADING} from "../utils/ActionTypes";
export function loading() {
console.log("action received");
return {
type: LOADING
}
}
also here is the Reducers file:
import {LOADING} from "../utils/ActionTypes";
const initialState= [{
Authorized: false,
user: null,
token: null,
loading: false
}];
export default function authenticationReducer(state = initialState, action) {
switch (action.type) {
case LOADING:
return console.log("loading....");
default:
return state;
}
}
and finally the Store file:
import {combineReducers, createStore, compose} from "redux";
import authenticationReducer from "../reducers/authenticationReducer";
const store = createStore(authenticationReducer, compose(
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));
export default store;
hi, I have just started learning redux and I have this issue where as you can see in the actions file there is a console message the problem is I'm not reaching that point and I don't know why any help is appreciated...
You're not calling your action this.props.onloading anywhere in your code. Calling it in the method will dispatch the corresponding action.
handleSubmit = (e) => {
e.preventDefault();
this.props.onloading()
};

How do I clear an array in the state of my Redux store?

I am building a small e-commerce shop and I am trying to clear my cart after a successful checkout. The cart contains cartItems which are stored in the Redux store. I am getting the console log in my function in the StripeCheckoutForm component and the action creator.
I am not seeing the console log for the reducer so I suspect something is wrong with my action creator. I am not sure about best practices concerning action creators. I was wondering when, why, and how to use dispatch in the action creator. The docs for Redux aren't exactly clear for me.
Here is my StripeCheckout:
import React, {Component} from 'react';
import { CardElement, injectStripe } from 'react-stripe-elements';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { clearCart } from '../actions/clearCartAction';
import getTotal from '../helpers/getTotalHelper';
import { Container, Col, Form, FormGroup, Input } from 'reactstrap';
import './StripeCheckoutForm.css';
const cardElement = {
base: {
color: '#32325d',
width: '50%',
lineHeight: '30px',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '18px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
const FIREBASE_FUNCTION = 'https://us-central1-velo-velo.cloudfunctions.net/charge/';
// Function used by all three methods to send the charge data to your Firebase function
async function charge(token, amount, currency) {
const res = await fetch(FIREBASE_FUNCTION, {
method: 'POST',
body: JSON.stringify({
token,
charge: {
amount,
currency,
},
}),
});
const data = await res.json();
data.body = JSON.parse(data.body);
return data;
}
class CheckoutForm extends Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
state = {
complete: false
}
clearCartHandler = () => {
console.log('clearCartHandler');
this.props.onClearCart()
}
// User clicked submit
async submit(ev) {
console.log("clicked!")
const {token} = await this.props.stripe.createToken({name: "Name"});
const total = getTotal(this.props.cartItems);
const amount = total; // TODO: replace with form data
const currency = 'USD';
const response = await charge(token, amount, currency);
if (response.statusCode === 200) {
this.setState({complete: true});
console.log('200!!',response);
this.clearCartHandler();
} else {
alert("wrong credit information")
console.error("error: ", response);
}
}
render() {
if (this.state.complete) {
return (
<div>
<h1 className="purchase-complete">Purchase Complete</h1>
<Link to='/'>
<button>Continue Shopping</button>
</Link>
</div>
);
}
return (
<div className="checkout-wrapper">
<Container className="App">
<h2 className='text-center'>Let's Checkout</h2>
<Form className="form">
<Col>
<FormGroup>
<Input
type="first name"
name="first name"
id="exampleEmail"
placeholder="first name"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
type="last name"
name="last name"
id="exampleEmail"
placeholder="last name"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
type="address"
name="address"
id="exampleEmail"
placeholder="address"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
type="city"
name="city"
id="exampleEmail"
placeholder="city"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
type="prefecture"
name="prefecture"
id="exampleEmail"
placeholder="prefecture"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
type="zipcode"
name="zipcode"
id="exampleEmail"
placeholder="zipcode"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
type="email"
name="email"
id="exampleEmail"
placeholder="myemail#email.com"
/>
</FormGroup>
</Col>
<div className="card-element">
<CardElement style={cardElement}/>
</div>
</Form>
<button className="checkout-button" disabled={false} onClick={this.submit}>Submit</button>
</Container>
</div>
);
}
}
const mapStateToProps = state => {
return {
cartItems: state.shoppingCart.cartItems
}
}
const mapDispatchToProps = state => {
return {
onClearCart: () => (clearCart())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(injectStripe(CheckoutForm));
Here is my action creator:
import { CLEAR_CART } from './types';
// export const clearCart = (dispatch) => {
// console.log('clear_action')
// dispatch({
// type: CLEAR_CART,
// })
// }
export function clearCart() {
console.log('clear_action')
return {
type: CLEAR_CART
}
}
and finally my reducer:
import {ADD_TO_CART} from '../actions/types';
import {REMOVE_FROM_CART} from '../actions/types';
import {CLEAR_CART} from '../actions/types';
const initialState = {
cartItems: [],
}
export default function(state = initialState, action) {
switch(action.type) {
case ADD_TO_CART:
console.log('ADD_reducer');
return {
...state,
cartItems: [...state.cartItems, action.payload],
}
case REMOVE_FROM_CART:
console.log('REMOVE_REDUCER', action.payload, state.cartItems);
return {
...state,
cartItems: state.cartItems.filter(item => item.id !== action.payload.id)
}
case CLEAR_CART:
console.log('CLEAR_REDUCER');
return {
...state,
cartItems: []
}
default:
return state;
}
}
Action has to be dispatched like below. Let me know if it works
const mapDispatchToProps = (dispatch) => {
return {
onClearCart: () => (dispatch(clearCart()))
}
};

Resources