Displaying/hiding JSON info based on clicked item in React/MaterialUI - javascript

I'm creating an FAQ section based on a JSON file I have that has an array of objects with 'Question' and 'Answer.
It's set up to where when you click on the question on the left, it displays the corresponding answer to the right...which does work correctly. The problem is I want to hide the data when the user clicks on that question again and that part is not working. When I click on a question again it will change the icon but doesn't change the box on the right. I want the Answer box to completely disappear when the question is clicked on again.
I'm guessing it's something that I'm doing wrong with setting the state:
const [clickedIndex, setClickedIndex] = useState({});
const [displayAnswer, setDisplayAnswer] = useState();
const handleClick = (index) => () => {
setClickedIndex((state) => ({
[index]: !state[index],
}));
setDisplayAnswer(faqdata[index].Answer);
console.log(displayAnswer);
};
<Grid container spacing={3}>
<Grid item>
<List
style={{
maxHeight: 430,
width: 500,
overflow: 'auto',
border: '1px solid black',
}}
>
{faqdata.map((item, index) => (
<ListItem style={{ cursor: 'pointer' }}>
<ListItemIcon>
{clickedIndex[index] ? <RemoveIcon /> : <AddIcon />}
</ListItemIcon>
<ListItemText
primary={item.Question}
onClick={handleClick(index)}
/>
</ListItem>
))}
</List>
</Grid>
<Grid item>
<List
style={{
maxHeight: 430,
width: 500,
overflow: 'auto',
border: '1px solid black',
}}
>
<ListItem>{displayAnswer}</ListItem>
</List>
</Grid>
</Grid>

I would consider using a single state variable to store the currently open answer which can be:
null (meaning that no answer is open)
a number (meaning the answer to the question with that index is open)
const Container = () => {
const [openQuestion, setOpenQuestion] = useState(null);
const toggleOpenQuestion = (index) => {
if(openQuestion === index) {
setOpenQuestion(null)
}
setOpenQuestion(index)
}
return <UIComponents openQuestion={openQuestion} toggleOpenQuestion={toggleOpenQuestion}/>
}
Then in the UI you can work out what to display based on this value as follows:
<Grid container spacing={3}>
<Grid item>
<List
style={{
maxHeight: 430,
width: 500,
overflow: "auto",
border: "1px solid black",
}}
>
{faqdata.map((item, index) => (
<ListItem style={{ cursor: "pointer" }}>
<ListItemIcon>
{openQuestion === index ? <RemoveIcon /> : <AddIcon />}
</ListItemIcon>
<ListItemText primary={item.Question} onClick={() => toggleOpenQuestion(index)} />
</ListItem>
))}
</List>
</Grid>
<Grid item>
<List
style={{
maxHeight: 430,
width: 500,
overflow: "auto",
border: "1px solid black",
}}
>
<ListItem>{displayAnswer}</ListItem>
</List>
</Grid>
</Grid>;
Worth mentioning here that you should probably wrap the toggleOpenQuestion function in a useCallback to prevent uneccessary re-renders in the child component:
const toggleOpenQuestion = useCallback((index) => {
if(openQuestion === index) {
setOpenQuestion(null)
}
setOpenQuestion(index)
}, [openQuestion])

Related

i wan to get timely divided number of slot in reactjs

