How to validate my custom input fields before save? - reactjs

"react-admin": "^3.7.1"
import {
Create,
SimpleForm,
TextInput,
SelectInput,
NumberInput,
required,
} from "react-admin";
import { getChoices } from "../../utils";
import RichTextInput from "ra-input-rich-text";
import { makeStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import CloudUploadIcon from "#material-ui/icons/CloudUpload";
import EditIcon from "#material-ui/icons/Edit";
import CropperModal from "./Modal";
import GoodForField from "./goodForField";
export const MEASUREMENT_UNITS = {
GM: "GM",
PIECE: "Piece",
GLASS: "Glass",
KATORI: "Katori",
CUP: "Cup",
BOWL: "Bowl",
};
export const RECIPE_PREPARATION_COMPLEXITY = {
EASY: "EASY",
MEDIUM: "MEDIUM",
HARD: "HARD",
};
const useStyles = makeStyles((theme) => ({
paper: {
position: "absolute",
backgroundColor: theme.palette.background.paper,
border: "2px solid #000",
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
root: {
margin: "10px 0",
},
input: {
display: "none",
},
}));
const RecipeCreate = (props) => {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const [file, setFile] = React.useState("");
const [imageLink, setImageLink] = React.useState("");
const [goodForIds, setGoodForIds] = React.useState([]);
const imageRef = React.useRef();
imageRef.current = imageLink;
const goodForRef = React.useRef();
goodForRef.current = goodForIds;
const transform = (data) => {
console.log({ imageRef });
return {
...data,
image_url: imageRef.current,
good_for: goodForRef.current,
};
};
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleChangeFileInput = (e) => {
setFile(e.target.files[0]);
handleOpen();
};
const handleClickEdit = () => {
setFile(imageLink);
handleOpen();
};
return (
<>
<CropperModal
open={open}
handleClose={handleClose}
file={file}
handleImageLink={setImageLink}
handleVisibility={setOpen}
/>
<Create transform={transform} {...props}>
<SimpleForm redirect={false}>
{imageLink && (
<div
style={{
display: "flex",
width: "100px",
height: "100px",
marginBottom: "20px",
position: "relative",
}}
>
<img
src={imageLink}
alt="dish"
style={{
width: "100px",
height: "100px",
border: "5px solid #c9c5c5",
cursor: "pointer",
}}
onClick={handleClickEdit}
/>
<EditIcon
onClick={handleClickEdit}
style={{
position: "absolute",
cursor: "pointer",
right: "-10px",
bottom: "-10px",
backgroundColor: "#c9c5c5",
fontSize: "1.2rem",
}}
/>
</div>
)}
<div className={classes.root}>
<input
accept=".png, .jpg, .jpeg"
className={classes.input}
id="contained-button-file"
multiple
type="file"
onChange={handleChangeFileInput}
/>
<label htmlFor="contained-button-file">
<Button
style={{ textTransform: "none" }}
startIcon={<CloudUploadIcon />}
variant="contained"
color="primary"
component="span"
>
{imageLink ? "Upload new" : "Upload picture"}
</Button>
</label>
</div>
<TextInput source="name" validate={required()} />
<SelectInput
source="measurement_unit"
lable="Measurement Unit"
choices={getChoices(MEASUREMENT_UNITS)}
validate={required()}
/>
<TextInput validate={required()} source="description" multiline />
<RichTextInput source="recipe" validate={required()} />
<TextInput
validate={required()}
source="preparation_time"
lable="Preparation time"
/>
<SelectInput
validate={required()}
source="preparation_complexity"
label="Preparation Complexity"
choices={getChoices(RECIPE_PREPARATION_COMPLEXITY)}
/>
<GoodForField goodForIds={goodForIds} setGoodForIds={setGoodForIds} />
<NumberInput validate={required()} source="calories" />
<NumberInput validate={required()} source="carbs" />
<NumberInput validate={required()} source="protein" />
<NumberInput validate={required()} source="fats" />
<NumberInput validate={required()} source="fibre" />
<TextInput source="preparation_video_link" label="Video url" />
</SimpleForm>
</Create>
</>
);
};
I am using Create component of react-admin to create the recipe resource. I also have an image scource through which I am uploading the image to cloud storage and later passing the url to create throught transform function.
CropperModal is a component that is used to crop the image and upload the image to cloud stoarge. When the form is submitted I am passing the image URL to the request body with the help of transform function. My problem is how can I validate this custom field before save ?
Note: CropperModal is purely a custom component, not using any of the react-admin utilities.

Related

POST request: Entering an object containing elements and an array

I have a project, and it is an ECommerce project, and the website contains several interfaces, and one of these interfaces is the "Create a Product" interface,
Create a product on the backend graph, written as follows:
enter image description here
And as it is clear from the previous request, there is an object called “En” and another object called “Ar”, and “En” contains “title, description, tags[]” as well as the “Ar”
The problem is that I didn't know how to enter these two objects through React
How can I do this?
In this file I tried to use the tag "Input" and entered the title, description and tag array, and I don't know if my entry method is correct.
import { React, useState } from "react";
import {
Input,
Modal,
ModalBody,
ModalFooter,
ModalHeader,
Button,
} from "reactstrap";
import { gql, useMutation } from "#apollo/client";
import { useCategories } from "../../../../hooks/useCategories";
import { useCollections } from "../../../../hooks/useCollections";
import { useBrand } from "../../../../hooks/useBrand";
import Select from "react-select";
const CREATE_PRODUCT = gql`
mutation createProduct(
$category: String!
$collection: String!
$price: Float!
$sale: Boolean!
$discount: Float!
$stock: Int!
$new: Boolean!
$brand: String!
) {
createProduct(
createProductInput: {
category: $category
collection: $collection
price: $price
sale: $sale
discount: $discount
stock: $stock
new: $new
brand: $brand
}
) {
category,
collection,
price,
sale,
brand,
discount,
stock,
new,
}
}
`;
const GET_PRODUCTS = gql`
query getProducts($limit: Int) {
getProducts(limit: $limit) {
id
title
category{
id
name
}
price
stock
sale
}
}
`;
const AddProduct = () => {
let input;
const categories = useCategories();
const collections = useCollections();
const brands = useBrand();
const categoriesOptions = categories?.data?.categories;
const collectionsOptions = collections?.data?.collections;
const brandsOptions = brands?.data?.brands;
console.log("brands backend: ", brandsOptions, categoriesOptions);
// /////////////////////////////////////////////////
const [category, setCategory] = useState(0);
const [collection, setCollection] = useState(0);
const [brand, setBrand] = useState(0);
const [enTitle, setEnTitle] = useState("");
const [arTitle, setArTitle] = useState("");
const [price, setPrice] = useState(0.0);
const [sale, setSale] = useState(false);
const [newProduct, setNewProduct] = useState(false);
const [discount, setDiscount] = useState(0);
const [stock, setStock] = useState(0);
const [enTags, setEnTags] = useState("");
const [arTags, setArTags] = useState("");
const [enDesc, setEnDesc] = useState("");
const [arDesc, setArDesc] = useState("");
const [description, setDescription] = useState("");
const handleEnTitle = (e) => {
setEnTitle(e.target.value);
};
const handleArTitle = (e) => {
setArTitle(e.target.value);
};
const handleDescription = (e) => {
setDescription(e.target.value);
}
const handlePrice = (e) => {
setPrice(e.target.value);
};
const handleSale = (e) => {
setSale(e.target.value);
};
const handleDiscount = (e) => {
setDiscount(e.target.value);
};
const handleStock = (e) => {
setStock(e.target.value);
};
const handleNewProduct = (e) => {
setNewProduct(e.target.value);
};
const handleEnTags = (e) => {
setEnTags(e.target.value);
};
const handleArTags = (e) => {
setArTags(e.target.value);
};
const handleEnDesc = (e) => {
setEnDesc(e.target.value);
};
const handleArDesc = (e) => {
setArDesc(e.target.value);
};
// //////////////////////////////////////////////
const [modal, setModal] = useState(false);
const togglePopup = () => setModal(!modal);
const [open, setOpen] = useState(false);
const [collectionInput, setCollectionInput] = useState("");
const message = (e) => {
alert("Category was created successfully!");
// return <Alert color="primary">Hey! Pay attention.</Alert>;
};
const resetInput = () => {
setCollectionInput("");
};
const [createProduct] = useMutation(CREATE_PRODUCT, {
refetchQueries: [
{ query: GET_PRODUCTS }, // DocumentNode object parsed with gql
"getProducts", // Query name
],
});
const [inputValue, setValue] = useState("");
const [selectedValueCollection, setSelectedValueCollection] = useState(0);
// handle selection for category
const handleChangeCategory = (value) => {
setCategory(value);
};
// handle selection for collection
const handleChangeCollection = (value) => {
setCollection(value);
};
return (
<div>
{/* <Button> */}
{/* price - (price * discount / 100) */}
<a
onClick={togglePopup}
href="#"
className="btn btn-sm btn-solid"
aria-hidden="true"
>
add product
</a>
{/* </Button> */}
<Modal
isOpen={modal}
toggle={togglePopup}
centered
style={{ padding: "1rem" }}
>
<ModalHeader toggle={togglePopup} style={{ padding: "1rem" }}>
<p>Add Product</p>
</ModalHeader>
<ModalBody style={{ padding: "2rem" }}>
<Select
value={category}
onChange={(categoriesOptions) => {
console.log("value vvv categoriesOptions:", categoriesOptions);
console.log("value.id categoriesOptions: ", categoriesOptions.id);
setCategory(categoriesOptions.id);
}}
options={categoriesOptions}
getOptionLabel={(e) => e.name}
getOptionValue={(e) => e.id}
/>
<Select
value={collection}
// onChange={handleChangeCollection}
onChange={(collectionsOptions) => {
console.log("value vvv:", collectionsOptions);
console.log("value.id: ", collectionsOptions.id);
setCollection(collectionsOptions.id);
}}
options={collectionsOptions}
getOptionLabel={(e) => e.name}
getOptionValue={(e) => e.id}
/>
<Select
value={brand}
//onChange={handleChangeCollection}
onChange={(brandsOptions) => {
console.log("value vvv:", brandsOptions);
console.log("value.id: ", brandsOptions.id);
setBrand(brandsOptions.id);
}}
options={brandsOptions}
getOptionLabel={(e) => e.name}
getOptionValue={(e) => e.id}
/>
<input
id="title"
value="En.title"
onChange={handleEnTitle}
placeholder="Price"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/>
<input
id="description"
value="En.description"
onChange={handleDescription}
placeholder="Price"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/>
<input
id="tags"
value="En.tags.[0]"
onChange={handleEnTags}
placeholder="tags"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/>
<input
id="price"
value={price}
onChange={handlePrice}
placeholder="Price"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/>
<input
id="sale"
value={sale}
onChange={handleSale}
placeholder="Sale"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/>
<input
id="discount"
value={discount}
onChange={handleDiscount}
placeholder="discount"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/>
<input
id="stock"
value={stock}
onChange={handleStock}
placeholder="Stock"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/>
<input
id="newProduct"
value={newProduct}
onChange={handleNewProduct}
placeholder="New"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/>
{/* <AsyncSelect
cacheOptions
defaultOptions
value={selectedValueCollection}
getOptionLabel={(e) => e.name}
getOptionValue={(e) => e._id}
propsLoadOptions={collectionsOptions}
onInputChange={handleInputChangeCollection}
onChange={handleChangeCollection}
/> */}
{/* <input
id="enTitle"
value={enTitle}
onChange={handleETitle}
placeholder="English Title"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/> */}
{/* <input
id="arTitle"
value={arTitle}
onChange={handleArTitle}
placeholder="Arabic Title"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/> */}
{/* <input
id="enTag"
value={enTags}
onChange={handleEnTags}
placeholder="English Tag"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/>
<input
id="arTag"
value={arTags}
onChange={handleArTags}
placeholder="Arabic Tag"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/> */}
{/* <input
id="enDesc"
value={enDesc}
onChange={handleEnDesc}
placeholder="English Desc"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/>
<input
id="arDesc"
value={arDesc}
onChange={handleArDesc}
placeholder="Arabic Desc"
type="text"
style={{
padding: "0.7rem",
paddingRight: "13rem",
margin: "0.7rem",
}}
/> */}
</ModalBody>
<ModalFooter style={{ paddingRight: "2rem" }}>
<Button
color="primary"
onClick={(e) => {
e.preventDefault();
togglePopup();
createProduct({
variables: {
category,
collection,
price,
sale,
brand,
discount,
stock,
neew,
},
});
console.log(
"jjjjjjjjjjjjjjjjjjjjjjjj: ",
category,
collection,
price,
sale,
brand,
discount,
stock,
neew
);
}}
>
Save
</Button>{" "}
<Button onClick={togglePopup}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
);
};
export default AddProduct;

Materail UI TypeError: event.target.getAttribute is not a function

I am making a form in React using Material UI and displaying the form data in a table on client side, the problem is when I Select language by the select dropdown then I get an error of TypeError: event.target.getAttribute is not a function.
Although This language data is not displayed in the table but I need it, it needs to be stored in the database(which currently I have not implemented)
I have referred Material UI select throws event.target.getAttribute is not a function and also this github issue but it did not worked for me.
Currently I am not using any state management tool like redux, I am passing down props from my App component.
Please help me as in where am I going wrong
App.js
import Projects from './components/Projects';
import FormModal from './components/FormModal'
import Sidebar from './components/Sidebar';
import { useState } from 'react';
import {nanoid} from 'nanoid'
import DeleteModal from './components/DeleteModal';
function App() {
const [formDatas,setFormDatas] = useState(
[
{
projectName: 'Test',
projectId: nanoid(),
nameSpace: 'John Doe'
},
])
// Show Table State
const [showTable, setShowTable] = useState(false)
// HOOKS FORM MODAL
const [addFormData, setAddFormData] = useState({
projectName: '',
projectDescription: '',
nameSpace: '',
language: ''
})
const handleAddFormChange = (event) => {
event.preventDefault()
const fieldName = event.target.getAttribute('name')
console.log(fieldName)
const fieldValue = event.target.value
const newFormData = {...addFormData}
newFormData[fieldName] = fieldValue
setAddFormData(newFormData)
}
// FORM SUBMIT HANDLER
const handleAddFormSubmit = (event) => {
event.preventDefault()
const newProject = {
projectId: nanoid(),
projectName: addFormData.projectName,
projectDescription: addFormData.projectDescription,
nameSpace: addFormData.nameSpace,
language: addFormData.language
}
// Copy all the previous formDatas and add newProject Data
const newProjects = [...formDatas, newProject ]
setFormDatas(newProjects)
// console.log(newProject)
// Clear Form Input Fields after Submitting
setAddFormData({
projectName: '',
projectDescription: '',
nameSpace: '',
language: ''
})
// After Submitting close Form Modal
handleClose()
}
// Delete Handler
const deleteModalHandler = (addFormData) => {
console.log(addFormData.projectId)
}
// MODAL OPEN CLOSE STATE
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<>
<Sidebar showTable = {showTable} setShowTable = {setShowTable}/>
<FormModal addFormData = {addFormData}
handleAddFormChange = {handleAddFormChange}
handleAddFormSubmit = {handleAddFormSubmit}
open = {open}
handleOpen = {handleOpen}
handleClose = {handleClose}/>
<Projects
formDatas = {formDatas}
addFormData = {addFormData}
deleteModalHandler={deleteModalHandler}
showTable = {showTable}
setShowTable = {setShowTable}/>
</>
);
}
export default App;
FormModal.js
import * as React from 'react';
import Box from '#mui/material/Box';
import Button from '#mui/material/Button';
import { TextField } from "#mui/material";
import Modal from '#mui/material/Modal';
import InputLabel from '#mui/material/InputLabel';
import MenuItem from '#mui/material/MenuItem';
import Select from '#mui/material/Select';
import { useState } from 'react';
import SearchIcon from '#mui/icons-material/Search';
import { InputAdornment } from '#mui/material';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
export default function FormModal({handleAddFormChange, addFormData,handleAddFormSubmit,open,handleOpen,handleClose}) {
return (
<div>
<Box sx = {{display: 'flex', justifyContent: 'flex-end',alignItems: 'flex-end', flexDirection: 'column'}}>
<Button sx = {{ marginTop: '5%', marginRight: '4%', borderRadius: '10px '}} variant="contained" onClick={handleOpen}>Create project + </Button>
{/* <input style = {{marginTop: '2.5%', marginRight: '5%', padding: '5px'}} type="text" placeholder = "Search..." /> */}
<TextField sx = {{marginTop: '2%', marginRight: '4%', padding: '5px'}} placeholder = "Search..." InputProps={{
endAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
)
}}/>
</Box>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<div style = {{height: '5vh', display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
<h3>Create New Project</h3>
</div>
<form onSubmit = {handleAddFormSubmit}>
<TextField
id="outlined-basic"
label="Project Name"
name = "projectName"
variant="outlined"
fullWidth= {true}
required = {true}
margin ="normal"
value={addFormData.projectName}
onChange={handleAddFormChange}
/>
<TextField
id="outlined-basic"
label="Project Description"
name = "projectDescription"
variant="outlined"
fullWidth = {true}
required = {true}
multiline
rows = {5}
margin ="normal"
value={addFormData.projectDescription}
onChange={handleAddFormChange}
/>
<TextField
id="outlined-basic"
label="Name Space"
name = "nameSpace"
variant="outlined"
fullWidth= {true}
required = {true}
autoComplete = {Math.random().toString()}
margin = "normal"
value={addFormData.nameSpace}
onChange={handleAddFormChange}
/>
<InputLabel id="demo-simple-select-label">Select Language</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
name = "name"
value={addFormData.language}
onChange={handleAddFormChange}
style = {{width: '100%'}}
>
<MenuItem value="english">English</MenuItem>
</Select>
<br />
<div style = {{display: 'flex', alignItems: 'center', justifyContent: 'space-evenly', marginTop: '7%'}}>
<Button style = {{width: '40%'}} variant = "contained" type="submit">Submit</Button>
<Button style = {{width: '40%'}} variant = "contained" onClick = {handleClose}>Cancel</Button>
</div>
</form>
</Box>
</Modal>
</div>
);
}
Projects.js
import React from "react";
import EditModal from "./EditModal";
import DeleteModal from "./DeleteModal";
import { styled } from "#mui/material/styles";
import Table from "#mui/material/Table";
import TableBody from "#mui/material/TableBody";
import TableCell, { tableCellClasses } from "#mui/material/TableCell";
import TableContainer from "#mui/material/TableContainer";
import TableHead from "#mui/material/TableHead";
import TableRow from "#mui/material/TableRow";
import { useState } from "react";
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
// backgroundColor: theme.palette.common.black,
backgroundColor: "#1976D2",
color: theme.palette.common.white,
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
},
}));
const StyledTableRow = styled(TableRow)(({ theme }) => ({
"&:nth-of-type(odd)": {
backgroundColor: theme.palette.action.hover,
},
// hide last border
"&:last-child td, &:last-child th": {
border: 0,
},
}));
const Projects = ({ showTable, setShowTable,addFormData, formDatas,deleteModalHandler}) => {
//const [showTable, setShowTable] = useState(true
return (
<div style={{ display: "flex", alignItems: "center", marginTop: "7%" }}>
{showTable ? <TableContainer > {/*component={Paper}*/}
<Table sx={{ width: '50%', marginLeft: 'auto', marginRight: 'auto' }} aria-label="customized table">
<TableHead>
<TableRow>
<StyledTableCell align="left">Project Name</StyledTableCell>
<StyledTableCell align="left">Project Id</StyledTableCell>
<StyledTableCell align="left">Created By</StyledTableCell>
<StyledTableCell align="center">Operations</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{formDatas.map((formData) => (
<StyledTableRow key = {formData.projectId}>
<StyledTableCell align="left" component="th" scope="row">
{formData.projectName}
</StyledTableCell>
<StyledTableCell align="left">
{formData.projectId}
</StyledTableCell>
<StyledTableCell align="left">
{formData.nameSpace}
</StyledTableCell>
<StyledTableCell align="center">
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-evenly",
}}
>
<EditModal />
<DeleteModal deleteModalHandler = {deleteModalHandler}/>
</div>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>: null}
</div>
);
};
export default Projects;
As mentioned #Pradip Dhakal in the comment <Select> only return the {name: '', value: ''} as a target object, this is causing the issue. There is no other objects.
So I used Material UI Native Select Component and it solved the problem
The only code that I changed in FormModal.js file is below.
<NativeSelect
// defaultValue={30}
value={addFormData.language}
onChange={handleAddFormChange}
inputProps={{
name: 'language',
id: 'uncontrolled-native',
}}
>
<option value = "" disabled>Select</option>
<option value={'english'}>English</option>
<option value={'german'}>German</option>
</NativeSelect>
This is the minimal code that I've try, and it's working fine in my end.
material UI version: "#material-ui/core": "^4.11.0",
// FormModal.js
import React from 'react';
import { TextField } from "#material-ui/core"
export const FormModal = ({ handleAddFormChange }) => {
return (
<TextField
id="outlined-basic"
label="Project Name"
name="projectName"
variant="outlined"
fullWidth={true}
required={true}
margin="normal"
value=""
onChange={handleAddFormChange}
/>
);
}
// app.js
import React from 'react';
import { TextField } from "#material-ui/core"
import { FormModal } from "./FormModal"
const App = () => {
const handleAddFormChange = (event) => {
event.preventDefault()
const fieldName = event.target.getAttribute('name')
console.log("Name: ", fieldName)
}
return (
<FormModal handleAddFormChange={handleAddFormChange} />
);
}
Can you please try to breakdown your code, and start from the basic one.

Material UI Custom Hover Color

Haven't made this feature before where you can change the color of button's hover.
I have already made a feature to change the radius with a slider, background color and font color using color-picker. However, I noticed the hover (for background AND font) could be better.
Here is the code:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import Slider from "#material-ui/core/Slider";
import Input from "#material-ui/core/Input";
import Button from "#material-ui/core/Button";
import { ChromePicker } from "react-color";
const useStyles = makeStyles((theme) => ({
root: {
"& > *": {
margin: theme.spacing(1)
}
},
Button: {
width: 150,
height: 50,
borderRadius: "var(--borderRadius)"
},
color: {
width: "36px",
height: "14px",
borderRadius: "2px"
},
swatch: {
padding: "5px",
background: "#fff",
borderRadius: "1px",
display: "inline-block",
cursor: "pointer"
},
popover: {
position: "absolute",
zIndex: "2"
},
cover: {
position: "fixed",
top: "0px",
right: "0px",
bottom: "0px",
left: "0px"
}
}));
export default function InputSlider() {
const classes = useStyles();
const [value, setValue] = React.useState(30);
const [color, setColor] = React.useState({ r: 0, g: 0, b: 0, a: 1 });
const [fontColor, setFontColor] = React.useState({
r: 255,
g: 255,
b: 255,
a: 1
});
const [displayColorPicker, setDisplayColorPicker] = React.useState(true);
const handleSliderChange = (event, newValue) => {
setValue(newValue);
};
const handleInputChange = (event) => {
setValue(event.target.value === "" ? "" : Number(event.target.value));
};
const handleBlur = () => {
if (value < 0) {
setValue(0);
} else if (value > 30) {
setValue(30);
}
};
const handleClick = () => {
setDisplayColorPicker(!displayColorPicker);
};
const handleClose = () => {
setDisplayColorPicker(false);
};
const handleChange = (color) => {
setColor(color.rgb);
};
const handleFontColorChange = (color) => {
setFontColor(color.rgb);
};
return (
<div className={classes.root}>
<style>
{`:root {
--borderRadius = ${value}px;
}`}
</style>
<Button
style={{
borderRadius: value,
background: `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`,
color: `rgba(${fontColor.r}, ${fontColor.g}, ${fontColor.b}, ${fontColor.a})`
}}
variant="contained"
color="primary"
value="value"
onChange={handleSliderChange}
className={classes.Button}
>
Fire laser
</Button>
<Grid container spacing={2}>
<Grid item xs>
<Slider
value={typeof value === "number" ? value : 0}
onChange={handleSliderChange}
aria-labelledby="input-slider"
/>
</Grid>
<Grid item>
<Input
value={value}
margin="dense"
onChange={handleInputChange}
onBlur={handleBlur}
inputProps={{
step: 10,
min: 0,
max: 24,
type: "number"
}}
/>
</Grid>
</Grid>
<div>
<div style={useStyles.swatch} onClick={handleClick}>
{displayColorPicker} <p class="h4">Background</p>
<div style={useStyles.color} />
</div>
{displayColorPicker ? (
<div style={useStyles.popover}>
<div style={useStyles.cover} onClick={handleClose}></div>
<ChromePicker color={color} onChange={handleChange} />
</div>
) : null}
</div>
<div>
<div style={useStyles.swatch} onClick={handleClick}>
{displayColorPicker} <p class="h4">Font</p>
<div style={useStyles.color} />
</div>
{displayColorPicker ? (
<div style={useStyles.popover}>
<div style={useStyles.cover} onClick={handleClose}></div>
<ChromePicker color={fontColor} onChange={handleFontColorChange} />
</div>
) : null}
</div>
</div>
);
}
And here is the sandbox - https://codesandbox.io/s/material-demo-forked-t8xut?file=/demo.js
Any advice?
Does anyone have a good Material UI article for editing/cool features and projects to play with?
You need to pass props to makeStyles.
First, pass fontColor variable as below when declaring classes:
const classes = useStyles({ hoverBackgroundColor, hoverFontColor })();
then in the useStyles, you can have access to the fontColor as a prop, as below:
const useStyles = ({ hoverBackgroundColor, hoverFontColor }) =>
makeStyles((theme) => ({
Button: {
width: 150,
height: 50,
borderRadius: "var(--borderRadius)",
"&:hover": {
backgroundColor: `rgba(${hoverBackgroundColor.r}, ${hoverBackgroundColor.g}, ${hoverBackgroundColor.b}, ${hoverBackgroundColor.a}) !important`,
color: `rgba(${hoverFontColor.r}, ${hoverFontColor.g}, ${hoverFontColor.b}, ${hoverFontColor.a}) !important`
}
},
sandbox

How render a list of options with renderOption in material UI

I want to change the background colour of the options inside an Autocomplete component, and the closest I can get is by using the renderOption prop.
The problem is that I can't figure out how to iterate (using map()) the options that I have in my state.
What I would like to do is something like
{state.myOptions.map( option => {
// here I would like to call renderOption = .....
}
Inside the <Autocomplete/> component
Is it possible to implement something like this or is there a well defined manner to do it?
EDIT
This is the component
import React, { useEffect } from 'react'
import { useForm, Form } from './hooks/useForm'
import EventIcon from '#material-ui/icons/Event';
import { makeStyles, TextField, Typography } from '#material-ui/core'
import CustomTextField from './inputs/CustomTextField';
import { Autocomplete } from '#material-ui/lab';
import { connect } from 'react-redux'
const EventForm = (props) => {
// Redux
const { family } = props
// React
const initialState = {
email: "",
password: "",
errors: {
email: "",
password: ""
},
familyMembers: ["rgeg"]
}
const { state, handleOnChange, setState } = useForm(initialState)
useEffect(() => {
family && state.familyMembers !== family.members && setState({
...state,
familyMembers: family.members
})
})
// Material UI
const useStyles = makeStyles(theme => (
{
message: {
marginTop: theme.spacing(3)
},
icon: {
backgroundColor: "lightgrey",
padding: "10px",
borderRadius: "50px",
border: "2px solid #3F51B5",
marginBottom: theme.spacing(1)
},
typography: {
marginBottom: theme.spacing(1),
marginTop: theme.spacing(4)
},
customTextField: {
marginTop: theme.spacing(0)
},
dateTimeWrapper: {
marginTop: theme.spacing(4)
}
}
))
const classes = useStyles()
return (
<>
<div>WORK IN PROGRESS...</div>
<br />
<br />
<EventIcon className={classes.icon} />
<Form
title="Add new event"
>
<Typography
variant="subtitle1"
className={classes.typography}
align="left">
Enter a title for this event
</Typography>
<CustomTextField
className={classes.customTextField}
label="Title"
/>
<Typography
variant="subtitle1"
className={classes.typography}
align="left">
Enter a location for this event
</Typography>
<CustomTextField
className={classes.customTextField}
label="Location"
/>
<Typography
variant="subtitle1"
className={classes.typography}
align="left">
Which member/s of the family is/are attending
</Typography>
<Autocomplete
multiple
id="tags-outlined"
options={state.familyMembers}
getOptionLabel={(option) => option.name}
// defaultValue={[familyMembers[0]]}
filterSelectedOptions
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="Members Attending"
placeholder="Family Member"
/>
)}
/>
</Form>
</>
);
}
// Redux
const mapStateToProps = (state) => {
return {
family: state.auth.family
}
}
export default connect(mapStateToProps)(EventForm);
If you only want to override the color of the option you can do it by overriding it's styles. No need to make custom option rendering function.
Above is the example of how can you achieve that.
import React, { useEffect } from 'react'
import { useForm, Form } from './hooks/useForm'
import EventIcon from '#material-ui/icons/Event';
import { makeStyles, TextField, Typography } from '#material-ui/core'
import CustomTextField from './inputs/CustomTextField';
import { Autocomplete } from '#material-ui/lab';
import { connect } from 'react-redux'
const EventForm = (props) => {
// Redux
const { family } = props
// React
const initialState = {
email: "",
password: "",
errors: {
email: "",
password: ""
},
familyMembers: ["rgeg"]
}
const { state, handleOnChange, setState } = useForm(initialState)
useEffect(() => {
family && state.familyMembers !== family.members && setState({
...state,
familyMembers: family.members
})
})
// Material UI
const useStyles = makeStyles(theme => (
{
message: {
marginTop: theme.spacing(3)
},
icon: {
backgroundColor: "lightgrey",
padding: "10px",
borderRadius: "50px",
border: "2px solid #3F51B5",
marginBottom: theme.spacing(1)
},
typography: {
marginBottom: theme.spacing(1),
marginTop: theme.spacing(4)
},
customTextField: {
marginTop: theme.spacing(0)
},
dateTimeWrapper: {
marginTop: theme.spacing(4)
},
option: {
backgroundColor: 'red'
}
}
))
const classes = useStyles()
return (
<>
<div>WORK IN PROGRESS...</div>
<br />
<br />
<EventIcon className={classes.icon} />
<Form
title="Add new event"
>
<Typography
variant="subtitle1"
className={classes.typography}
align="left">
Enter a title for this event
</Typography>
<CustomTextField
className={classes.customTextField}
label="Title"
/>
<Typography
variant="subtitle1"
className={classes.typography}
align="left">
Enter a location for this event
</Typography>
<CustomTextField
className={classes.customTextField}
label="Location"
/>
<Typography
variant="subtitle1"
className={classes.typography}
align="left">
Which member/s of the family is/are attending
</Typography>
<Autocomplete
multiple
id="tags-outlined"
classes={{
option: classes.option
}}
options={state.familyMembers}
getOptionLabel={(option) => option.name}
// defaultValue={[familyMembers[0]]}
filterSelectedOptions
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="Members Attending"
placeholder="Family Member"
/>
)}
/>
</Form>
</>
);
}
// Redux
const mapStateToProps = (state) => {
return {
family: state.auth.family
}
}
export default connect(mapStateToProps)(EventForm);
Wow, this took a while but the solution seems to use 'renderTags' algong with
here is the exact solution
import React, { useEffect } from 'react'
import { useForm, Form } from './hooks/useForm'
import EventIcon from '#material-ui/icons/Event';
import { makeStyles, TextField, Typography } from '#material-ui/core'
import CustomTextField from './inputs/CustomTextField';
import { Autocomplete } from '#material-ui/lab';
import { connect } from 'react-redux'
import Chip from '#material-ui/core/Chip';
import getColorvalue from './outputs/ColorValues'
const EventForm = (props) => {
// Redux
const { family } = props
// React
const initialState = {
email: "",
password: "",
errors: {
email: "",
password: ""
},
familyMembers: ["rgeg"]
}
const { state, handleOnChange, setState } = useForm(initialState)
useEffect(() => {
family && state.familyMembers !== family.members && setState({
...state,
familyMembers: family.members
})
})
// Material UI
const useStyles = makeStyles(theme => (
{
message: {
marginTop: theme.spacing(3)
},
icon: {
backgroundColor: "lightgrey",
padding: "10px",
borderRadius: "50px",
border: "2px solid #3F51B5",
marginBottom: theme.spacing(1)
},
typography: {
marginBottom: theme.spacing(1),
marginTop: theme.spacing(4)
},
customTextField: {
marginTop: theme.spacing(0)
},
dateTimeWrapper: {
marginTop: theme.spacing(4)
}
}
))
const classes = useStyles()
return (
<>
<div>WORK IN PROGRESS...</div>
<br />
<br />
<EventIcon className={classes.icon} />
<Form
title="Add new event"
>
<Typography
variant="subtitle1"
className={classes.typography}
align="left">
Enter a title for this event
</Typography>
<CustomTextField
className={classes.customTextField}
label="Title"
/>
<Typography
variant="subtitle1"
className={classes.typography}
align="left">
Enter a location for this event
</Typography>
<CustomTextField
className={classes.customTextField}
label="Location"
/>
<Typography
variant="subtitle1"
className={classes.typography}
align="left">
Which member/s of the family is/are attending
</Typography>
<Autocomplete
multiple
id="tags-outlined"
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="outlined"
key={option}
style={{
backgroundColor: `${getColorvalue(state.familyMembers[state.familyMembers.indexOf(option)].color)}`,
color: "white"
}}
label={option.name}
onDelete={() => console.log("test")}
{...getTagProps({ index })}
/>
))
}
options={state.familyMembers}
getOptionLabel={(option) => option.name}
// defaultValue={[familyMembers[0]]}
filterSelectedOptions
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="Members Attending"
placeholder="Family Member"
/>
)}
/>
</Form>
</>
);
}
// Redux
const mapStateToProps = (state) => {
return {
family: state.auth.family
}
}
export default connect(mapStateToProps)(EventForm);

