react button conditional rendering not working properly - javascript

working on a cart app in a udemy course - the problem is when the quantity gets bought it supposed to make the button disabled but its not working, only showing the add to cart button without disabling it when quantity are zero
data.countInStock seems not to be updating
import { Button } from 'react-bootstrap';
import { Card } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import React, { useContext } from 'react';
import Rating from './Rating';
import axios from 'axios';
import { Store } from '../Store';
function Product(props){
const {product} = props;
const {state , dispatch:ctxDispatch} = useContext(Store);
const {cart: {cartItems}} = state
const addToCartHandler = async (item )=>{
const existItem = cartItems.find((x)=> x._id === product._id);
const quantity = existItem ? existItem.quantity+1:1 ;
const {data} = await axios.get(`/api/products/${item._id}`);
if(data.countInStock < quantity){
window.alert('sorry product is out of stock')
return;
}
ctxDispatch({
type:'CART_ADD_ITEM'
, payload:{...item , quantity},
});
};
return(
<Card>
<Link to={`/product/${product.slug}`}>
<img src={product.image} className="card-img-top" alt={product.name} />
</Link>
<Card.Body>
<Link to={`/product/${product.slug}`}>
<Card.Title>{product.name}</Card.Title>
</Link>
<Rating rating={product.rating} numReviews={product.numReviews} />
<Card.Text>${product.price}</Card.Text>
{ product.countInStock === 0 ? (
<Button color="light" disabled={true} > Out of stock</Button>
):(
<Button onClick={() => addToCartHandler(product)}>Add to cart</Button>
)}
</Card.Body>
</Card>
)}
it's not showing the button out of stock when quantity gets used, What's wrong with the code?
full code: https://github.com/basir/mern-amazona/commit/12e565bf6e1859b963729eaba46a5352962fe9e1
full code with backend : https://github.com/basir/mern-amazona/tree/12e565bf6e1859b963729eaba46a5352962fe9e1

Maybe this could start you out. There's no need to make 2 buttons. You can just manipulate the state of the button using your logic
const isOutOfStock = product.countInStock === 0
const buttonText = isOutOfStock ? "Out of stock" : "Add to cart"
<Button color="light" disabled={isOutOfStock} onClick={() => addToCartHandler(product)}>{buttonText}</Button>

Related

How to toggle class in react, but one component at once(all with the same classes)

