passing callback function to build cart functionality in React - javascript

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

Related

Pass props to a component from another component, React Router v4

I'm trying to pass props to a component from another component with React Router v4, but I'm not getting the result I want.
This is how I'm trying to pass props:
render() {
const { selectedLanguage, repos, error, dashboard } = this.state
return (
<React.Fragment>
<LanguagesNav
selected={selectedLanguage}
onUpdateLanguage={this.updateLanguage}
/>
{error && <p>{error}</p>}
{repos && <LoginForm repos={repos} selected={selectedLanguage} dashboard={dashboard} onUpdateLogin={this.updateLogin} />}
<Route
path='/dashboard'
render={(props) => (
<Dashboard {...props}
repos={repos}
selected={selectedLanguage}
/>
)}
/>
</React.Fragment>
The whole component I want to send props from:
Login.js
import React from 'react'
import PropTypes from 'prop-types'
import languagesdata from '../languagesdata.json'
import { fetchLanguageRepos } from '../utils/api'
import Dashboard from './Dashboard'
import {BrowserRouter as Router, Route } from 'react-router-dom'
import { Link } from 'react-router-dom'
function LanguagesNav ({ selected, onUpdateLanguage}) {
const languages = ['EU', 'ES', 'EN']
return (
<div >
<h1 className='center-text header-lg'>
GAUR 2.0
</h1>
<ul className='flex-center'>
{languages.map((language) => (
<li key={language}>
<button
className='btn-clear nav-link'
style={language === selected ? { color: 'rgb(187, 46, 31)' } : null }
onClick={() => onUpdateLanguage(language)}>
{language}
</button>
</li>
))}
</ul>
</div>
)
}
LanguagesNav.propTypes = {
selected: PropTypes.string.isRequired,
onUpdateLanguage: PropTypes.func.isRequired
}
function LoginForm ({ repos, selected, dashboard, onUpdateLogin }) {
var language = {}
switch (selected) {
case "EU":
selected = "EU";
language = repos[0].terms;
break;
case "ES":
selected = "ES";
language = repos[1].terms;
break;
case "EN":
selected = "EN";
language = repos[2].terms;
break;
}
return (
<form className='column player'>
<div className='row player-inputs'>
<input
type='text'
id='username'
className='input-light'
placeholder={language.username}
autoComplete='off'
/>
</div>
<div className='row player-inputs'>
<input
type='password'
id='password'
className='input-light'
placeholder={language.password}
autoComplete='off'
/>
</div>
<div className='row player-inputs'>
<Link
className='btn dark-btn'
to={{
pathname: '/dashboard',
}}
>
{language.login}
</Link>
</div>
</form>
)
}
LoginForm.propTypes = {
repos: PropTypes.array.isRequired
}
export default class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null,
dashboard: false
}
this.updateLanguage = this.updateLanguage.bind(this)
}
componentDidMount () {
this.updateLanguage(this.state.selectedLanguage)
}
updateLanguage (selectedLanguage) {
this.setState({
selectedLanguage,
error: null
})
fetchLanguageRepos(selectedLanguage)
.then((repos) => this.setState({
repos,
error: null,
}))
.catch(() => {
console.warn('Error fetching repos: ', error)
this.setState({
error: 'There was an error fetching the repositories.'
})
})
}
render() {
const { selectedLanguage, repos, error, dashboard } = this.state
return (
<React.Fragment>
<LanguagesNav
selected={selectedLanguage}
onUpdateLanguage={this.updateLanguage}
/>
{error && <p>{error}</p>}
{repos && <LoginForm repos={repos} selected={selectedLanguage} dashboard={dashboard} onUpdateLogin={this.updateLogin} />}
<Route
path='/dashboard'
render={(props) => (
<Dashboard {...props}
repos={repos}
selected={selectedLanguage}
/>
)}
/>
</React.Fragment>
)
}
}
And this is the component I want to send props to:
Dashboard.js
import React from 'react'
import PropTypes from 'prop-types'
import Card from './Card'
import Loading from './Loading'
import { fetchLanguageRepos } from '../utils/api'
import Profile from './Profile'
import { Link } from 'react-router-dom'
function ReposGrid ({ repos, selected, profile, onUpdateProfile }) {
var language = {}
switch (selected) {
case "EU":
selected = "EU";
language = repos[0].terms;
break;
case "ES":
selected = "ES";
language = repos[1].terms;
break;
case "EN":
selected = "EN";
language = repos[2].terms;
break;
}
return (
<ul className='grid space-around'>
<li key={language.studyplan}>
<button
onClick={() => {onUpdateProfile(`${profile}`)}}
>
<Card
header={language.studyplan}
>
</Card>
</button>
</li>
<li key={language.careers}>
<Link to='/'>
<Card
header={language.careers}
>
</Card>
</Link>
</li>
<li key={language.census}>
<Card
header={language.census}
>
</Card>
</li>
<li key={language.sms}>
<Card
header={language.sms}
>
</Card>
</li>
<li key={language.tuitions}>
<Card
header={language.tuitions}
>
</Card>
</li>
<li key={language.surveys}>
<Card
header={language.surveys}
>
</Card>
</li>
<li key={language.titles}>
<Card
header={language.titles}
>
</Card>
</li>
<li key={language.exams}>
<Card
header={language.exams}
>
</Card>
</li>
<li key={language.record}>
<Card
header={language.record}
>
</Card>
</li>
</ul>
)
}
export default class Dashboard extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null,
loading: true,
profile: false
}
this.updateProfile = this.updateProfile.bind(this)
}
componentDidMount () {
var { repos, selectedLanguage } = this.props
this.setState({
selectedLanguage,
repos,
error: null,
loading: false,
profile: false
})
}
updateProfile (profile) {
this.setState({
error: null,
profile: true
})
}
render() {
const { selectedLanguage, repos, error } = this.props
const { profile } = this.state
if (profile === true) {
return (
<React.Fragment>
{repos && <Profile repos={repos} selectedLanguage={selectedLanguage} />}
</React.Fragment>
)
}
return (
<React.Fragment>
{repos && <ReposGrid repos={repos} selected={selectedLanguage} profile={profile} onUpdateProfile={this.updateProfile} />}
</React.Fragment>
)
}
}
I should be getting something like this:
Instead, I'm getting this:
Just to give more information, this is my index.js where I have set a route for Login.js:
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import Login from './components/Login'
import Dashboard from './components/Dashboard'
import Nav from './components/Nav'
import {BrowserRouter as Router, Route } from 'react-router-dom'
class App extends React.Component {
render() {
return (
<Router>
<div className='container'>
<Nav />
<Route exact path='/' component={Login} />
</div>
</Router>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
)
I've tried setting the route in index.js, but it didn't work either. What's the correct way to do it?

Reactjs - Issue Sending Dispatch to Reducer?

I am working on putting together a webpage with routing and I am having trouble getting redux to work. My goal is to send a GET response to the reducer but just to test the setup right now my goal is to send true. I can retrieve data from the redux store but I can't seem to send it and I am unsure where I might be going wrong. Here is what is supposed to happen:
Auth is checked in login or signup
App.js is wrapped in a provider
User can go to Cart.js and by clicking a button dispatch the value true
The can navigate to Menu.js and should be able to console.log the new value from the reducer
My problem: I can't seem to actually dispatch the true value. Nothing breaks but when I go to the Menu page, the console log shows the initial state of the reducer.
This has worked for me before in React Native. I'm wondering if I should be setting this up differently? or if authentication is messing things up?
Below is a sample of my code:
App.js
import React, { Component } from 'react';
import {
Route,
BrowserRouter as Router,
Switch,
Redirect,
} from "react-router-dom";
import Home from './pages/Home';
import Signup from './pages/Signup';
import Login from './pages/Login';
import Menus from './pages/Menus';
import Carts from './pages/Carts';
import Orders from './pages/Orders';
import Land from './pages/Land';
import { auth } from './services/firebase';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import ourReducer from './store/reducer';
const store = createStore(ourReducer);
global.api = 'https://pitapal.metis-data.site'
//global.api = 'http://localhost:3008';
function PrivateRoute({ component: Component, authenticated, ...rest }) {
return (
<Route
{...rest}
render={(props) => authenticated === true
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />}
/>
)
}
function PublicRoute({ component: Component, authenticated, ...rest }) {
return (
<Route
{...rest}
render={(props) => authenticated === false
? <Component {...props} />
: <Redirect to='/home' />}
/>
)
}
class App extends Component {
constructor() {
super();
this.state = {
authenticated: false,
loading: true,
};
}
componentDidMount() {
auth().onAuthStateChanged((user) => {
if (user) {
this.setState({
authenticated: true,
loading: false,
});
} else {
this.setState({
authenticated: false,
loading: false,
});
}
})
}
render() {
return this.state.loading === true ? <h2>Loading...</h2> : (
<Provider store={ store }>
<Router>
<Switch>
<Route exact path="/" component={Signup}></Route>
<PrivateRoute path="/home" authenticated={this.state.authenticated} component={Home}></PrivateRoute>
<PrivateRoute path="/menus" authenticated={this.state.authenticated} component={Menus}></PrivateRoute>
<PrivateRoute path="/carts" authenticated={this.state.authenticated} component={Carts}></PrivateRoute>
<PrivateRoute path="/order" authenticated={this.state.authenticated} component={Orders}></PrivateRoute>
<PublicRoute path="/signup" authenticated={this.state.authenticated} component={Signup}></PublicRoute>
<PublicRoute path="/login" authenticated={this.state.authenticated} component={Login}></PublicRoute>
</Switch>
</Router>
</Provider>
);
}
}
export default App;
Reducer.js
import { combineReducers } from 'redux';
const INITIAL_STATE = {
carts: 'nothing'
};
const ourReducer = (state = INITIAL_STATE, action) => {
const newState = { ...state };
switch (action.type) {
case "CARTS":
return {
...state,
carts: action.value
}
break;
}
return newState;
};
export default combineReducers({
reducer: ourReducer,
});
Carts.js
class Carts extends Component {
render() {
return (
<Container>
<Button onClick={()=>this.props.setCart(true)}>sendToRedux</Button>
</Container>
);
}
}
const mapStateToProps = (state) => {
const { reducer } = state
return { reducer }
};
const mapDispachToProps = dispatch => {
return {
setCart: (y) => dispatch({ type: "CARTS", value: y })
};
}
export default connect(mapStateToProps,
mapDispachToProps
)(Carts);
Menu.js
import React, { Component } from 'react';
import Header from "../components/Header";
import MenuItem from '../components/MenuItem';
import classes from './menus.module.css'
import { auth, db } from "../services/firebase";
import { Container, Col, Row } from 'react-bootstrap';
import { connect } from 'react-redux';
class Menu extends Component {
render() {
console.log('my carts data:', this.props.reducer.carts);
}
return (
<Container>
welcome to menu
</Container>
);
}
}
const mapStateToProps = (state) => {
const { reducer } = state
return { reducer }
};
export default connect(mapStateToProps)(Menu);
EDIT:
here is my screenshot from Redux Devtools, this means the dispatch is definitely being sent correct?
So it seems the issue is that when I navigate a page using my header component, state gets reloaded, the entire app is relaoded. Wondering if someone knows what it might be. Below is my header component:
Header.js
import { Link } from 'react-router-dom';
import { auth } from '../services/firebase';
//import "./header.css";
import React, { Component } from 'react'
import { Navbar, Nav, NavItem, NavDropdown, Form, FormControl, Button } from 'react-bootstrap'
// import {Link} from 'react-router-dom'
//import classes from './navbar.module.css';
import 'bootstrap/dist/css/bootstrap.min.css';
class Header extends Component {
render() {
return (
<div>
{auth().currentUser
?
<Navbar className="fixed-top" collapseOnSelect expand="lg" style={{ backgroundColor: '#485671' }} variant="dark">
<Navbar.Brand href="#home">PitaPal</Navbar.Brand>
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="mr-auto">
<Nav.Link href="/login">Home</Nav.Link>
<Nav.Link href="/menus">Manage my Menus</Nav.Link>
</Nav>
<Nav>
<Button onClick={() => auth().signOut()} variant="outline-success">Sign Out</Button>
</Nav>
</Navbar.Collapse>
</Navbar>
:
<Navbar className="fixed-top" collapseOnSelect expand="lg" style={{ backgroundColor: '#485671' }} variant="dark">
<Navbar.Brand href="#home">PitaPal</Navbar.Brand>
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="mr-auto">
<Nav.Link href="/login">Login</Nav.Link>
<Nav.Link href="/signup">Sign Up</Nav.Link>
</Nav>
<Nav>
<Nav.Link href="contact">Contact Us</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
}
</div>
)
}
}
export default Header;
Issue
Your Header is using plain anchor tags to issue navigation to your route pages which has the side-effect of also doing a page load.
Solution
Use the react-router-dom Link component for linking.
Either specify the as prop of the Nav.Link or use Link
<Nav.Link as={Link} to="/login">Home</Nav.Link>
or
<Link to="/login">Home</Link>
class Header extends Component {
render() {
return (
<div>
{auth().currentUser
?
<Navbar className="fixed-top" collapseOnSelect expand="lg" style={{ backgroundColor: '#485671' }} variant="dark">
<Navbar.Brand href="#home">PitaPal</Navbar.Brand>
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="mr-auto">
<Link to="/login">Home</Link>
<Link to="/menus">Manage my Menus</Link>
</Nav>
<Nav>
<Button onClick={() => auth().signOut()} variant="outline-success">Sign Out</Button>
</Nav>
</Navbar.Collapse>
</Navbar>
:
<Navbar className="fixed-top" collapseOnSelect expand="lg" style={{ backgroundColor: '#485671' }} variant="dark">
<Navbar.Brand href="#home">PitaPal</Navbar.Brand>
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="mr-auto">
<Link to="/login">Login</Link>
<Link to="/signup">Sign Up</Link>
</Nav>
<Nav>
<Link to="contact">Contact Us</Link>
</Nav>
</Navbar.Collapse>
</Navbar>
}
</div>
)
}
}

React & React Router, Unable to call the parent function

I am developing a React app (with react-router-dom) and trying to call a function defined in the parent component App. The parent component has been defined like so
import React, { Component, Fragment } from 'react';
import { Link, NavLink } from 'react-router-dom';
import Routes from './components/routes';
import { withRouter } from 'react-router';
import { Auth } from "aws-amplify";
import Login from './components/Login';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.userHasAuthenticated = this.userHasAuthenticated.bind(this);
this.state = {
isAuthenticated: false
}
}
userHasAuthenticated = (value) => {
this.setState({ isAuthenticated: value });
}
handleLogout = async event => {
await Auth.signOut();
this.userHasAuthenticated(false);
this.props.history.push("/");
}
async componentDidMount() {
try {
await Auth.currentSession();
this.userHasAuthenticated(true);
this.props.history.push("/chat");
} catch(e) {
if (e !== 'No current user') {
alert(e);
}
}
}
render() {
return (
<Fragment>
<div className="navbar navbar-expand-lg navbar-light bg-light">
<Link to="/" className="navbar-brand" href="#"><h1>Sample App</h1></Link>
<div className="collapse navbar-collapse" id="navbarNav">
<ul className="navbar-nav">
{this.state.isAuthenticated ?
<Fragment>
<li className="nav-item">
<NavLink to="/chat" className="nav-link">Chat</NavLink>
</li>
<li className="nav-item">
<NavLink to="/" className="nav-link" onClick={this.handleLogout}>Logout</NavLink>
</li>
</Fragment> :
<Fragment>
<li className="nav-item">
<NavLink to="/" className="nav-link">Login</NavLink>
</li>
<li className="nav-item">
<NavLink to="/Signup" className="nav-link">Signup</NavLink>
</li>
</Fragment>
}
</ul>
</div>
</div>
<Routes userHasAuthenticated= { this.userHasAuthenticated } isAuthenticated = { this.state.isAuthenticated }/>
</Fragment>
);
}
}
export default withRouter(App);
The login component is supposed to authenticate the user, update the state (using the hasUserAuthenticated function defined in the parent component) and redirect the user to another page.
import React, { Component } from "react";
import { FormGroup, FormControl, FormLabel, Button } from "react-bootstrap";
import { Auth } from "aws-amplify";
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: ""
};
}
validateForm() {
return this.state.email.length > 0 && this.state.password.length>0;
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}
handleSubmit = async event => {
event.preventDefault();
try {
await Auth.signIn(this.state.email, this.state.password);
this.userHasAuthenticated(true);
this.props.history.push("/chat");
} catch (e) {
alert(e.message);
}
}
render() {
return (
<div className="Home">
<div className="col-md-4">
<form onSubmit={this.handleSubmit}>
<FormGroup controlId="email">
<FormLabel>Email</FormLabel>
<FormControl
autoFocus
type="email"
value={this.state.email}
onChange={this.handleChange}
/>
</FormGroup>
<FormGroup controlId="password" >
<FormLabel>Password</FormLabel>
<FormControl
value={this.state.password}
onChange={this.handleChange}
type="password"
/>
</FormGroup>
<Button type="submit">
Login
</Button>
</form>
</div>
</div>
);
}
}
My Routes component looks like below:
import React from 'react';
import Signup from './Signup';
import Login from './Login';
import NotFound from './NotFound';
import chat from './chat';
import { Route, Switch } from "react-router-dom";
export default ( { childProps } ) =>
<Switch>
<Route exact path ="/" component={Login} props={childProps}/>
<Route exact path ="/Signup" component={Signup} props={childProps}/>
<Route exact path ="/chat" component={chat} props={childProps}/>
<Route component={NotFound} />
</Switch>;
However, the app throws an error saying this.userHasAuthenticated is not a function. What am I doing wrong? Any help would be welcome.
In Routes.js you need these changes
import React from 'react';
import Signup from './Signup';
import Login from './Login';
import NotFound from './NotFound';
import Chat from './chat';
import { Route, Switch } from "react-router-dom";
export default parentProps =>
<Switch>
<Route exact path ="/" render={(props => <Login {...props} {...parentProps} />) />
<Route exact path ="/Signup" render={(props => <Signup {...props} {...parentProps} />) />
<Route exact path ="/chat" render={(props => <Chat {...props} {...parentProps} />) />
<Route component={NotFound} />
</Switch>;
And also in Login or any child component use this.props.userHasAuthenticated(true); instead of this.userHasAuthenticated(true);

redux don't show props on another page

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();
}

React router renders component after a refresh

This is a strange issue, but when I try to do a redirect using a link, nothing happens, just the URL changes. But when I refresh the browser the component gets rendered. What am I doing wrong?
My nav.js
import React from 'react';
import {Navbar, Nav, NavItem, Modal, Button, FormControl} from 'react-bootstrap';
import {BrowserRouter, Link, Route, Switch} from 'react-router-dom';
import {auth} from '../firebase';
import Questions from './questions';
import {About} from './about';
import {Home} from './home';
import {LinkContainer} from 'react-router-bootstrap';
import Question from "./question";
class Navigation extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
this.openLogin = this.openLogin.bind(this);
this.handleClose = this.handleClose.bind(this);
}
componentDidMount() {
auth.onAuthStateChanged((user) => {
if (user) {
this.setState({
user: user
}, function () {
this.props.checkUserState(this.state.user)
});
}
});
}
logout() {
auth.signOut()
.then(() => {
this.setState({
user: null
}, function () {
this.props.checkUserState(this.state.user)
});
});
}
login() {
var email = document.getElementById('email').value;
var password = document.getElementById('password').value;
auth.signInWithEmailAndPassword(email, password)
.then(result => {
const user = result.user;
this.setState({
user: user,
},
function () {
this.props.checkUserState(this.state.user)
});
document.getElementById('close').click();
document.getElementById('questions').click();
}
).catch(e => console.log(e));
}
openLogin() {
this.setState({show: true});
}
handleClose() {
this.setState({show: false});
}
render() {
return (
<React.Fragment>
<BrowserRouter>
<React.Fragment>
<Navbar>
<Navbar.Header>
<Navbar.Brand>
<Link id='home' to="/">UczIchApp</Link>
</Navbar.Brand>
</Navbar.Header>
<Nav>
<LinkContainer id='about' to='/about'>
<NavItem>O nas</NavItem>
</LinkContainer>
{
this.state.user ?
<React.Fragment>
<LinkContainer id="questions" to='/questions'>
<NavItem>Zadania</NavItem>
</LinkContainer>
<NavItem onClick={this.logout}>Wyloguj się</NavItem>
</React.Fragment>
:
<NavItem onClick={this.openLogin}>Zaloguj się</NavItem>
}
</Nav>
</Navbar>
<Switch>
<Route exact path="/about" component={About}/>
<Route exact path="/questions" component={Questions}/>
<Route exact path="/" component={Home}/>
<Route path='/question/:id' component={Question}/>
</Switch>
</React.Fragment>
</BrowserRouter>
<Modal
show={this.state.show}
onHide={this.handleClose}>
<Modal.Header
closeButton>
<Modal.Title> Modal
heading </Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<FormControl
id="email"
type="email"
label="Email address"
placeholder="Enter email"/>
<FormControl id="password" label="Password" type="password"/>
<Button onClick={this.login}>Zaloguj</Button>
</form>
</Modal.Body>
<Modal.Footer>
<Button id="close" onClick={this.handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</React.Fragment>
)
}
}
export default Navigation;
My Questions.js
import React from 'react';
import firebase from 'firebase';
// import {Button} from 'react-bootstrap';
import {BrowserRouter as Router, Link, Route} from 'react-router-dom';
import Question from './question';
class Questions extends React.Component {
constructor(props) {
super(props);
this.state = {
currentItem: '',
username: '',
questions: []
};
}
componentDidMount() {
const questionsRef = firebase.database().ref('Works').orderByChild('available').equalTo(true).limitToFirst(10);
questionsRef.on('value', (snapshot) => {
let questions = snapshot.val();
let newState = [];
for (let question in questions) {
newState.push({
id: question,
category: questions[question].category,
level: questions[question].level,
pointAmount: questions[question].pointAmount,
pointBoost: questions[question].pointBoost,
photoURL: questions[question].photoURL,
});
}
this.setState({
questions: newState
});
});
}
render() {
return (
<section id='loopContainer' className='display-question'>
<div className='wrapper'>
<ul style={{listStyleType: 'none'}}>
{
this.state.questions.map(question => {
return (
<li key={question.id}>
<h3>Kategoria: {question.category}</h3>
<p>Poziom: {question.level}</p>
<p>Punkty: {question.pointAmount + question.pointBoost}</p>
<img alt='' style={{width: '20%'}} src={question.photoURL}/>
<Router>
<React.Fragment>
<Link to={`/question/${question.id}`}
style={{display: 'block', margin: 'auto'}}>Rozwiaz to zadanie
</Link>
</React.Fragment>
</Router>
</li>
)
})
}
</ul>
</div>
</section>
)
}
}
export default Questions;
My Question.js
import React from 'react';
import firebase from 'firebase';
export default class Question extends React.Component {
constructor(p) {
super(p);
this.state = {
currentItem: '',
username: '',
questions: []
};
}
componentDidMount() {
const questionsRef = firebase.database().ref('Works').orderByChild('firebaseKey').equalTo(this.props.match.params.id);
questionsRef.on('value', (snapshot) => {
let questions = snapshot.val();
let newState = [];
for (let question in questions) {
newState.push({
id: question,
category: questions[question].category,
level: questions[question].level,
pointAmount: questions[question].pointAmount,
pointBoost: questions[question].pointBoost,
photoURL: questions[question].photoURL,
});
}
console.log(newState);
this.setState({
questions: newState
});
});
}
render() {
return (
this.state.questions.map(question => {
return (
<section key={question.id} className='display-question'>
<div className='wrapper'>
<h3>Kategoria: {question.category}</h3>
<p>Poziom: {question.level}</p>
<p>Punkty: {question.pointAmount + question.pointBoost}</p>
<img alt='' style={{width: '80%'}} src={question.photoURL}/>
</div>
</section>
)
})
)
}
}
What I'm trying to do. I'm trying to get a Question component rendered, when a link rendered with the Questions component is clicked. Based on the id, the Question component will differ.
This is the Questions component example (It's a list):
When I click the link under an image it changes the url, like this: http://localhost:3000/question/-LDvDwsIrf_SCwSinpMa, but nothing else happens. I have to manually refresh the page to get the component.
This is the single Question component being rendered
What am I missing?
You define multiple Router instances. However, there should be exactly one Router instance. The Router is typically placed pretty much "top-level" within your App / Main / Root component.

Categories