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>
Related
This question already has answers here:
React - TypeError: Cannot read properties of undefined (reading 'params')
(12 answers)
Closed 3 months ago.
I wanna display certain product when it's clicked on based on their id from django rest framework api.. I was doing that using match from the react-router-dom v5, but I don't know how to accomplish the same thing with match not existing in v6. I looked at the react-router-dom v6 documentation for a clue and i couldn't find a solution..
ProductScreen.js:
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { Row, Col, Image, ListGroup, Button, Card } from "react-bootstrap";
//import products from "../../products";
import Rating from "../Rating";
import axios from "axios";
function ProductScreen({ match }) {
const [product, setProduct] = useState([]);
useEffect(() => {
async function fetchProduct() {
const { data } = await axios.get(`/api/products/${match.params.id}`);
setProduct(data);
}
fetchProduct();
}, []);
return (
<div>
<Link to="/" className="btn btn-dark my-3">
Go Back
</Link>
<Row>
<Col md={6}>
<Image
className="imgCustom"
src={product.image}
alt={product.name}
></Image>
</Col>
<Col md={3}>
<ListGroup variant="flush">
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating
value={product.rating}
text={`${product.numReviews} reviews`}
color={"f8e825"}
/>
</ListGroup.Item>
<ListGroup.Item>Price : ${product.price}</ListGroup.Item>
<ListGroup.Item>Description: ${product.description}</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup variant="flush">
<ListGroup.Item>
<Row>
<Col>Price : </Col>
<Col>
<strong>$ {product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Status : </Col>
<Col>
{product.coutInStock > 0 ? "In Stock" : "Out of Stock"}
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Button
className="btn-block"
disabled={product.coutInStock === 0}
type="button"
>
Add to Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
{product.name}
</div>
);
}
export default ProductScreen;
You can use useParmas for this:
import { useParams } from "react-router-dom";
function ProductScreen() {
const { id } = useParams();
useEffect(() => {
async function fetchProduct() {
const { data } = await axios.get(`/api/products/${id}`);
setProduct(data);
}
fetchProduct();
}, []);
}
can use the useParams to get the id
function ProductScreen({ match }) {
const [product, setProduct] = useState([]);
const { id } = useParams();
useEffect(() => {
async function fetchProduct() {
if(id){
const { data } = await axios.get(`/api/products/${id}`);
setProduct(data);
}
}
fetchProduct();
}, [id]);
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='/'>
...
)
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
})
};
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
I have a search.js file and a search-date.js file. In the search.js file, I render a SearchDate container. What I don't understand is the behaviour of the MenuItem component when it is clicked.
As you can see, the function onDayChange is passed down from Search to SearchDate. This function is then passed to MenuItem on the onClick property. onDayChange in Search needs a date argument.
Right now the alert call I've made outputs: object. Where does this object come from? I can't see anywhere in my code that it's being sent by me. And I'm not sure where to look in the Material-UI docs.
search.js:
import SearchDate from '../components/search-date';
import { modelInstance } from '../model/model';
class Search extends Component {
constructor(props){
super(props)
this.state = {
data: null,
searchSuggestion: 'Search for tweets here',
anchorEl: null,
date: 'Today',
page: 0,
placeName: 'the World'
}
componentDidMount() {
modelInstance.addObserver(this);
}
handleClick = event => {
this.setState({ anchorEl: event.currentTarget });
};
onDayChange = date => {
alert(typeof date);
this.setState({date: date})
this.setState({ anchorEl: null });
};
render(){
return(
<div className='search'>
<Row id='searchInput'>
<SearchInput handleInput={this.handleInput.bind(this)} searchInput={this.state.searchInput} searchSuggestion={this.state.searchSuggestion} page={1}/>
</Row>
<Row>
<SearchNav page={this.state.page}/>
</Row>
<Row id='date-location'>
<Col xs={2} sm={2} md={2} className='text'>
<p>FROM</p>
</Col>
<Col xs={4} sm={4} md={4} className='date'>
<SearchDate date={this.state.date} anchorEl={this.state.anchorEl} click={this.handleClick} dayChange={this.onDayChange}/>
</Col>
<Col xs={2} sm={2} md={2} className='text'>
<p>IN</p>
</Col>
<Col xs={4} sm={4} md={4} className='location'>
<SearchLocation placeName = {this.state.placeName} handleLocation={this.handleLocation.bind(this)}/>
</Col>
</Row>
</div>
)
}
}
export default Search;
search-date.js:
const SearchDate = ({date, anchorEl, click, dayChange}) => {
return(
<React.Fragment>
<Button
// variant="raised"
aria-owns={anchorEl ? 'simple-menu' : null}
aria-haspopup="true"
onClick={click}
margin={10}
>
{date}
</Button>
<Menu
id="simple-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={dayChange}
>
{/* {daysList} */}
<MenuItem onClick={dayChange}>Yesterday</MenuItem>
<MenuItem onClick={dayChange}>2 Days past</MenuItem>
<MenuItem onClick={dayChange}>3 Days past</MenuItem>
<MenuItem onClick={dayChange}>4 Days past</MenuItem>
<MenuItem onClick={dayChange}>5 Days past</MenuItem>
<MenuItem onClick={dayChange}>6 Days past</MenuItem>
<MenuItem onClick={dayChange}>7 Days past</MenuItem>
</Menu>
</React.Fragment>
);
}
export default withStyles(styles)(SearchDate);
Material-UI passes the DOM event as an argument on the onClick.
onDayChange = (date) => (event) => { ...your code }
<MenuItem onClick={onDayChange('2 days past')}>2 Days past</MenuItem>
You can pass whatever you want in the event handler. The outer function will get called at the time of rendering. The inner function, which has your handler, will get at the time of menu item click. So your date parameter might get stale if the page doesn't refresh over night, for instance. Personally, I'd pass the # of days as the argument, then get the current date in the handler and do the offset there.