let me explain my situation.
I am building a MERN project to my portfolio and I am trying to make a button toggle between the name of an item and a inputfield. So when the user click the pen (edit), it will add a class with the displain:none; in the div with the text coming from the MongoDB data base to hide it and will remove it from the div with the input. I could manage to do it. BUT since the amount of items can inscrease, clicking in one of them cause the toggle in all of them.
It was ok until I send some useState as props to the component.
This is my code from the App.jsx
import React, {useState, useEffect} from "react";
import Axios from "axios";
import "./App.css";
import ListItem from "./components/ListItem";
function App() {
//here are the use states
const [foodName, setFoodName] = useState("");
const [days, setDays] = useState(0);
const [newFoodName, setNewFoodName] = useState("");
const [foodList, setFoodList] = useState([]);
//here is just the compunication with the DB of a form that I have above those components
useEffect(() => {
Axios.get("http://localhost:3001/read").then((response) => {
setFoodList(response.data);
});
}, []);
const addToList = () => {
Axios.post("http://localhost:3001/insert", {
foodName: foodName,
days: days,
});
};
const updateFood = (id) => {
Axios.put("http://localhost:3001/update", {
id: id,
newFoodName: newFoodName,
});
};
return (
<div className="App">
//Here it starts the app with the form and everything
<h1>CRUD app with MERN</h1>
<div className="container">
<h3 className="container__title">Favorite Food Database</h3>
<label>Food name:</label>
<input
type="text"
onChange={(event) => {
setFoodName(event.target.value);
}}
/>
<label>Days since you ate it:</label>
<input
type="number"
onChange={(event) => {
setDays(event.target.value);
}}
/>
<button onClick={addToList}>Add to list</button>
</div>
//Here the form finishes and now it starts the components I showed in the images.
<div className="listContainer">
<hr />
<h3 className="listContainer__title">Food List</h3>
{foodList.map((val, key) => {
return (
//This is the component and its props
<ListItem
val={val}
key={key}
functionUpdateFood={updateFood(val._id)}
newFoodName={newFoodName}
setNewFoodName={setNewFoodName}
/>
);
})}
</div>
</div>
);
}
export default App;
Now the component code:
import React from "react";
//Material UI Icon imports
import CancelIcon from "#mui/icons-material/Cancel";
import EditIcon from "#mui/icons-material/Edit";
//import CheckIcon from "#mui/icons-material/Check";
import CheckCircleIcon from "#mui/icons-material/CheckCircle";
//App starts here, I destructured the props
function ListItem({val, key, functionUpdateFood, newFoodName, setNewFoodName}) {
//const [foodList, setFoodList] = useState([]);
//Here I have the handleToggle function that will be used ahead.
const handleToggle = () => {
setNewFoodName(!newFoodName);
};
return (
<div
className="foodList__item"
key={key}>
<div className="foodList__item-group">
<h3
//As you can see, I toggle the classes with this conditional statement
//I use the same classes for all items I want to toggle with one click
//Here it will toggle the Food Name
className={
newFoodName
? "foodList__item-newName-delete"
: "foodList__name"
}>
{val.foodName}
</h3>
<div
className={
newFoodName
? "foodList__item-newName-group"
: "foodList__item-newName-delete"
}>
//Here is the input that will replace the FoodName
<input
type="text"
placeholder="The new food name..."
className="foodList__item-newName"
onChange={(event) => {
setNewFoodName(event.target.value);
}}
/>
//Here it will confirm the update and toggle back
//Didn't implement this yet
<div className="foodList__icons-confirm-group">
<CheckCircleIcon
className="foodList__icons-confirm"
onClick={functionUpdateFood}
/>
<small>Update?</small>
</div>
</div>
</div>
//here it will also desappear on the same toggle
<p
className={
newFoodName
? "foodList__item-newName-delete"
: "foodList__day"
}>
{val.daysSinceIAte} day(s) ago
</p>
<div
className={
newFoodName
? "foodList__item-newName-delete"
: "foodList__icons"
}>
//Here it will update, and it's the button that toggles
<EditIcon
className="foodList__icons-edit"
onClick={handleToggle}
/>
<CancelIcon className="foodList__icons-delete" />
</div>
</div>
);
}
export default ListItem;
I saw a solution that used different id's for each component. But this is dynamic, so if I have 1000 items on the data base, it would display all of them, so I can't add all this id's.
I am sorry for the very long explanation. It seems simple, but since I am starting, I spent the day on it + searched and tested several ways.
:|

How to use react router with Algolia search hits?

