Why can't I toggle this button on click - javascript

I am trying to achieve a behavior on click. What I want is to have the button show “Click to close” when clicked, and then once you click again - revert back to its initial state (showing ‘Easy Riddles’).
Here is a snippet of my code:
import React, { useState } from "react";
import { Accordion, Card, Button, Container, Row, Col } from "react-bootstrap";
const Riddles = (props) => {
const levelStatus = {
easy: "Easy Riddles",
medium: "Intermediate Riddles",
hard: "Hard Riddles",
};
const collapseButton = "Click to close";
const [close, setClose] = useState({
easy: false,
medium: false,
hard: false,
});
// Handle click
const handleClick = (e) => {
setClose({
close.easy: true
});
};
return (
<>
<div className="riddlesection">
<Container>
<Row>
<Col className="riddlegrid" xs={12} sm={12} md={4}>
<Accordion>
<Card id="easy">
<Card.Header>
<Accordion.Toggle
id="easy"
onClick={handleClick}
value="Easy Riddles"
as={Button}
variant="link"
eventKey="0"
>
{close.easy ? levelStatus.easy : collapseButton}
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>
<Row>
<Col xs={6} sm={6} md={6}>
Countdown
</Col>
<Col className="resetlink" xs={6} sm={6} md={6}>
Switch
</Col>
</Row>
<div>Hello! I'm the body</div>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</Col>
</Row>
</Container>
</div>
</>
);
};
What can I do to achieve differently the behavior that I want?

you need to update the state as below
const handleClick = (e) => {
setClose(prevCloseState => {
...prevCloseState,
easy: true
})
};

Related

preventing useState to render the initial value

