How to conditionally render options in Material UI list? - javascript

I've been trying to conditionally render the list items based on the user's selection but i can't seem to figure it out. I'd like to guide the users through steps in order to arrive at a final solution. I even tried rendering a different menu but it looks like the component does not re-render once the option selected change so the menu stays the same.
import React, { Fragment, useState, useEffect } from 'react';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import TextField from '#material-ui/core/TextField';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
function TroubleshootingForm() {
const [anchorEl, setAnchorEl] = useState(null);
const [softorHardProb, setSoftorHardProb] = useState(null);
const [selectedIndex, setSelectedIndex] = useState(1);
const options = ['Select issue type', 'Software', 'Hardware'];
const softwareOptions = ['Smart Assistant', 'Sounds', '3rd Party Apps', 'OPPO Clone Phone'];
const hardwareOptions = ['Dislay', 'Battery', 'Charger', 'DOA'];
const handleClickListItem = event => {
setAnchorEl(event.currentTarget);
};
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setAnchorEl(null);
console.log(selectedIndex);
};
const handleClose = () => {
setAnchorEl(null);
};
// useEffect(() => {}, []);
return (
<Fragment>
<Typography variant="h6" gutterBottom>
Hardware or Software?
</Typography>
<Grid container spacing={3}>
<Grid item xs={12}>
<div>
<List component="nav" aria-label="Device settings">
<ListItem
button
aria-haspopup="true"
aria-controls="lock-menu"
aria-label="when device is locked"
onClick={handleClickListItem}
>
<ListItemText
primary="When device is locked"
secondary={
selectedIndex === 1
? options[selectedIndex]
: softwareOptions[
(
<ListItemText
primary="When device is locked"
secondary={
selectedIndex === 1
? options[selectedIndex]
: softwareOptions[selectedIndex]
}
/>
)
]
}
/>
</ListItem>
</List>
<Menu
id="lock-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
{options.map((option, index) => (
<MenuItem
key={option}
disabled={index === 0}
selected={index === selectedIndex}
onClick={event => handleMenuItemClick(event, index)}
>
{option}
</MenuItem>
))}
</Menu>
</div>
</Grid>
{/* <Grid item xs={12}>
{renderNextstep()}
</Grid> */}
</Grid>
</Fragment>
);
}
export default TroubleshootingForm;
The idea is for the user to chose options and the render the next step with other options and then so on until the final solution based on the previous selection. I added a screenshot from another app and the idea is that the current rendering of the component changes based on the selection. It starts by showing step 1 > select an option and component re-renders showing step 2 etc...

Related

Send selected text to another div in react JS using onSelect event?

I have a two div I want to send selected text to another div using onSelect event? Right now entire para sending to another div but I want to send just selected text. How can I do this?
Demo:- https://codesandbox.io/s/selected-text-send-to-another-div-using-onselect-0ccnrn?file=/src/App.js
My Code:-
import React from "react";
import { Box, Grid, TextField, Typography } from "#material-ui/core";
import { useState } from "react";
const SendSelectedText = () => {
const [label1, setlabel1]=useState('');
const [para, setPara]=useState('This is Old Para');
const handleClick = () => {
setlabel1(para);
};
return (
<>
<Box className="sendSelectedTextPage">
<Grid container spacing={3}>
<Grid item xl={6} lg={6} md={6}>
<textarea onSelect={handleClick}>{para}</textarea>
</Grid>
<Grid item xl={6} lg={6} md={6}>
<TextField
variant="outlined"
size="small"
label="Label One"
value={label1}
multiline
rows={3}
className="sendSelectedTextPageInput"
/>
</Grid>
</Grid>
</Box>
</>
);
};
export default SendSelectedText;
Thanks for your support!
All you need is use window.getSelection().
Here is solution
import React from "react";
import { Box, Grid, TextField, Typography } from "#material-ui/core";
import { useState } from "react";
const SendSelectedText = () => {
const [label1, setlabel1] = useState("");
const [para, setPara] = useState("This is Old Para");
const handleMouseUp = () => {
setlabel1(window.getSelection().toString()); // setting up label1 value
};
return (
<>
<Box className="sendSelectedTextPage">
<Grid container spacing={3}>
<Grid item xl={6} lg={6} md={6}>
// changed event to onMouseUp
<textarea onMouseUp={handleMouseUp}>{para}</textarea>
</Grid>
<Grid item xl={6} lg={6} md={6}>
<TextField
variant="outlined"
size="small"
label="Label One"
value={label1}
multiline
rows={3}
className="sendSelectedTextPageInput"
/>
</Grid>
</Grid>
</Box>
</>
);
};
export default SendSelectedText;
Sandboxlink
You have to use the selection
const handleClick = (e) => {
setlabel1(
e.target.value.substring(e.target.selectionStart, e.target.selectionEnd)
);
};
or
const handleClick = (e) => {
setlabel1(
para.substring(e.target.selectionStart, e.target.selectionEnd)
);
};
Based on this
sandbox