Adding image draft-js-image-plugin

I've tried multiple different ways to try to attach an image in draft-js.
My attempts followed the example in the tutorial https://www.draft-js-plugins.com/plugin/image. For the "Add Image Button Example", the full source of which can be found here https://github.com/draft-js-plugins/draft-js-plugins/tree/master/docs/client/components/pages/Image/AddImageEditor. I have also tried to follow this medium article that does not use the image plugin to do a similar thing https://medium.com/#siobhanpmahoney/building-a-rich-text-editor-with-react-and-draft-js-part-2-4-persisting-data-to-server-cd68e81c820.
If I set the initial state with the image already embedded it renders. However, when using a mechanism that adds it later on it doesn't seem to work. I've read some mentions of various version of plugins / draft.js having issues.
My versions:
"draft-js-image-plugin": "^2.0.7",
"draft-js": "^0.11.6",
import { EditorState } from 'draft-js';
import Editor from 'draft-js-plugins-editor';
import createInlineToolbarPlugin, { Separator } from 'draft-js-inline-toolbar-plugin';
import createImagePlugin from 'draft-js-image-plugin';
import 'draft-js/dist/Draft.css';
import 'draft-js-inline-toolbar-plugin/lib/plugin.css';
import {
ItalicButton,
BoldButton,
UnderlineButton,
HeadlineOneButton,
CodeButton,
UnorderedListButton,
OrderedListButton,
BlockquoteButton,
} from 'draft-js-buttons';
interface TextEditorProps {
label?: string;
value: EditorState;
onChange?: (editorState: EditorState) => void;
className?: string;
disabled?: boolean;
}
const useStyles = makeStyles((theme) => ({
label: {
margin: theme.spacing(1)
},
editor: {
boxSizing: 'border-box',
border: '1px solid #ddd',
cursor: 'text',
padding: '16px',
borderRadius: '2px',
marginBottom: '2em',
boxShadow: 'inset 0px 1px 8px -3px #ABABAB',
background: '#fefefe',
minHeight: '50vh'
}
}));
const inlineToolbarPlugin = createInlineToolbarPlugin();
const imagePlugin = createImagePlugin();
const { InlineToolbar } = inlineToolbarPlugin;
const plugins = [inlineToolbarPlugin, imagePlugin];
const TextEditor:React.FunctionComponent<TextEditorProps> = ({label, value, onChange, className, disabled }: TextEditorProps) => {
const editor = React.useRef(null);
const focusEditor = () => {
editor.current.focus();
}
React.useEffect(() => {
focusEditor()
}, []);
const classes = useStyles();
const insertImage = () => {
onChange(imagePlugin.addImage(value, "https://ichef.bbci.co.uk/news/976/cpsprodpb/12A9B/production/_111434467_gettyimages-1143489763.jpg"));
}
return (
<Box className={className} onClick={focusEditor}>
{label && <InputLabel className={classes.label}>{label}</InputLabel>}
<div className="menuButtons">
<button className="inline styleButton">
<i
className="material-icons"
style={{
fontSize: "16px",
textAlign: "center",
padding: "0px",
margin: "0px"
}}
onClick={insertImage}
>
image
</i>
</button>
</div>
<Box className={!disabled && classes.editor} >
<Editor
ref={editor}
editorState={value}
onChange={onChange}
plugins={plugins}
spellCheck={true}
readOnly={disabled}
/>
{<InlineToolbar>
{(externalProps) => (
<>
<BoldButton {...externalProps} />
<ItalicButton {...externalProps} />
<UnderlineButton {...externalProps} />
<CodeButton {...externalProps} />
<Separator {...externalProps} />
<UnorderedListButton {...externalProps} />
<OrderedListButton {...externalProps} />
<BlockquoteButton {...externalProps} />
<HeadlineOneButton {...externalProps} />
</>
)}
</InlineToolbar>}
</Box>
</Box>
)
}
You editorState change is overriding the change from insertImage. You are triggering focusEditor along with onClick insertImage
Don't need additional plugins, if you don't mind getting hands dirty and learning how to insert into content.
<i
className="material-icons"
style={{
fontSize: "16px",
textAlign: "center",
padding: "0px",
margin: "0px"
}}
onClick={() => insertImage('https://ichef.bbci.co.uk/news/976/cpsprodpb/12A9B/production/_111434467_gettyimages-1143489763.jpg')}
>
custom insertImage function, in your case, editorState should be replaced with 'value'
import { EditorState, AtomicBlockUtils } from “draft-js”; //<-- need add this import
const insertImage = ( url ) => {
const contentState = editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity(
'IMAGE',
'IMMUTABLE',
{ src: url },)
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const newEditorState = EditorState.set( editorState, { currentContent: contentStateWithEntity });
onChange(AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, '')) //Update the editor state
};
image upload with draftjs

Resources