I'm using Algolia's react instant search and I want to know what code I can use that'll send me to a specific page when I click on a "hit" from the hits widget. I'm using Next.js.
Code:
import React from 'react';
import { useRef, useState, useEffect } from 'react';
import algoliasearch from 'algoliasearch/lite';
import { InstantSearch } from 'react-instantsearch-dom';
import { Index } from 'react-instantsearch-dom';
import { Configure } from 'react-instantsearch-dom';
import { Pagination } from 'react-instantsearch-dom';
const searchClient = algoliasearch(
'XXXXXXXXXX',
'XXXXXXXXXXXXXXXXXXXXXXXXXXX'
);
const Hit = ({ hit }) => <p>{hit.title}</p>;
import { connectSearchBox } from 'react-instantsearch-dom';
const SearchBox = ({ currentRefinement, isSearchStalled, refine }) => (
<form noValidate action="" role="search">
<div className="container flex justify-center items-center px-4 sm:px-6 lg:px-8 relative">
<input
type="search"
placeholder='Search Documentation'
value={currentRefinement}
onChange={event => refine(event.currentTarget.value)}
className="h-7 w-96 pr-8 pl-5 rounded z-0 hover:text-gray-500 outline-none border-b-2"
/>
<i className="fa fa-search text-gray-400 z-20 hover:text-gray-500"></i>
</div>
<button onClick={() => refine('')}>Reset query</button>
{isSearchStalled ? 'My search is stalled' : ''}
</form>
);
const CustomSearchBox = connectSearchBox(SearchBox);
import { connectHits } from 'react-instantsearch-dom';
const Hits = ({ hits }) => (
<table className="table-auto">
{hits.map(hit => (
<tbody>
<tr>
<td className="text-black font-bold" key={hit.objectID}>{hit.title}</td>
</tr>
</tbody>
))}
</table>
);
const CustomHits = connectHits(Hits);
import { QueryRuleCustomData } from 'react-instantsearch-dom';
function SearchApp({location, history}) {
const [showHits, setShowHits] = useState(false);
return (
<div>
<>
<InstantSearch
indexName="prod_Directory"
searchClient={searchClient}
>
<Index indexName="prod_Directory">
{/* Widgets */}
<div>
<CustomSearchBox onFocus={()=>setShowHits(true)} onBlur={()=>setShowHits(false)}/>
<CustomHits className="table-auto"/>
{/*
{showHits ? <CustomHits className="table-auto"/> : null}
*/}
</div>
</Index>
<Configure hitsPerPage={2} />
<QueryRuleCustomData
transformItems={items => {
const match = items.find(data => Boolean(data.redirect));
if (match && match.redirect) {
window.location.href = match.redirect;
}
return [];
}}
>
{() => null}
</QueryRuleCustomData>
</InstantSearch>
</>
</div>
)
}
export default SearchApp
I couldn't find anything about this in the Algolia docs. Again, I want to be able to click on one of my hits, and have it redirect or route me to a specific page.
It looks like you're using a custom Hits widget here rather than the out-of-the-box instantsearch.js widget (which is fine).
You're going to want to build you link here in the hit template:
const Hits = ({
hits
}) => ( <
table className = "table-auto" > {
hits.map(hit => ( <
tbody >
<
tr >
<
td className = "text-black font-bold"
key = {
hit.objectID
} > {
hit.title
} < /td> <
/tr>
<
/tbody>
))
} <
/table>
);
For instance if you store the URLs in the object records, you could do something like:
{
hit.title
}
More likely, you'll want to build onClick event using Link. Something like:
<Link
onClick={() => {
setIsOpen(false);
}}
to={`/product/${hit.objectID}`}
>
hit.title
</Link>
In either case, just make sure everything you need to build the link (URL, routing IDs, etc.) is embedded in the Algolia records, then just build your links within your hit template as you typically would for your application.
I found the answer:
import router, {useRouter} from "next/router";
import { connectHits } from 'react-instantsearch-dom';
const Hits = ({ hits }) => (
<table className="table-auto">
{hits.map(hit => (
<tbody>
<tr>
<td className="text-black font-bold" key={hit.objectID} onClick={() => router.push(hit.url)}>{hit.title}</td>
</tr>
</tbody>
))}
</table>
);
const CustomHits = connectHits(Hits);
In your search records (I used the Algolia index to make mine), you just need to code in a url in your JSON record and then use react router on the hit.url attribute!

Share State between two specific instances of the same react component React