Create a dropdown that will appear after clicking on chip component that is within TextField

I am having trouble trying to implement a customized dropdown that does not seem to be a built in feature in Material UI. I am using Material UI for all these components btw. So I have a TextField with a Chip for the End Adornment.
The expected behavior is that if the user were to click on the chip, there should be a dropdown that pops up under the TextField where the user can select the type of vehicle. However, I don't think there is a built in Material UI way to do this. Any suggestions? Any suggestions would be appreciated. Thanks!
You can implement it like this:
https://codesandbox.io/s/hungry-golick-kdoylz?file=/src/App.js
import { useState } from "react";
import { TextField, Chip, InputAdornment, Menu, MenuItem } from "#mui/material";
import KeyboardArrowDown from "#mui/icons-material/KeyboardArrowDown";
export default function App() {
const [selectedItem, setSelectedItem] = useState("Jeep");
return (
<TextField
label="With normal TextField"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<ChipDropDown
items={["Jeep", "Volvo", "Ferrari"]}
selectedItem={selectedItem}
onChanged={setSelectedItem}
/>
</InputAdornment>
)
}}
variant="filled"
/>
);
}
const ChipDropDown = ({ items, selectedItem, onChanged }) => {
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (item) => {
onChanged(item);
setAnchorEl(null);
};
return (
<div>
<Chip
label={selectedItem}
onClick={(e) => setAnchorEl(e.currentTarget)}
onDelete={(e) => e}
deleteIcon={<KeyboardArrowDown />}
/>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={(e) => setAnchorEl(null)}
>
{items.map((item) => (
<MenuItem key={item} onClick={(e) => handleClick(item)}>
{item}
</MenuItem>
))}
</Menu>
</div>
);
};

Cannot read properties of undefined (reading 'map') in React

I'm trying to make a ecommerce site using the commerce.js library. I have already 8 products in the library API. I have a Products.js component where I'm passing those array of product objects I got from App.js and again passing single product object to the Product.js component. To do this, I'm using mapping function to pass each object as props to the Product.js component. And I'm receiving this error. I tried commenting out the mapping block and only console logged the products variable from App.js and the prop products in Products.js, there I'm getting all the 8 products so no problem in there. But I don't get it why I'm getting this error in the mapping function.
App.js
import React, { useState, useEffect } from "react";
import { commerce } from "./lib/commerce";
import { Products, Navbar } from "./components";
function App() {
const [products, setProducts] = useState([]);
const fetchProduct = async () => {
const data = await commerce.products.list();
setProducts(data);
};
useEffect(() => {
fetchProduct();
}, []);
console.log("products", products);
return (
<div className="App">
<Navbar />
<Products products={products} />
</div>
);
}
export default App;
Products.js
import React from "react";
import { Grid } from "#material-ui/core";
import Product from "./Product/Product";
import useStyles from "./style";
const Products = ({ products }) => {
const classes = useStyles();
return (
<main className={classes.content}>
<div className={classes.toolbar} />
<Grid container justifyContent="center" spacing={4}>
{products.data.map((product) => (
<Grid item key={product.id} xs={12} sm={6} md={4} lg={3}>
<Product product={product} />
</Grid>
))}
</Grid>
</main>
);
};
export default Products;
Product.js
import React from "react";
import {
Card,
CardMedia,
CardContent,
CardActions,
Typography,
IconButton,
} from "#material-ui/core";
import { AddShoppingCart } from "#material-ui/icons";
import useStyles from "./styles";
const Product = ({ product }) => {
const classes = useStyles();
return (
<Card className={classes.root}>
<CardMedia
className={classes.media}
image={product.media.source}
title={product.name}
/>
<CardContent>
<div className={classes.cardContent}>
<Typography variant="h5" gutterBottom>
{product.name}
</Typography>
<Typography variant="h5">
{product.price.formatted_with_symbol}
</Typography>
</div>
<Typography
variant="body2"
dangerouslySetInnerHTML={{ __html: product.description }}
/>
</CardContent>
<CardActions disableSpacing className={classes.cardActions}>
<IconButton aria-label="Add to Cart">
<AddShoppingCart />
</IconButton>
</CardActions>
</Card>
);
};
export default Product;
In your App.js change your fetchProduct function as follows:
const fetchProduct = async = {
const products = await commerce.products.list();
setProducts(products.data);
}
and then in your Products.js map over the products instead of products.data,
{products.map((product) => (
<Grid item key={product.id} xs={12} sm={6} md={4} lg={3}>
<Product product={product} />
</Grid>
))}
EXPLANATION:
The initial State of products is an empty array, which is passed down to the products component, where you tried products.data.map() but products is an array (initially) and there is no data property on arrays, so it threw error.
There are several ways you can get around this, but if you are only using the products data from the response then you can go with the above approach, where you map over the products not products.data and setProducts() with the products array instead of products object.
Do you can to attached screen with error?
I think, you try to read data before is are loading, you can prevent this by add something like this (products.data || []).map for example.. or change default state in App.js on useState({data:[]})