i just create this timepicker using mui and momentjs. 2 timepicker, one input, and one button, when user selects particular time like in first picker 8:am and in second one is 9:00am, and enter the slot like 6 so output will be 8:00-8:10, 8:10-8:20, 8:20 - 8:30, 8:40 - 8:50, 8:50 - 9:00. i am done with this to think about logic.
import * as React from "react";
import TextField from "#mui/material/TextField";
import { AdapterDateFns } from "#mui/x-date-pickers/AdapterDateFns";
import { LocalizationProvider } from "#mui/x-date-pickers/LocalizationProvider";
import { TimePicker } from "#mui/x-date-pickers/TimePicker";
import { Box } from "#mui/system";
import moment from "moment";
import { Button, Grid } from "#mui/material";
import Paper from "#mui/material/Paper";
export default function BasicDatePicker() {
const [startTime, setStartTime] = React.useState([]);
const [endTime, setEndTime] = React.useState([]);
return (
<Box
component="main"
sx={{ flexGrow: 1, p: 3 }}
style={{ marginTop: "100px" }}
>
<LocalizationProvider
dateAdapter={AdapterDateFns}
sx={{ flexGrow: 1, p: 3 }}
>
<Box
sx={{
display: "flex",
flexWrap: "wrap",
"& > :not(style)": {
m: "0 auto",
width: 300,
height: 350,
},
}}
>
<Paper elevation={3}>
<Grid container>
<Grid item xs={12} style={{ margin: "25px" }}>
<TimePicker
label="Start Time"
value={startTime}
onChange={(newValue) => {
setStartTime(newValue);
// setStartTime(moment(newValue).add(1, "days"));
// console.log(newValue)
}}
renderInput={(params) => <TextField {...params} />}
/>
</Grid>
<Grid item xs={12} style={{ margin: "25px" }}>
<TimePicker label="End Time"
// disabled
value={endTime}
onChange={(newValue) => {
setEndTime(newValue);
}}
renderInput={(params) => <TextField {...params} />}
/>
</Grid>
<Grid item xs={12} style={{ textAlign: "center" , }}>
{" "}
<TextField id="outlined-basic" label="Slot" variant="outlined"/>
</Grid>
<Grid item xs={12} style={{ textAlign: "center", margin: "10px" }}>
{" "}
<Button variant="contained">Action</Button>
</Grid>
</Grid>
</Paper>
</Box>
</LocalizationProvider>
</Box>
);
}
Here you can use this function, pass the moment objects to the function.
const today = moment();
const someday = moment().add(1,"h");
function getSlots(one, two){
const slots = [];
while(one.unix() < two.unix()){
const tmp = one;
const slot = `${tmp.format("h:mm")}-${one.add(10,"m").format("h:mm")}`;
}
return slots;
}
console.log(getSlots(today, someday,"10 min"));

How to remove border bottom for last child for Box Component Material UI

i have Box Component where Lisitems are wrapped,for each child i have a borderbottom of 1px solid class set for Box component,but i dont want it for last child,how can i achieve that.
{data.map((item, i) => {
return (
<Box key={i} className={classes.itemsBorderBottom}>
<ListItem >
<ListItemText>
<Typography }>
{item.message}
</Typography>
<Box>
<Typography}>
{item.name}-{item.type}
</Typography>
<Box >
<Typography >
{formatDate(item.created, 'DD/MM/YYYY')}
</Typography>
<Typography>
{moment(item.created).format('hh:mm a')}
</Typography>
</Box>
</Box>
</ListItemText>
</ListItem>
</Box>
)
})}
const useStyles = makeStyles({
itemsBorderBottom: {
borderBottom: '1px solid #EFEFEF',
'&:last-child': {
borderBottom: 'none'
}
}
})
check this out!
const useStyles = makeStyles({
itemsBorderBottom: {
borderBottom: '1px solid #EFEFEF',
'&:nth-last-child(1)': {
borderBottom: 'none'
}
}
})

React and Material UI: how can I expand only one single card