If you look to my JSX I am trying to create sizes from the product object the problem is when I loop throguh the product.size.map() it threw an error that says product is undefined.
How to solve this?
update
I was able to do some workaround but I am sure it is not considered as a best practice.
import React , {useState , useEffect} from 'react'
import { Row, Col, Image, ListGroup, Card, Button } from 'react-bootstrap'
import {Link} from 'react-router-dom'
import Rating from '../components/Rating'
import axios from 'axios'
const ProductScreen = ({match}) => {
const [product , setProduct] = useState({})
useEffect(()=>{
let componentMounted = true
const fetchProducts = async ()=>{
const {data} = await axios.get(`http://172.30.246.130:5000/api/products/${match.params.id}`)
if(componentMounted){
setProduct(data)
}
}
fetchProducts()
return () => {
componentMounted = false
}
},[match])
if(Object.keys(product).length===0) return null // this is the workaround I have done what do you think?
else{
return (
<>
<Link className='btn btn-light my-3' to='/'>
Go Back
</Link>
<Row>
<Col md={6}>
<Image src={product.image} alt={product.name} />
</Col>
<Col md={3}>
<ListGroup variant='flush'>
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating rating={product.rating} reviews={product.numReviews}/>
</ListGroup.Item>
<ListGroup.Item>
<strong> Price: ${product.price}</strong>
</ListGroup.Item>
<ListGroup.Item>
<strong>Description :</strong> {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup>
<ListGroup.Item>
<Row>
<Col>
Price :
</Col>
<Col>
<strong>${product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Status :
</Col>
<Col>
<strong>{product.countInStock > 0 ? 'In Stock' : "Out Of Stock"}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Sizes :
</Col>
<Col>
{product.size.map(s=><Button key={s} className='p-2 m-1'>{s}</Button>)}
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item className='d-grid gap-2'>
<Button type='button' disabled={product.countInStock === 0}>
Add To Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
</>
)
}}
export default ProductScreen
import React , {useState , useEffect} from 'react'
import { Row, Col, Image, ListGroup, Card, Button } from 'react-bootstrap'
import {Link} from 'react-router-dom'
import Rating from '../components/Rating'
import axios from 'axios'
const ProductScreen = ({match}) => {
const [product , setProduct] = useState({})
useEffect(()=>{
let componentMounted = true
const fetchProducts = async ()=>{
const {data} = await axios.get(`http://172.30.246.130:5000/api/products/${match.params.id}`)
if(componentMounted){
setProduct(data)
}
}
fetchProducts()
return () => {
componentMounted = false
}
},[match,product ])
return (
<>
<Link className='btn btn-light my-3' to='/'>
Go Back
</Link>
<Row>
<Col md={6}>
<Image src={product.image} alt={product.name} />
</Col>
<Col md={3}>
<ListGroup variant='flush'>
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating rating={product.rating} reviews={product.numReviews}/>
</ListGroup.Item>
<ListGroup.Item>
<strong> Price: ${product.price}</strong>
</ListGroup.Item>
<ListGroup.Item>
<strong>Description :</strong> {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup>
<ListGroup.Item>
<Row>
<Col>
Price :
</Col>
<Col>
<strong>${product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Status :
</Col>
<Col>
<strong>{product.countInStock > 0 ? 'In Stock' : "Out Of Stock"}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Sizes :
</Col>
<Col>
{console.log(product)}
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item className='d-grid gap-2'>
<Button type='button' disabled={product.countInStock === 0}>
Add To Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
</>
)
}
export default ProductScreen
You can't prevent the initial state being rendered.
(I've added an update to the bottom related to the updated question.)
If that component can't be usefully rendered without the product, then product should be a prop it receives, not a state member it fetches. The fetch should be in the parent component.
But if you want the fetch to remain in the component, you need to set a marker value (like null) and branch your rendering based on that marker value, e.g.:
if (!product) {
return /* ...render a "loading" message... */;
}
// ..render `product` here...
Re your edited question: You seem to be having an issue with product.size.map(/*...*/). If we look at your initial state, you have:
const [product, setProduct] = useState({});
Since it doesn't have a size property, product.size is undefined and product.size.map will fail with an error about product.size being undefined.
You haven't shown the code trying to do the product.size.map, but you have various options.
You could use a guard:
{product.size && product.size.map(/*...*/)}
You could include an empty array in the initial state:
const [product, setProduct] = useState({size: []});
Remove product from the dependencies of useEffect as that will trigger the callback and causes an infinite loop.
useEffect(() => {
...
},[match])
To not render anything until product is fetched, use a falsy value like undefined/null as the initial state:
const [product , setProduct] = useState();
and check product before returing the JSX:
if(!product) return null;
return (
<>
<Link className='btn btn-light my-3' to='/'>
...
)

How to Set Rows in React-Bootstrap Correctly?

how do I set the number of cards per row? I need 4 cards to be in one row of mine.
I have been trying for hours.. can't figure out what i do wrong..
I was getting all sorts of compilation errors when i played around with this.
thanks!
:)
import { React } from 'react';
import { Card, Button, CardGroup } from 'react-bootstrap';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
const MyDishes = props => {
const { dishes } = props;
return (
<CardGroup style={{display: 'flex', flexDirection: 'row'}}>
{dishes.length > 0 &&
dishes.map(d => (
<Row xs={1} md={2} className="g-4">
{Array.from({ length: 4 }).map((_, idx) => (
<Col>
<Card key={d.id} style={{ width: '100em' }} style={{flex: 1}}>
<Card.Img variant="top" src={d.attributes.picture} />
<Card.Body>
<Card.Title>{d.attributes.name}</Card.Title>
<Card.Text>Possibly some text here</Card.Text>
<Link to={`/dishes/${d.id}`}>
<Button variant="primary">Full Recipe</Button>
</Link>
</Card.Body>
</Card>
</Col>
))}
</Row>
</CardGroup>
);
};
const mapStateToProps = state => {
return {
dishes: state.myDishes
};
};
export default connect(mapStateToProps)(MyDishes);
Above bootstrap 5
<Row className="row-cols-4">
...
</Row>
or any version
<Row className="row-cols-4">
{Array.from({ length: 4 }).map((_, idx) => (
<Col className="col-3">
...
</Col>
</Row>

Recharts show tooltip on ALL charts on hover

I'm using recharts/ react to visualize some simple data, and running into a wall. I want to show the line + tooltip on ALL the graphs whenever a user hovers over any of the graphs. Been trying to use state or dispatch but running into a wall.
I've attached code snippets for my chart and dashboard files with the attempt at using dispatcher. I dont't know where in chart.js to actually call showTooltip. The functionality I want is to show the tooltips for all charts whenever a user hovers over any single chart. If one tooltip = active, I want all tooltips=active. Any guidance would be super helpful!
chart.js snippet
export default function Chart(props) {
const {state, dispatch} = useContext(AppContext);
const showTooltip = (newValue) => {
dispatch({ type: 'HOVER', data: newValue,});
};
const theme = createMuiTheme({
palette: {
primary: {
main: '#041f35'
},
secondary: {
main: '#5dc5e7'
}
}
});
return (
<React.Fragment>
<MuiThemeProvider theme={theme}>
<Title>{props.title}</Title>
<ResponsiveContainer width="100%" height="100%">
<LineChart
width={500}
height={300}
data={data}
margin={{
top: 5,
right: 5,
left: -35,
bottom: 5,
}}
>
<XAxis dataKey="time" />
<YAxis axisLine={false} tickLine={false}/>
<Tooltip />
<CartesianGrid vertical={false} stroke="#d3d3d3"/>
<Line type="monotone" dataKey="mktplace1" stroke={theme.palette.primary.main} activeDot={{ r: 8 }} />
<Line type="monotone" dataKey="mktplace2" stroke={theme.palette.secondary.main} />
</LineChart>
</ResponsiveContainer>
</MuiThemeProvider>
</React.Fragment>
);
}
dashboard.js file snippet
export const AppContext = React.createContext();
// Set up Initial State
const initialState = {
active: new Boolean(false),
};
function reducer(state, action) {
switch (action.type) {
case 'HOVER':
return {
active: new Boolean(true)
};
default:
return initialState;
}
}
export default function Dashboard() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div className={classes.root}>
<CssBaseline />
<main className={classes.content}>
<Container maxWidth="lg" className={classes.container}>
<Grid container spacing={2}>
{/* Chart */}
<Grid item xs={12} sm={12} md={6} lg={6} xl={6}>
<Paper className={fixedHeightPaper}>
<AppContext.Provider value={{ state, dispatch }}>
<Chart title="Sales by Marketplace"/>
</AppContext.Provider>
</Paper>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6} xl={6}>
<Paper className={fixedHeightPaper}>
<AppContext.Provider value={{ state, dispatch }}>
<Chart title="Sales by Marketplace"/>
</AppContext.Provider>
</Paper>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6} xl={6}>
<Paper className={fixedHeightPaper}>
<AppContext.Provider value={{ state, dispatch }}>
<Chart title="Sales by Marketplace"/>
</AppContext.Provider>
</Paper>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6} xl={6}>
<Paper className={fixedHeightPaper}>
<AppContext.Provider value={{ state, dispatch }}>
<Chart title="Sales by Marketplace"/>
</AppContext.Provider>
</Paper>
</Grid>
</Grid>
</Container>
</main>
</div>
);
}
I know this was a long time ago, but there is a "syncId" property to put on your chart. All charts with the same syncId will show tooltips at the same time.

How to generate data for the child component?

Here is the parent code:
import {Col,Container,Row} from 'react-bootstrap';
import {useEffect,useState} from "react";
import AppConfig from '../../utils/AppConfig';
import BB from './BB';
import React from "react";
import Roster from '../../utils/Roster';
import MonthPicker from '../monthPicker/MonthPicker';
export default function AA(){
const[rosterMonth,setRosterMonth]=useState(new Date());
const[rosterTableData,setRosterTableData]=useState({});
let monthPickerMinDate=JSON.parse(AppConfig.MIN_DATE);
monthPickerMinDate=new Date(monthPickerMinDate.year,monthPickerMinDate.month-1,monthPickerMinDate.date);
useEffect(()=>{
const getData = async () => {
let roster = new Roster();
let rosterData = await roster.get(rosterMonth.getFullYear(),rosterMonth.getMonth()+1);
let rosterParam = await roster.getRosterParam();
setRosterTableData(
{
"rosterData":rosterData,
"rosterParam":rosterParam
}
)
}
getData();
},[rosterMonth]);
let updateMonth=(year,month)=>{
//console.log("updateMonth="+year+","+month);
let newDate=new Date();
newDate.setFullYear(year);
newDate.setMonth(month);
setRosterMonth(newDate);
}
return(
<div className="App p-1">
<Container fluid={true} className="tableContainer">
<Row>
<Col className="font-weight-bold text-center tableCaption" md={12} lg={12} sm={12} xl={12} xs={12}>
<u>Roster</u>
</Col>
</Row>
<Row>
<Col md={12} lg={12} sm={12} xl={12} xs={12}>
<MonthPicker
minDate={monthPickerMinDate}
onSelect={updateMonth} />
</Col>
</Row>
<Row>
<Col className="d-flex justify-content-center p-0" md={12} lg={12} sm={12} xl={12} xs={12}>
<BB rosterTableData={rosterTableData}/>
</Col>
</Row>
</Container>
</div>
)
}
Here is the child code:
export default function BB(props){
console.log(props);
return(<div></div>);
}
The expected result is that:
When the user picks a month from the MonthPicker, the parent component submit the select month and year to server.
Get the result from the server and then send the result to the child component.
The actual result is that the child components show its props twice(both the parent initial mount and update mount), that may be caused by 2 state variables exist.
However, I don't know how to implement the function without using 2 state variables.
Is there any more simple solution?
If it's really an issue, I'd just use conditional rendering - have rosterTableData be empty initially, and check if it's empty before rendering the BB:
const[rosterTableData,setRosterTableData]=useState();
<Col className="d-flex justify-content-center p-0" md={12} lg={12} sm={12} xl={12} xs={12}>
{rosterTableData && <BB rosterTableData={rosterTableData}/>}
</Col>

props don't have the right data in reactjs

i use a firebase database and i take data from this base into JSON format. With this data i am using map function and i want to render my data into other components. My code is as shown below. The first component
function Products() {
const [url, setUrl] = useState([]);
useEffect(() => {
async function asyncCall() {
const myurl = await axios.get("i put a example link here:mydata.json")
setUrl(myurl.data)
}
asyncCall();
},[]);
return (
<Row>
{url.map((url => (
<Col key={url.id} sm={12} md={6} lg={4} xl={3}>
<Bags url={url} />
</Col>
)
))}
</Row>
)
}
The second component that i want to render my data
function Bags(props) {
return (
<Row>
<CardDeck>
<Col sm={14} md={8} lg={6}>
<Card className='my-3 p-3 rounded'>
{
props.url ? (
<div>
<Card.Img variant="top" src={ props.url.img || 'holder.js/100px160'} />
<Card.Body>
<Card.Title> {props.url.name} </Card.Title>
<Card.Text>
This is the greatest albums of rock band Pearl Jam according to Nikolas
</Card.Text>
</Card.Body>
</div>
) : (
<div className="myprogress">
<CircularProgress color="secondary" />
</div>
)
}
</Card>
</Col>
</CardDeck>
</Row>
)
}
With the second component i want to produce the number of Bootstrap-React Cards depending of the number of data i have. For example if i have 6 elements into my JSON file i want in the second component to produce 6 react-bootstrap Cards and print for each some informations like the name.
With the above code i accomplished to pass the props but the props that i console.log is not my data. This is what i get in my console when i
console.log(props)
Can anyone tell how i can pass my data correctly or suggest a better way to do that.
I hope my question is understood. I can give more information i anyone wants
I think this is what you are trying to achieve:
function Products() {
const [url, setUrl] = useState([]);
useEffect(() => {
async function asyncCall() {
const myurl = await axios.get("i put a example link here:mydata.json");
setUrl(myurl.data);
}
asyncCall();
}, []);
return (
<Row>
{/*{ {url.map((url => ( */}
{/* the url in the arrow function was shadowing the url array that you were trying to pass to the bags componenet */}
<Col key={url.id} sm={12} md={6} lg={4} xl={3}>
<Bags url={url} />
</Col>
{/* )
))} */}
</Row>
);
}
function Bags(props) {
return (
<Row>
<CardDeck>
<Col sm={14} md={8} lg={6}>
<Card className="my-3 p-3 rounded">
{props.url.length > 0 ? (
props.url.map((el) => (
<div>
<Card.Img
variant="top"
src={el.img || "holder.js/100px160"}
/>
<Card.Body>
<Card.Title> {el.name} </Card.Title>
<Card.Text>
This is the greatest albums of rock band Pearl Jam
according to Nikolas
</Card.Text>
</Card.Body>
</div>
))
) : (
<div className="myprogress">
<CircularProgress color="secondary" />
</div>
)}
</Card>
</Col>
</CardDeck>
</Row>
);
}
can you please confirm the results?
Try this and tell me if it works
import React, { useEffect, useState } from "react";
import axios from "axios";
import "./styles.css";
export default function App() {
const [url, setUrl] = useState([]);
useEffect(() => {
async function asyncCall() {
const response = await fetch(
"https://mysiteproject-8adcf.firebaseio.com/products.json"
);
const responseJson = await response.json();
console.log(responseJson);
setUrl(responseJson);
}
asyncCall();
}, []);
return (
<div>
{url.map((url => (
<Col key={url.id} sm={12} md={6} lg={4} xl={3}>
<Bags url={url} />
</Col>
)
))}
</div>
);
}
I almost solved the problem with this implementation
function Bags() {
const [url, setUrl] = useState([]);
//const [myfinal,setFinal] = useState([]);
useEffect(() => {
async function asyncCall() {
const myurl = await axios.get("https://mysiteproject-8adcf.firebaseio.com/products.json")
setUrl(myurl.data)
}
asyncCall();
},[]);
if (url) {
//let myvar = url;
//console.log(myvar.img);
//console.log(myvar);
url.map((url) => console.log(url.img));
}
//console.log()
return (
<Row>
<CardDeck>
<Col sm={14} md={8} lg={6}>
<Card className='my-3 p-3 rounded'>
{url.length > 0 ? (
url.map((el) => (
<div>
<Card.Img
variant="top"
src={el.img || "holder.js/100px160"}
/>
<Card.Body>
<Card.Title> {el.name} </Card.Title>
<Card.Text>
This is the greatest albums of rock band Pearl Jam
according to Nikolas
</Card.Text>
</Card.Body>
</div>
))
) : (
<div className="myprogress">
<CircularProgress color="secondary" />
</div>
)}
</Card>
</Col>
</CardDeck>
</Row>
)
}
I don't know if this implementation is optimal

Categories