React Material-UI override popover completely

I currently using a Select component in my app.
I built a custom modal component that I want to launch instead of the list items when the select is clicked. Is there a way to override the handler for clicks on all portions of the component, such as icon, text field, and dropdown arrow to launch my modal? I want to take just the styling of this component essentially and override the onChange and MenuItem stuff.
<Select
value={props.selectedValue}
onChange={props.onTimeChange}
displayEmpty
startAdornment={
<InputAdornment position="start">
<DateRangeIcon />
</InputAdornment>
}
>
{/* DONT USE THESE MENU ITEMS AND USE CUSTOM MODAL INSTEAD */}
{/*<MenuItem value={-1} disabled>*/}
{/* Start Date*/}
{/*</MenuItem>*/}
{/*<MenuItem value={1}>Last Hour</MenuItem>*/}
{/*<MenuItem value={24}>Last Day</MenuItem>*/}
{/*<MenuItem value={24 * 7}>Last Week</MenuItem>*/}
{/*<MenuItem value={24 * 31}>Last Month</MenuItem>*/}
{/*<MenuItem value={''}>All</MenuItem>*/}
</Select>
In order for it to make sense to leverage Select while using an alternative display for the options, it is important that you provide it with menu items for all the allowed values, because the display of the selected item is based on finding a matching MenuItem for the current value (though it would also be possible to provide the Select with a single MenuItem with a dynamic value and text matching whatever the current selected value is).
You can use a "controlled" approach for managing the open state of the Select using the open and onOpen props (you can leave out onClose since the close should always be triggered by your custom display of the options). This way, rather than trying to override the different events that cause the Select to open, you just let it tell you when it should open (via the onOpen prop), but instead of opening the Select, leave its open prop as always false and only open your custom popup.
Here's a working example:
import React from "react";
import InputAdornment from "#material-ui/core/InputAdornment";
import Button from "#material-ui/core/Button";
import DateRangeIcon from "#material-ui/icons/DateRange";
import Popover from "#material-ui/core/Popover";
import Box from "#material-ui/core/Box";
import Select from "#material-ui/core/Select";
import MenuItem from "#material-ui/core/MenuItem";
export default function SimplePopover() {
const [value, setValue] = React.useState(1);
const [open, setOpen] = React.useState(false);
const selectRef = React.useRef();
const handleSelection = newValue => {
setValue(newValue);
setOpen(false);
};
return (
<Box m={2}>
<Select
ref={selectRef}
value={value}
onChange={e => setValue(e.target.value)}
displayEmpty
open={false}
onOpen={() => setOpen(true)}
startAdornment={
<InputAdornment position="start">
<DateRangeIcon />
</InputAdornment>
}
>
<MenuItem value={1}>Last Hour</MenuItem>
<MenuItem value={24}>Last Day</MenuItem>
<MenuItem value={24 * 7}>Last Week</MenuItem>
<MenuItem value={24 * 31}>Last Month</MenuItem>
<MenuItem value={""}>All</MenuItem>
</Select>
<Popover
id="simple-popover"
open={open}
anchorEl={selectRef.current}
onClose={() => handleSelection(value)}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<Button onClick={() => handleSelection(1)}>Last Hour</Button>
<Button onClick={() => handleSelection(24)}>Last Day</Button>
</Popover>
</Box>
);
}
Here's a second example using a single, dynamic MenuItem for the selected value instead of a comprehensive set of menu items:
import React from "react";
import InputAdornment from "#material-ui/core/InputAdornment";
import Button from "#material-ui/core/Button";
import DateRangeIcon from "#material-ui/icons/DateRange";
import Popover from "#material-ui/core/Popover";
import Box from "#material-ui/core/Box";
import Select from "#material-ui/core/Select";
import MenuItem from "#material-ui/core/MenuItem";
export default function SimplePopover() {
const [value, setValue] = React.useState(1);
const [text, setText] = React.useState("Last Hour");
const [open, setOpen] = React.useState(false);
const selectRef = React.useRef();
const handleSelection = (newValue, newText) => {
setValue(newValue);
setText(newText);
setOpen(false);
};
return (
<Box m={2}>
<Select
ref={selectRef}
value={value}
onChange={e => setValue(e.target.value)}
displayEmpty
open={false}
onOpen={() => setOpen(true)}
startAdornment={
<InputAdornment position="start">
<DateRangeIcon />
</InputAdornment>
}
>
<MenuItem value={value}>{text}</MenuItem>
</Select>
<Popover
id="simple-popover"
open={open}
anchorEl={selectRef.current}
onClose={() => handleSelection(value)}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<Button onClick={() => handleSelection(1, "Last Hour")}>
Last Hour
</Button>
<Button onClick={() => handleSelection(24, "Last Day")}>
Last Day
</Button>
</Popover>
</Box>
);
}