Before y'all say global state(redux), I'd like to say one thing. I'm mapping through an array I fetched from my API. I receive images and map over them and render my Slider component. Every 2 sliders must share the same state. So, then if i move to the next slide in the first slider, then the second slider must also go to the next slide(but not any other slides). If I move to the next slide in the 5th slider, the 6th must also move to the next slide... so on.
Component where I map over slides:
<div className='image-grid'>
{screenshots.map((imagesByResolution, resIdx, screenshotResArr) => {
return imagesByResolution.map((img, scriptIdx, screenshotScriptsArr) => {
return <Slider slides={formattedSlides} />;
});
})}
</div>
Slider:
import Button from '#material-ui/core/Button';
import MobileStepper from '#material-ui/core/MobileStepper';
import { useTheme } from '#material-ui/core/styles';
import KeyboardArrowLeft from '#material-ui/icons/KeyboardArrowLeft';
import KeyboardArrowRight from '#material-ui/icons/KeyboardArrowRight';
import React from 'react';
import SwipeableViews from 'react-swipeable-views';
import { autoPlay } from 'react-swipeable-views-utils';
import { encodeImage } from '../services/images';
import useStyles from '../styles/slider';
const AutoPlaySwipeableViews = autoPlay(SwipeableViews);
export interface ISlide {
title: string;
img: ArrayBuffer;
}
interface Props {
slides: ISlide[];
}
export default function Slider(props: Props) {
console.log(props);
const { slides } = props;
const classes = useStyles();
const theme = useTheme();
const [activeSlide, setActiveSlide] = React.useState(0);
const maxSlides = slides.length;
const handleNext = () => {
setActiveSlide((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveSlide((prevActiveStep) => prevActiveStep - 1);
};
const handleSlideChange = (step: number) => {
setActiveSlide(step);
};
return (
<div className={classes.root}>
<div className={classes.header}>
<h4 className={classes.title}>{slides[activeSlide].title}</h4>
</div>
<AutoPlaySwipeableViews
axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={activeSlide}
onChangeIndex={handleSlideChange}
enableMouseEvents
>
{slides.map((slide, index) => (
<div key={index}>
{Math.abs(activeSlide - index) <= 2 ? (
<img className={classes.img} src={encodeImage(slide.img, 'image/png')} alt={slide.title} />
) : null}
</div>
))}
</AutoPlaySwipeableViews>
<MobileStepper
steps={maxSlides}
position='static'
variant='text'
activeStep={activeSlide}
nextButton={
<Button size='small' onClick={handleNext} disabled={activeSlide === maxSlides - 1}>
Next
{theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
</Button>
}
backButton={
<Button size='small' onClick={handleBack} disabled={activeSlide === 0}>
{theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
Back
</Button>
}
/>
</div>
);
}
If this is not possible using either some global state management library or plain ol' react state, what is the other alternative? Thanks in advance!
Pass a unique key prop to each instance of your component.
Credits: https://stackoverflow.com/a/65654818/9990676

Getting redirected to the details Page on Click

Am making a simple react js app that displays a list of companies from a Node js / express server in a table.
Each row when clicked shows a modal with some of the details about that company , the modal has two buttons one for closing and the other for seeing the full details of the company.
Am trying to redirect the user to an other page with the full details of the company when the full details button is clicked.
I know i can pass the id as props and then use axios to fetch the company details using that id from the endpoint ("localhost:5000/companies/id"),
But the redirecting to the new page part am not familiar with so i appreciate any help in regards of this or a better way to implement this functionality.
Here is the code i used to show case the table :
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import BootStrapTable from "react-bootstrap-table-next"
import paginationFactory from 'react-bootstrap-table2-paginator';
import {Modal , Button} from "react-bootstrap"
export default function DataTable (){
const [companies,setCompanies] = useState([]);
const [modalInfo,setModalInfo] = useState([]);
const [showModal,setShowModal] = useState(false);
const [show ,setShow] = useState(false);
const handleCLose = () => setShow(false)
const handleShow = () => setShow(true)
const getCompaniesData = async () =>{
try{
const data = await axios.get("http://localhost:5000/companies")
setCompanies(data.data)
}
catch(e){
console.log(e)
}
}
useEffect(()=>{
getCompaniesData();
},[])
const columns = [
{dataField:"id",text:"id"},
{dataField:"name",text:"name"},
]
const rowEvents = {
onClick : (e,row)=>{
console.log(row)
setModalInfo(row)
toggleTrueFalse()
}
}
const toggleTrueFalse = () =>{
setShowModal(handleShow);
}
const ModalContent = () =>{
return ( <Modal show={show} onHide={handleCLose}>
<Modal.Header closeButton>
<Modal.Title>
{modalInfo.name}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<h1 >Company Details :</h1>
<ul>
<ol>source_id : {modalInfo.source_id}</ol>
<ol>source_name : {modalInfo.source_name}</ol>
<ol>name : {modalInfo.name}</ol>
<ol>website : {modalInfo.website}</ol>
<ol>email : {modalInfo.email}</ol>
<ol>phone : {modalInfo.phone}</ol>
<ol>postal_code : {modalInfo.postal_code}</ol>
<ol>city : {modalInfo.city}</ol>
<ol>country : {modalInfo.country}</ol>
</ul>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleCLose}>Full Details</Button>
<Button className="btn btn-danger" variant="secondary" onClick={handleCLose}>Close</Button>
</Modal.Footer>
</Modal> )
}
return (
<div>
<h1 className="text-center">Share-work Data Table</h1>
<BootStrapTable
keyField="id"
data={companies}
columns={columns}
pagination={paginationFactory()}
rowEvents = {rowEvents}
/>
{show ? <ModalContent/> : null}
</div>
)
}
you could use react-router-dom history hooks:
First import useHistory from react-router-dom
const history = useHistory();
then you can replacce with a function onClick in your Button:
history.push('yourpath', {details:detail})
either history.replace should work:
history.replace('yourpath', {details:detail})
In the following page in your case will be details page you can access your details with the hook useLocation from react-router-dom:
First import useLocation from react-router-dom
location = useLocation();
to access it:
location.state.details
I hope this work for you

Type Error property undefined even though action fired off defining it

I am currently having a button redirect to the same location a button on a prior dashboard takes you to it is essentially a render of a graph and all of its notes and questions rendered. Now the specific snippet in the error works from my initial location which is denoted below in my screenshot that speculation button will take you to the rendered show route below it. However, the button that appears after you have made a new question and note do not they use the same snippet but one errors out I am also adding the snippet for the buttons.
*edit 1 adding show.js
show.js
// built-in(lifecycle) methods imported here
import React, { Component } from 'react'
// import components from local
import Graph from '../components/Graph'
import Notes from '../components/Notes'
import Questions from '../components/Questions'
//imbrl allows us to enable routing by updating url and rendering needed component listed in routeer
import { NavLink } from 'react-router-dom'
//bootstrap WIP
import Button from 'react-bootstrap/Button'
import Row from 'react-bootstrap/Row'
import Col from 'react-bootstrap/Col'
import Card from 'react-bootstrap/Card'
// access state from redux store
import { connect } from 'react-redux'
class Show extends Component {
render() {
// variables for objects
const graph = this.props.graphs.find(graph => { return graph.id === parseInt(this.props.match.params.id)})
const notes = this.props.notes.filter(note => note.graph.id === graph.id)
const questions = this.props.questions.filter(question => question.graph.id === graph.id)
// if graph exists it loads all corresponding notes and questions with it
if (graph) {
return (
<Row>
<Col md={3}>
<Notes graph={graph} notes={notes} />
</Col>
<Col md={6} >
<Card>
<Graph graph={graph}/>
</Card>
<NavLink
to={`/graphs/${graph.id}/interact`}>
<Button>Interact</Button>
</NavLink>
</Col>
<Col md={3}>
<Questions graph={graph} questions={questions} />
</Col>
</Row>
)
} else {
return (
<div>
<NavLink
style={{marginRight: '10px'}}
to="/">
<Button variant="dark" size="lg" block>Add Data to get started</Button>
</NavLink>
</div>
)
}
}
}
// this will need access to the objects
const mapStateToProps = state => {
return {
graphs: state.graphs,
notes: state.notes,
questions: state.questions
}
}
export default connect (mapStateToProps)(Show)
graphinput.js
import React, { Component } from 'react'
// import actions future?
import { addNote } from '../actions/addSpeculations'
import { addQuestion} from '../actions/addSpeculations'
// browser url routing render component as needed
import { NavLink } from 'react-router-dom'
// import local components
import Note from '../components/Note'
import Question from '../components/Question'
// bootstrap styling
import Button from 'react-bootstrap/Button'
import Form from 'react-bootstrap/Form'
// allows access to redux store this is a stateful component
import { connect } from 'react-redux'
class GraphInput extends Component {
// initial state placejolders
state ={
note: {
content: ""
},
question: {
content: ""
},
visible: false,
view: false
}
// state will be updated everytime the form value changes
handleChange = (event) => {
this.setState({
[event.target.name]: {content: event.target.value, graph_id: this.props.graph_id}
})
}
// visible key will show as true to confirm before calling fetch.
handleSubmit = (event) => {
event.preventDefault()
this.setState({visible: true})
}
handleSave = () => {
this.props.addNote(this.state.note)
this.props.addQuestion(this.state.question)
this.setState({
note: {content: ""},
question: {content: ""},
visible: false,
view: true
})
}
// if user cancels submission, state should reset to initial values
handleCancel = () => {
this.setState({
note: {content: ""},
question: {content: ""},
visible: false,
view: false
})
}
render () {
// Need to check current state to base what return should be used for what user interaction has done.
/*const validated= this.state.note.content.length > 20 && this.state.note.content.length > 20*/
if (this.state.visible === false && this.state.view === false){
/// render form and graph
return (
<div>
<h3> Add your Observations or Speculation below. </h3>
<Form onSubmit={event => this.handleSubmit(event)} >
<Form.Group>
<Form.Control size="lg" type="text" name="note" placeholder="Make an observation." value={this.state.note.content} onChange={event => this.handleChange(event)} />
</Form.Group>
<Form.Group>
<Form.Control size="lg" type="text" name="question" placeholder="Ask a question" value={this.state.question.content} onChange={event => this.handleChange(event)} />
</Form.Group>
<Button type="submit" >Add</Button>
</Form>
</div>
)
} else if (this.state.visible === true && this.state.view === false) {
/// rwendering draft to confirm submission
return (
<div>
<Note note={this.state.note} />
<Question question={this.state.question} />
<Button type="submit" onClick={this.handleSave}> Save Speculation to Database</Button>
<Button type="submit" variant="danger" onClick={this.handleCancel}>Cancel</Button>
</div>
)
} else if (this.state.view === true) {
// after saving, user can now navigate to view all
return (
<NavLink
style={{ marginRight: '10px'}, {backgroundColor: 'transparent'}}
to={`/graphs/${this.props.graph_id}/speculations`}
graph={this.props.graph}>
<Button size="lg" block>View All Speculation for This Graph</Button>
</NavLink>
)
}
}
}
// only state needed is graph speculations for specific graph
const mapStateToProps = (state) => {
return {
graph: state.graph
}
}
// Dispatch to props for Notes and Questions
export default connect(mapStateToProps, {addNote, addQuestion})(GraphInput)
Broken Button
<NavLink
style={{ marginRight: '10px'}, {backgroundColor: 'transparent'}}
to={`/graphs/${this.props.graph_id}/speculations`}
graph={this.props.graph}>
<Button size="lg" block>View All Speculation for This Graph</Button>
</NavLink>
Working Button
<NavLink
style={{ marginRight: '10px'}}
to={`/graphs/${this.props.graph.id}/speculations`}
url={this.props.graph.screenshot_url} >
<Button variant="success" >
Speculations
</Button>
</NavLink>
This is the button that works
This is where the button should take you
This button does not go to the show with that specific graph's notes and questions. (Both are using the same snippet to identify graph and its id or are supposed to)

Categories