I'm using React and Material UI in order to show some mapped cards mapped. When I try to expand a card, all the cards are expanded at the same time. I figured out that I have to pass an index inside my "handleExpandClick" function, but still not working. Maybe I did it some kind of typo.
I found this question Expand one card on click that regards the same problem, but seems not to be applied to my situation.
Here my piece of code:
const useStyles = makeStyles(theme => ({
card: {
maxWidth: 345,
marginBottom: 15
},
media: {
height: 0,
paddingTop: "56.25%" // 16:9
},
expand: {
transform: "rotate(0deg)",
marginLeft: "auto",
transition: theme.transitions.create("transform", {
duration: theme.transitions.duration.shortest
})
},
expandOpen: {
transform: "rotate(180deg)"
},
avatar: {
width: 90
},
root: {
display: "flex",
justifyContent: "center",
flexWrap: "wrap",
"& > *": {
margin: theme.spacing(0.5)
}
},
list: {
width: 200
}
}));
const ItininerariesList = ({ itineraries, activities }) => {
const classes = useStyles();
const [expanded, setExpanded] = React.useState(false);
let path = window.location.pathname;
let currentId = path.substring(path.lastIndexOf("/") + 1);
const itinerariesPerCity = itineraries.filter(
itiner => itiner.city_id === currentId
);
const handleExpandClick = () => {
setExpanded(!expanded);
};
return (
<Fragment>
{itinerariesPerCity.map((itinerary, i) => (
<Card className={classes.card} key={itinerary._id}>
<CardHeader
avatar={
<Grid
container
direction="column"
justify="flex-start"
alignItems="center"
className={classes.avatar}
>
<Avatar
aria-label="user"
alt={itinerary.profile_name}
src={itinerary.profile_img}
>
<PersonIcon />
</Avatar>
<Typography variant="caption" component="p">
{itinerary.profile_name}
</Typography>
</Grid>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={itinerary.title}
subheader={itinerary.sub_title}
/>
<CardContent>
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
</IconButton>
<IconButton aria-label="share">
<ShareIcon />
</IconButton>
<IconButton
className={clsx(classes.expand, {
[classes.expandOpen]: expanded
})}
onClick={() => handleExpandClick()}
aria-expanded={expanded}
aria-label="show more"
>
<ExpandMoreIcon />
</IconButton>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
<ActivitiesList
activities={activities}
itineraryId={itinerary._id}
/>
</CardContent>
</Collapse>
</Card>
))}
</Fragment>
);
};
export default ItininerariesList;
Any suggestions or guidance would be greatly appreciated. Thank you in advance.
In the expand handler you indeed have to pass the index. And also use it in the state.
Something like it:
const [expandedId, setExpandedId] = React.useState(-1);
...
const handleExpandClick = (i) => {
setExpandedId(expandedId === i ? -1 : i);
};
...
<CardContent />
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
</IconButton>
<IconButton aria-label="share">
<ShareIcon />
</IconButton>
<IconButton
onClick={() => handleExpandClick(i)}
aria-expanded={expandedId === i}
aria-label="show more"
>
<ExpandMoreIcon />
</IconButton>
</CardActions>
<Collapse in={expandedId === i} timeout="auto" unmountOnExit>
<CardContent>
<div>ActivitiesList</div>
</CardContent>
</Collapse>
Here is a working example: https://codesandbox.io/s/eloquent-sara-wswrn

Grid Items are in a column instead of a row Material-UI

I am trying to have a grid that has 3 items in a row. However the grid in my code continues vertically and there is only one item per row. There is an image in each grid item as well. How can I change it so there is 3 grid items in a row. Here's my code:
Grid.js:
const styles = theme => ({
root: {
display: "flex",
flexWrap: "wrap",
justifyContent: "space-around",
overflow: "hidden",
backgroundColor: theme.palette.background.paper,
margin: 0
},
paper: {
margin: "1%",
padding: "1%",
width: "80%"
},
});
class GridListings extends Component {
componentDidMount() {
this.props.getPosts();
}
render() {
const { classes } = this.props;
const { posts, loading } = this.props.post;
let postContent;
if (posts === null || loading) {
postContent = <div> loading</div>;
} else {
postContent = <PostFeed posts={posts} />;
}
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<Typography align="center" variant="display2">
Sneaker Listings
</Typography>
{postContent}
</Paper>
</div>
);
}
}
postfeed.js:
class PostFeed extends Component {
render() {
const { posts } = this.props;
return posts.map(post => <ListingPost key={post._id} post={post} />);
}
}
ListingPost.js:
const styles = theme => ({
root: {
flexGrow: 1,
maxWidth: "30%",
padding: theme.spacing.unit * 2
},
image: {
maxWidth: "100%",
maxHeight: "100%"
},
img: {
margin: "auto",
display: "block",
maxWidth: "100%",
maxHeight: "100%"
}
});
class ListingPost extends Component {
onDeleteClick(id) {
console.log(id);
}
render() {
const { post, auth, classes } = this.props;
return (
<Paper className={classes.root}>
<Grid container spacing={16} direction="row">
<Grid item>
<ButtonBase className={classes.image}>
<img
className={classes.img}
alt="complex"
src={post.productImage}
/>
</ButtonBase>
</Grid>
<Grid item xs={12} sm container direction="row">
<Grid item xs container spacing={16}>
<Grid item xs>
<Typography gutterBottom variant="subheading">
Shoes
</Typography>
<Typography gutterBottom>Size:12</Typography>
<Typography color="textSecondary">Brand New</Typography>
</Grid>
<Grid item>
<Typography style={{ cursor: "pointer" }}>Remove</Typography>
</Grid>
</Grid>
<Grid item>
<Typography variant="subheading">$19.00</Typography>
</Grid>
</Grid>
</Grid>
</Paper>
);
}
}
Any help is appreciated.
You have to use the bootstrap class row in below component. Try below code
PostFeed.js
class PostFeed extends Component {
render() {
const { posts } = this.props;
let postss= [];
posts.map(post => {
postss.push(<ListingPost key={post._id} post={post} />);
});
return (
<div className="row">
{postss}
</div>
);
}
}
OR
In your grid.js just add div with className row
<Paper className={classes.paper}>
<Typography align="center" variant="display2">
Sneaker Listings
</Typography>
<div className="row">
{postContent}
</div>
</Paper>

