I'm trying to build an ecommerce Website, trying to build add to cart functionality, when i pass a callback function using props from a component to Parent component, it doesnt seem to work. I cant find any good documentation on this. I'm using commercejs as backend
Here is my product code:
import React, { Component } from 'react';
import stripHtml from 'string-strip-html';
import { Card, ListGroup, ListGroupItem, Button} from 'react-bootstrap';
class ProductItem extends Component {
constructor(props){
super(props);
this.handleAddToCart = this.handleAddToCart.bind(this);
}
handleAddToCart() {
this.props.onAddToCart(this.props.product.id, 1);
}
render() {
const { product } = this.props
const { result } =(product.description);
return(
<Card style={{ width: '18rem' }}>
<Card.Img variant="top" src={product.media.source} alt={product.name} />
<Card.Body>
<Card.Title>{product.name}</Card.Title>
<Card.Text>
{result}
</Card.Text>
</Card.Body>
<ListGroup className="list-group-flush">
<ListGroupItem>{product.price.formatted_with_symbol}</ListGroupItem>
<ListGroupItem>Dapibus ac facilisis in</ListGroupItem>
</ListGroup>
<Card.Body>
<Button name='Add to cart' className='product__btn' onClick={this.handleAddToCart}>Add to Cart</Button>
<Card.Link href="#">Another Link</Card.Link>
</Card.Body>
</Card>
)
}
}
export default ProductItem;
App.js file:
import React, { Component } from 'react';
import { commerce } from './lib/commerce';
import ProductsList from './components/ProductList';
import { Nav, Navbar, Form, FormControl, Button, NavDropdown} from 'react-bootstrap';
class App extends Component {
constructor(props) {
super(props);
this.state={
products:[],
cart:{},
}
}
componentDidMount() {
this.fetchProducts();
this.fetchCart();
}
fetchProducts() {
commerce.products.list().then((products) => {
this.setState({products: products.data});
}).catch((error) => {
console.log('There was an error fetching the products', error);
});
}
fetchCart() {
commerce.cart.retrieve().then((cart) => {
this.setState({ cart });
}).catch((error) => {
console.error('There was an error fetching the cart', error);
});
}
handleAddToCart(productId, quantity) {
commerce.cart.add(productId, quantity).then((item) => {
this.setState({cart: item.cart})
}).catch((error) => {
console.error('There was an error adding the item to the cart', error);
});
}
render() {
const { products } = this.state;
return(
<div className='App'>
<Navbar bg="light" expand="lg">
<Navbar.Brand href="#home">React-Bootstrap</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<Nav.Link href="#home">Home</Nav.Link>
<Nav.Link href="#link">Link</Nav.Link>
<NavDropdown title="Dropdown" id="basic-nav-dropdown">
<NavDropdown.Item href="#action/3.1">Action</NavDropdown.Item>
<NavDropdown.Item href="#action/3.2">Another action</NavDropdown.Item>
<NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item href="#action/3.4">Separated link</NavDropdown.Item>
</NavDropdown>
</Nav>
<Form inline>
<FormControl type="text" placeholder="Search" className="mr-sm-2" />
<Button variant="outline-success">Search</Button>
</Form>
</Navbar.Collapse>
</Navbar>
<ProductsList products={products} onAddToCart={this.handleAddToCart}/>
</div>
)
}
}
export default App;
You haven't actually called the function in the product list component right? Any reason for the same? You need to call the function even in the child component, just sending it won't help.
EDIT:
You haven't bound the function in the parent component. Replace handleAddToCart(params) with handleAddToCart=(params)=> and the code should work
So I am not sure if passing refs would be the best thing to do but it's kinda what I have set-out to do tell me if there is a better option..
So I am trying to have an onClick of a nav link, scroll down to the the div "contactForm".
App.js
import ContactForm from './components/ContactForm'
import ParllaxPage from './components/ParllaxPage'
import NavigationBar from './components/NavigationBar'
import React from 'react';
import './App.css';
const App = () => {
return (
< div cssClass="App" >
<body>
<span><NavigationBar /></span>
<ParllaxPage cssClass="parallax-wrapper" />
<ParllaxPage cssClass="parallax-wrapper parallax-pageOne" />
<ContactForm />
</body >
</div >
);
}
export default App;
I was trying to use forwardRef but I am not sure that I was doing it correctly so...
NavigationBar.js
import ContactForm from "./ContactForm";
import React, { useRef } from "react";
import App from "../App";
import { Nav, Navbar, Form, FormControl, Button } from "react-bootstrap";
const ContactFormRef = React.forwardRef((props, ref) => (
<ContactForm className="contactForm" ref={ref}>
{props.children}
</ContactForm>
));
const scrollToRef = (ref) => ref.current.scrollIntoView({ behavior: "smooth" });
const NavigationBar = () => {
const ref = React.forwardRef(ContactFormRef);
return (
<Navbar bg="light" expand="lg">
<Navbar.Brand href="#home">A1 Gutters</Navbar.Brand>
<Navbar.Toggle aria-controls="b casic-navbar-nav" />
<Nav className="mr-auto">
<Nav.Link href="#home">Home</Nav.Link>
<Nav.Link href="#link">Link</Nav.Link>
<Nav.Link href="#" onClick={console.log(ref)}>
Contact
</Nav.Link>
</Nav>
</Navbar>
);
};
export default NavigationBar;
I don't think the other files really need to be shown, I am just trying to get the className out of the ContactForm component so I can scroll to it onClick.. I currently just have a console.log in the onClick.
Using Hooks will simplify here.
Have state variable for gotoContact and ref for contactRef
Add click handler for navigation link contact
Add useEffect hook and when ever use click on contact and ref is available (value in ref.current) then call the scroll to view)
import ContactForm from "./components/ContactForm";
import ParllaxPage from "./components/ParllaxPage";
import React, { useState, useEffect, useRef } from "react";
import "./App.css";
const NavigationBar = ({ onClickContact }) => {
return (
<Navbar bg="light" expand="lg">
<Navbar.Brand href="#home">A1 Gutters</Navbar.Brand>
<Navbar.Toggle aria-controls="b casic-navbar-nav" />
<Nav className="mr-auto">
<Nav.Link href="#home">Home</Nav.Link>
<Nav.Link href="#link">Link</Nav.Link>
<Nav.Link href="#" onClick={() => onClickContact()}>
Contact
</Nav.Link>
</Nav>
</Navbar>
);
};
const App = () => {
const [gotoContact, setGotoContact] = useState(false);
const contactRef = useRef(null);
useEffect(() => {
if (gotoContact && contactRef.current) {
contactRef.current.scrollIntoView();
setGotoContact(false);
}
}, [gotoContact, contactRef.current]);
return (
<div cssClass="App">
<body>
<span>
<NavigationBar onClickContact={() => setGotoContact(true)} />
</span>
<ParllaxPage cssClass="parallax-wrapper" />
<ParllaxPage cssClass="parallax-wrapper parallax-pageOne" />
<div ref={contactRef}>
<ContactForm />
</div>
</body>
</div>
);
};
export default App;
You should identify the div "contactForm" with an id and have an anchor tag point to it:
<div id="contactForm"></div>
You can add scroll-behaviour: smooth to the body in CSS
No need to create a separate ContactFormRef wrapper. Simply use React.forwardRef in ContactForm itself. Those not passing a ref will not have to know it forwards refs.
Then, remember to further pass the ref received to a native element or use useImperativeHandle hook to add methods to it without passing it further down.
const ref = React.forwardRef(ContactFormRef)
This is wrong.
You should do it the same as with native components:
const ref = useRef()
return <ContactForm ref={ref} >
// etc
</ContactForm>
You are not rendering the ContactFormRef, so the reference points no nothing!
App.js should be like:
...
const App = () => {
const myNestedRefRef=React.useRef();
return (
...
<NavigationBar contactRef={myNestedRefRef}/>
...
<ContactForm ref={myNestedRefRef} />
...
);
}
...
ContactForm.js
...
function ContactForm=React.forwardRef((props, ref) => (
<form ref={ref}>
...
</form>
));
NavigationBar.js
const NavigationBar = ({contactRef}) => {
return (
...
<Nav.Link href="#" onClick={console.log(contactRef)}>
...
);
};
Consider that
If the <ContactForm/> hasn't been rendered yet, the ref will look like {current:null}
I have a navbar for all pages. I want to make a cart in it, however, when I go to the internal product page, the props that I transmit are not displayed.
Why is this happening ?
I think this is my problem React router v4 not working with Redux
but how i can implement this ?
What do you think ?
App.js
import React, {Component} from 'react';
import {Container} from 'reactstrap';
import {
BrowserRouter,
Route,
Switch
} from "react-router-dom";
import './App.css';
import NavbarMenu from './components/navbar'
import Main from './components/main';
import Good from './components/good';
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<NavbarMenu/>
<Container>
<Switch>
<Route path="/" exact component={Main}/>
<Route path="/good/:id" component={Good}/>
</Switch>
</Container>
</div>
</BrowserRouter>
);
}
}
export default App;
navbar
import React from 'react';
import {
Collapse,
Navbar,
NavbarToggler,
NavbarBrand,
Nav,
UncontrolledDropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
Button
} from 'reactstrap';
import {withRouter} from 'react-router-dom';
import connect from 'react-redux/es/connect/connect';
import {getCart} from '../../redux/actions/cartAction';
class NavbarMenu extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
isOpen: false
};
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
console.log(this.props)
const cartLength = this.props.cart.length;
const cartItems = this.props.cart.map(e => {
return <div key={e.id} style={{marginBottom: '20px'}}>
<DropdownItem
style={{display: 'inline-block', width: 'auto'}}
onClick={() => {
this.props.history.push('/good/' + e.id)
}}>
{e.name}
</DropdownItem>
<Button
style={{display: 'inline-block', float: 'right', marginRight: '20px'}}
color="danger"
>X</Button>
</div>
});
return (
<Navbar
color="light"
light expand="md"
style={{marginBottom: '20px'}}
>
<NavbarBrand
style={{cursor: 'pointer'}}
onClick={() => {
this.props.history.push('/')
}}
>
Shop
</NavbarBrand>
<NavbarToggler onClick={this.toggle}/>
<Collapse isOpen={this.state.isOpen} navbar>
<Nav className="ml-auto" navbar>
<UncontrolledDropdown nav inNavbar>
<DropdownToggle nav caret>
Cart: {cartLength} items
</DropdownToggle>
<DropdownMenu right style={{width: '300px'}}>
{cartItems}
<DropdownItem divider/>
</DropdownMenu>
</UncontrolledDropdown>
</Nav>
</Collapse>
</Navbar>
);
}
}
const mapStateToProps = state => ({
cart: state.cart.cart
});
export default withRouter(connect(mapStateToProps, {getCart})(NavbarMenu));
Based on the prints you gave, you are opening the item on a diferent window, because of that, the variables on the session in the window are not passed.
One solution you could use is to save pieces of your store that you will need later in the browser localStorage.
You can do that using this by using the Redux subscribe function.
A example could be:
localStorage.js
export const loadState = () => {
try {
const serializedState = localStorage.getItem('state');
if (serializedState === null) return undefined;
return JSON.parse(serializedState);
} catch (err) { return undefined; }
};
export const saveState = (state) => {
try {
const serializedState = JSON.stringify(state);
} catch (err) {
// errors
}
}
And in the redux store you can put:
import { loadState, saveState } from './localStorage';
const persistedState = loadState();
const store = createStore(persistedState);
store.subscribe(() => saveState(store.getState()));
Source: https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage
I solved my problem by adding this code
componentDidMount() {
this.props.getCart();
}
I'm trying to make an HOC that assign values to the parameter "color" in an icon component in React js.
I have 3 different colors. They come as follow :
pimary is #f7a014
secondary is #dd8b08
ternary is #56t00
So I can do :
<MyComponent color='primary' />
Here is my withColor HOC :
import React from 'react';
import propTypes from 'prop-types';
function mapColors(color) {
if (color === 'primary') {
return '#f8af39';
}
if (color === 'secondary') {
return '#fff';
}
if (color === 'ternary') {
return '#004c64';
}}
export const withColor = WrappedComponent => {
const NewComponent = ({ color, ...props }) => (
<WrappedComponent color={mapColors(color)} {...props} />
);
NewComponent.propTypes = {
color: propTypes.oneOf(['primary', 'secondary', 'ternary']),
};
return NewComponent;
};
export default withColor;
Here is my component with the icon in it :
import React from 'react';
import { RequestCloseModal } from 'HOC/connectModals';
import { compose } from 'redux';
import {
Collapse,
Modal,
ModalHeader,
ModalBody,
ModalFooter,
} from 'reactstrap';
import { ChevronDown, ChevronUp } from 'react-feather';
import PropTypes from 'prop-types';
import Industrie from 'components/Icons/Industrie';
import FinancerContainer from 'backoffice/containers/Financer/FinancerContainer';
import withContainer from 'HOC/withContainer';
import withColor from 'HOC/WithColor';
class FinancerMatchingDetails extends RequestCloseModal {
static propTypes = {
isOpen: PropTypes.bool,
};
constructor(props) {
super(props);
this.state = {
collapse: false,
};
}
toggle = () => {
this.setState({ collapse: !this.state.collapse });
};
render() {
const { isOpen } = this.props;
return (
<Modal size="xxl" isOpen={isOpen} toggle={this.close}>
<ModalHeader toggle={this.close}>
<div className="col-md-3">
<h4>Some text</h4>
<p>Some text</p>
</div>
</div>
</div>
<div>
<h4>Some text</h4>
{this.state.collapse ? (
<ChevronUp height={20} onClick={this.toggle} />
) : (
<ChevronDown height={20} onClick={this.toggle} />
)}
</div>
</ModalBody>
<ModalFooter>
<div className="container">
<Collapse isOpen={this.state.collapse}>
<div className="row">
<div className="col">
<MyIcon color="primary" />
</div>
</Collapse>
</div>
</ModalFooter>
</Modal>
);
}
}
export default compose(withContainer(FinancerContainer), withColor)(
FinancerMatchingDetails,
);
I'm new to HOC and can't make it to assign the color primary. Thanks for your help.
You defined HOC but never pass any components through it.
E.g.
import withColor from 'HOC/WithColor'; // your HOC
import SomeComponent from 'SomeComponent'; // component that you would like to use with HOC
then you need to define a component that is the result of HOC applied to your component:
const ColoredComponent = withColor(SomeComponent);
and then you can use it as you expected:
<ColoredComponent color="primary" />
I am working on a web application using React and bootstrap. When it comes to applying button onClick, I'm having a hard time to have page being redirect to another. If after a href, I cannot go the another page.
So would you please tell me is there any need for using react-navigation or other to navigate the page using Button onClick ?
import React, { Component } from 'react';
import { Button, Card, CardBody, CardGroup, Col, Container, Input, InputGroup, InputGroupAddon, InputGroupText, Row, NavLink } from 'reactstrap';
class LoginLayout extends Component {
render() {
return (
<div className="app flex-row align-items-center">
<Container>
...
<Row>
<Col xs="6">
<Button color="primary" className="px-4">
Login
</Button>
</Col>
<Col xs="6" className="text-right">
<Button color="link" className="px-0">Forgot password?</Button>
</Col>
</Row>
...
</Container>
</div>
);
}
}
update:
React Router v6:
import React from 'react';
import { useNavigate } from "react-router-dom";
function LoginLayout() {
let navigate = useNavigate();
const routeChange = () =>{
let path = `newPath`;
navigate(path);
}
return (
<div className="app flex-row align-items-center">
<Container>
...
<Button color="primary" className="px-4"
onClick={routeChange}
>
Login
</Button>
...
</Container>
</div>
);
}}
React Router v5 with hooks:
import React from 'react';
import { useHistory } from "react-router-dom";
function LoginLayout() {
const history = useHistory();
const routeChange = () =>{
let path = `newPath`;
history.push(path);
}
return (
<div className="app flex-row align-items-center">
<Container>
...
<Row>
<Col xs="6">
<Button color="primary" className="px-4"
onClick={routeChange}
>
Login
</Button>
</Col>
<Col xs="6" className="text-right">
<Button color="link" className="px-0">Forgot password?</Button>
</Col>
</Row>
...
</Container>
</div>
);
}
export default LoginLayout;
with React Router v5:
import { useHistory } from 'react-router-dom';
import { Button, Card, CardBody, CardGroup, Col, Container, Input, InputGroup, InputGroupAddon, InputGroupText, Row, NavLink } from 'reactstrap';
class LoginLayout extends Component {
routeChange=()=> {
let path = `newPath`;
let history = useHistory();
history.push(path);
}
render() {
return (
<div className="app flex-row align-items-center">
<Container>
...
<Row>
<Col xs="6">
<Button color="primary" className="px-4"
onClick={this.routeChange}
>
Login
</Button>
</Col>
<Col xs="6" className="text-right">
<Button color="link" className="px-0">Forgot password?</Button>
</Col>
</Row>
...
</Container>
</div>
);
}
}
export default LoginLayout;
with React Router v4:
import { withRouter } from 'react-router-dom';
import { Button, Card, CardBody, CardGroup, Col, Container, Input, InputGroup, InputGroupAddon, InputGroupText, Row, NavLink } from 'reactstrap';
class LoginLayout extends Component {
constuctor() {
this.routeChange = this.routeChange.bind(this);
}
routeChange() {
let path = `newPath`;
this.props.history.push(path);
}
render() {
return (
<div className="app flex-row align-items-center">
<Container>
...
<Row>
<Col xs="6">
<Button color="primary" className="px-4"
onClick={this.routeChange}
>
Login
</Button>
</Col>
<Col xs="6" className="text-right">
<Button color="link" className="px-0">Forgot password?</Button>
</Col>
</Row>
...
</Container>
</div>
);
}
}
export default withRouter(LoginLayout);
Don't use a button as a link. Instead, use a link styled as a button.
<Link to="/signup" className="btn btn-primary">Sign up</Link>
React Router v5.1.2:
import { useHistory } from 'react-router-dom';
const App = () => {
const history = useHistory()
<i className="icon list arrow left"
onClick={() => {
history.goBack()
}}></i>
}
This can be done very simply, you don't need to use a different function or library for it.
onClick={event => window.location.href='/your-href'}
I was trying to find a way with Redirect but failed. Redirecting onClick is simpler than we think. Just place the following basic JavaScript within your onClick function, no monkey business:
window.location.href="pagelink"
First, import it:
import { useHistory } from 'react-router-dom';
Then, in function or class:
const history = useHistory();
Finally, you put it in the onClick function:
<Button onClick={()=> history.push("/mypage")}>Click me!</Button>
A very simple way to do this is by the following:
onClick={this.fun.bind(this)}
and for the function:
fun() {
this.props.history.push("/Home");
}
finlay you need to import withRouter:
import { withRouter } from 'react-router-dom';
and export it as:
export default withRouter (comp_name);
useHistory() from react-router-dom can fix your problem
import React from 'react';
import { useHistory } from "react-router-dom";
function NavigationDemo() {
const history = useHistory();
const navigateTo = () => history.push('/componentURL');//eg.history.push('/login');
return (
<div>
<button onClick={navigateTo} type="button" />
</div>
);
}
export default NavigationDemo;
If all above methods fails use something like this:
import React, { Component } from 'react';
import { Redirect } from "react-router";
export default class Reedirect extends Component {
state = {
redirect: false
}
redirectHandler = () => {
this.setState({ redirect: true })
this.renderRedirect();
}
renderRedirect = () => {
if (this.state.redirect) {
return <Redirect to='/' />
}
}
render() {
return (
<>
<button onClick={this.redirectHandler}>click me</button>
{this.renderRedirect()}
</>
)
}
}
if you want to redirect to a route on a Click event.
Just do this
In Functional Component
props.history.push('/link')
In Class Component
this.props.history.push('/link')
Example:
<button onClick={()=>{props.history.push('/link')}} >Press</button>
Tested on:
react-router-dom: 5.2.0,
react: 16.12.0
If you already created a class to define the properties of your Button (If you have a button class created already), and you want to call it in another class and link it to another page through a button you created in this new class, just import your "Button" (or the name of your button class) and use the code below:
import React , {useState} from 'react';
import {Button} from '../Button';
function Iworkforbutton() {
const [button] = useState(true);
return (
<div className='button-class'>
{button && <Button onClick={()=> window.location.href='/yourPath'}
I am Button </Button>
</div>
)
}
export default Iworkforbutton
A simple click handler on the button, and setting window.location.hash will do the trick, assuming that your destination is also within the app.
You can listen to the hashchange event on window, parse the URL you get, call this.setState(), and you have your own simple router, no library needed.
class LoginLayout extends Component {
constuctor() {
this.handlePageChange = this.handlePageChange.bind(this);
this.handleRouteChange = this.handleRouteChange.bind(this);
this.state = { page_number: 0 }
}
handlePageChange() {
window.location.hash = "#/my/target/url";
}
handleRouteChange(event) {
const destination = event.newURL;
// check the URL string, or whatever other condition, to determine
// how to set internal state.
if (some_condition) {
this.setState({ page_number: 1 });
}
}
componentDidMount() {
window.addEventListener('hashchange', this.handleRouteChange, false);
}
render() {
// #TODO: check this.state.page_number and render the correct page.
return (
<div className="app flex-row align-items-center">
<Container>
...
<Row>
<Col xs="6">
<Button
color="primary"
className="px-4"
onClick={this.handlePageChange}
>
Login
</Button>
</Col>
<Col xs="6" className="text-right">
<Button color="link" className="px-0">Forgot password </Button>
</Col>
</Row>
...
</Container>
</div>
);
}
}
With React Router v5.1:
import {useHistory} from 'react-router-dom';
import React, {Component} from 'react';
import {Button} from 'reactstrap';
.....
.....
export class yourComponent extends Component {
.....
componentDidMount() {
let history = useHistory;
.......
}
render() {
return(
.....
.....
<Button className="fooBarClass" onClick={() => history.back()}>Back</Button>
)
}
}
I was also having the trouble to route to a different view using navlink.
My implementation was as follows and works perfectly;
<NavLink tag='li'>
<div
onClick={() =>
this.props.history.push('/admin/my- settings')
}
>
<DropdownItem className='nav-item'>
Settings
</DropdownItem>
</div>
</NavLink>
Wrap it with a div, assign the onClick handler to the div. Use the history object to push a new view.
Make sure to import {Link} from "react-router-dom";
And just hyperlink instead of using a function.
import {Link} from "react-router-dom";
<Button>
<Link to="/yourRoute">Route Name</Link>
</Button>