Auto scroll react redux implementation

I am implementing auto-scroll option for my application. In below case channels are list of data which fetch from database. I used redux to call api. how can i connect InfiniteScroll and channels list to get auto-scroll feature?
import React, { Fragment } from "react";
import { Grid } from "#material-ui/core";
import ChannelCard from "./Card";
import CreateChannel from "./Create";
import SimpleSelect from "./Filter";
import InfiniteScroll from "react-infinite-scroll-component";
const ChannelList = ( {channels: { channels}}) => {
const [data, setData] = React.useState({
items: Array.from({ length: 20 })
});
const { items } = data;
const fetchMoreData = () => {
// a fake async api call like which sends
// 20 more records in 1.5 secs
setTimeout(() => {
setData({
items: items.concat(Array.from({ length: 20 }))
});
}, 1500);
};
//view
const view = (
<Fragment>
<Grid container>
<Grid item xs={6} sm={6} md={10} lg={10} xl={10}></Grid>
<Grid>
<SimpleSelect />
</Grid>
</Grid>
<Fragment>
<InfiniteScroll
dataLength={items.length}
next={fetchMoreData}
hasMore={true}
loader={<h4>Loading...</h4>}
>
<Grid container>
{channels.map(channel => (
<Grid key={channel._id} item xs={6} sm={6} md={3} lg={2} xl={2}>
<ChannelCard
channel={channel}
isAuthenticated={isAuthenticated}
/>
</Grid>
))}
</Grid>
</InfiniteScroll>
</Fragment>
</Fragment>
);
return <Fragment>{view}</Fragment>;
};
export default ChannelList;
(I used redux to call api. how can i connect InfiniteScroll and channels list to get auto-scroll feature?)
InfiniteScroll component requires the children to be the array of components that you want to have infinite scrolling on, since you have only one child which is the Grid component it is doing its job for only one item hence its not working: https://www.npmjs.com/package/react-infinite-scroll-component
just remove the wrapping Grid component
<InfiniteScroll
dataLength={items.length}
next={fetchMoreData}
hasMore={true}
loader={<h4>Loading...</h4>}
>
{channels.map(channel => (
<Grid key={channel._id} item xs={6} sm={6} md={3} lg={2} xl={2}>
<ChannelCard
channel={channel}
isAuthenticated={isAuthenticated}
/>
</Grid>
))}
</InfiniteScroll>

Categories

Resources