Grid Items aren't beside eachother Material UI

I am using the Grid component in material-ui. I am having trouble having the grid items stack beside each other. Right now they are stacking below each other. I am not sure what is making them stack below each other. I have made it so only on small screens the grid items will stack on top of each other, other wise each grid item should take up 4 columns. I am using react for the front-end. Here is my code:
GridItem:
const styles = theme => ({
image: {
maxWidth: "100%",
maxHeight: "100%"
},
});
render() {
const { post, auth, classes } = this.props;
<div className={classes.root}>
<Link to={`/post/${post._id}`}>
<Grid
item
key={post.key}
sm={12}
md={4}
lg={4}
>
<img src={post.productImage} className={classes.image} />
<Typography>
{post.name}
<br />
{post.size}
</Typography>
</Grid>
</Link>
</div>
PostFeed:
render() {
const { posts } = this.props;
return posts.map(post => <ListingPost key={post._id} post={post} />);
}
}
Grid:
const styles = theme => ({
root: {
display: "flex",
flexWrap: "wrap",
justifyContent: "space-around",
overflow: "hidden",
backgroundColor: theme.palette.background.paper,
margin: 0
},
grid: {
margin: "auto",
width: "80%",
display: "inner-block"
},
paper: {
margin: "1%",
padding: "1%",
width: "80%"
},
});
render() {
const { classes } = this.props;
const { posts, loading } = this.props.post;
let postContent;
if (posts === null || loading) {
postContent = <div> loading</div>;
} else {
postContent = <PostFeed posts={posts} />;
}
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<Typography align="center" variant="display2">
Listings
</Typography>
<Grid container className={classes.grid} spacing={16}>
{postContent}
</Grid>
</Paper>
</div>
);
}
}
Like Yash Rahurikar said, remove the line flexWrap: "wrap" of the Grid style, and add wrap='nowrap' to your Grid container.
You can try to use
<Grid container direction={'row'}></Grid>
so you inner items may arrange themselves beside each other.
Hope that helps
<Grid
container
direction="row"
justify="flex-start"
alignItems="flex-start"
spacing={1}
>
//above 👆🏻 should be your main grid container
//wrap your grid item with the link using component prop
<Grid item key={post.key} component={Link} to={`/post/${post._id}`} >
// Your rest content #here
</Grid>
</Grid>
Try using align-items property.
<Grid container spacing={1} alignItems="flex-end">

Categories

Resources