I have the Material UI Country select Autocomplete, but the chosen option is not showing to the frontend, already configured a code to save the option to the server, but failed to show it on the frontend.
CountrySelect.jsx:
export default function CountrySelect({ user, country, setCountry, defaultValue, getOptionLabel }) {
// onChange={(e) => setCountry(e.target.value)}
return (
<Autocomplete
id="country-select-demo"
sx={{ maxWidth: true, background: '#f3f3f4' }}
options={countries}
defaultValue={{ label: `${user.country}` }}
autoHighlight
getOptionLabel={(option) => option.label}
renderOption={(props, option) => (
<Box component="li" sx={{ '& > img': { mr: 2, flexShrink: 0 } }} {...props}>
<img
loading="lazy"
width="20"
src={`https://flagcdn.com/w20/${option.code.toLowerCase()}.png`}
srcSet={`https://flagcdn.com/w40/${option.code.toLowerCase()}.png 2x`}
alt=""
/>
{option.label} ({option.code}) +{option.phone}
</Box>
)}
renderInput={(params) => (
<TextField
{...params}
label="Choose a country"
inputProps={{
...params.inputProps,
autoComplete: 'new-password', // disable autocomplete and autofill
}}
defaultValue=""
onBlur={(e) => setCountry(e.target.value)}
/>
)}
/>
);
}
const countries = [
// COUNTRIES
];
EditProfile.jsx:
import { useContext, useState } from "react"
import { Context } from "../../context/Context";
import CountrySelect from "../../components/countrySelect/CountrySelect";
export default function Settings() {
const [country, setCountry] = useState("");
const { user, dispatch, isFetching } = useContext(Context);
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({type: "UPDATE_START"})
const updatedUser = {
userId: user._id,
firstName,
lastName,
birthday,
country,
};
try {
const res = await axios.put("/users/"+user._id, updatedUser);
setSuccess(true);
dispatch({ type: "UPDATE_SUCCESS", payload: res.data });
} catch (err) {
dispatch({type: "UPDATE_FAILURE"});
}
};
return(
<form className="settingsForm" onSubmit={handleSubmit}>
<CountrySelect
country={country}
setCountry={setCountry}
defaultValue={user.country} />
</form>
)
}
The code is working fine, I get the user input option saved in the server (Mongodb), but I want to show the option to the frontend like a placeholder or default value! just want to show the chosen option, Now it's only showing Choose a country
Related
here in this code I am able to upload only a single image and upload it to cloudinary api and it works fine, the PROBLEM is it keeps only sending only a single image even though i've specified multiple in the input tag and it loops after it has the event target files and append them to the formData, while what I want is to upload as many images as I want and I have the mongodb model image field as an array. so backend is very solid and doesn't have a problem. the issue is on the front end not being able to send an array of images. it only sends one. i have removed the react hook form validation just incase to see if it works
import axios from 'axios';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { useEffect, useReducer, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { toast } from 'react-toastify';
import { getError } from '../../utils/error';
import { tokens } from '../../utils/theme';
import {
Grid,
Box,
List,
ListItem,
Typography,
Card,
Button,
ListItemText,
TextField,
useTheme,
CircularProgress,
FormControlLabel,
Checkbox,
} from '#mui/material';
import Header from '../../components/Header';
import Topbar from '../../components/global/Topbar';
function reducer(state, action) {
switch (action.type) {
case 'FETCH_REQUEST':
return { ...state, loading: true, error: '' };
case 'FETCH_SUCCESS':
return { ...state, loading: false, error: '' };
case 'FETCH_FAIL':
return { ...state, loading: false, error: action.payload };
case 'UPDATE_REQUEST':
return { ...state, loadingUpdate: true, errorUpdate: '' };
case 'UPDATE_SUCCESS':
return { ...state, loadingUpdate: false, errorUpdate: '' };
case 'UPDATE_FAIL':
return { ...state, loadingUpdate: false, errorUpdate: action.payload };
case 'UPLOAD_REQUEST':
return { ...state, loadingUpload: true, errorUpload: '' };
case 'UPLOAD_SUCCESS':
return {
...state,
loadingUpload: false,
errorUpload: '',
};
case 'UPLOAD_FAIL':
return { ...state, loadingUpload: false, errorUpload: action.payload };
default:
return state;
}
}
export default function AdminProductEditScreen() {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const [isFeatured, setIsFeatured] = useState(false);
const [inStock, setInStock] = useState(false);
const [imports, setImports] = useState(false);
const [exports, setExports] = useState(false);
const [image, setImage] = useState([]);
const [category, setCategory] = useState([]);
const { query } = useRouter();
const productId = query.id;
const [
{ loading, error, loadingUpdate, loadingUpload }
dispatch,
] = useReducer(reducer, {
loading: true,
error: '',
});
const {
register,
handleSubmit,
control,
formState: { errors },
setValue,
} = useForm();
useEffect(() => {
const fetchData = async () => {
try {
dispatch({ type: 'FETCH_REQUEST' });
const { data } = await axios.get(`/api/admin/products/${productId}`);
dispatch({ type: 'FETCH_SUCCESS' });
setValue('name', data.name);
setValue('slug', data.slug);
setValue('headerTitle', data.headerTitle);
setImports(data.imports);
setExports(data.exports);
setValue('image', data.image);
} catch (err) {
dispatch({ type: 'FETCH_FAIL', payload: getError(err) });
}
};
fetchData();
}, [productId, setValue]);
const router = useRouter();
const uploadHandler = async (e, imageField = 'image') => {
const url = `https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/upload`;
try {
dispatch({ type: 'UPLOAD_REQUEST' });
const {
data: { signature, timestamp },
} = await axios('/api/admin/cloudinary-sign');
const files = Array.from(e.target.files);
const formData = new FormData();
files.forEach((file) => {
formData.append('file', file);
});
formData.append('signature', signature);
formData.append('timestamp', timestamp);
formData.append('api_key', process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY);
const { data } = await axios.post(url, formData);
dispatch({ type: 'UPLOAD_SUCCESS' });
setValue(imageField, data.secure_url);
toast.success('File uploaded successfully');
} catch (err) {
dispatch({ type: 'UPLOAD_FAIL', payload: getError(err) });
toast.error(getError(err));
}
};
const updateCategory = (e) => {
const cats = e.target.files.split(',');
cats?.map((cat) => {
console.log(cat);
setCategory(cat);
});
};
const submitHandler = async ({
name,
slug,
headerTitle,
category,
price,
image,
}) => {
try {
dispatch({ type: 'UPDATE_REQUEST' });
await axios.put(`/api/admin/products/${productId}`, {
name,
slug,
headerTitle,
imports,
exports,
category,
image,
});
dispatch({ type: 'UPDATE_SUCCESS' });
toast.success('Product updated successfully');
router.push('/products');
} catch (err) {
dispatch({ type: 'UPDATE_FAIL', payload: getError(err) });
toast.error(getError(err));
}
};
return (
<Box title={`Edit Product ${productId}`} m="20px">
<Box display="flex" justifyContent="space-between">
<Header title="Product Edit" subtitle="List of Product to be edited" />
<Topbar />
</Box>
{loading && <CircularProgress></CircularProgress> ? (
<Box>Loading...</Box>
) : error ? (
<Box color="error">{error}</Box>
) : (
<ListItem width="100%">
<form onSubmit={handleSubmit(submitHandler)}>
<List sx={{ width: '60vw', border: '2px solid red' }}>
<ListItem>
<Controller
name="name"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id="name"
label="Name"
error={Boolean(errors.name)}
helperText={errors.name ? 'Name is required' : ''}
{...field}
></TextField>
)}
></Controller>
</ListItem>
<ListItem>
<Controller
name="slug"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id="slug"
label="Slug"
error={Boolean(errors.slug)}
helperText={errors.slug ? 'Slug is required' : ''}
{...field}
></TextField>
)}
></Controller>
</ListItem>
<ListItem>
<Controller
name="headerTitle"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id="headerTitle"
label="header Title"
error={Boolean(errors.headerTitle)}
helperText={
errors.headerTitle ? 'headertitle is required' : ''
}
{...field}
></TextField>
)}
></Controller>
</ListItem>
<ListItem>
<FormControlLabel
label="Import"
control={
<Checkbox
onClick={(e) => setImports(e.target.checked)}
checked={imports}
name="imports"
/>
}
></FormControlLabel>
</ListItem>
<ListItem>
<FormControlLabel
label="Export"
control={
<Checkbox
onClick={(e) => setExports(e.target.checked)}
checked={exports}
name="exports"
/>
}
></FormControlLabel>
</ListItem>
<ListItem>
<Controller
name="price"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id="price"
label="Price"
{...field}
></TextField>
)}
></Controller>
</ListItem>
<ListItem>
<Controller
name="image"
control={control}
defaultValue=""
rules={{
required: true,
}}
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id="image"
label="Image"
{...field}
></TextField>
)}
></Controller>
</ListItem>
<ListItem>
<Button variant="contained" component="label">
Upload File
<input
type="file"
onChange={(e) => uploadHandler(e)}
hidden
multiple
/>
</Button>
{loadingUpload && <CircularProgress className="mx-4" />}
</ListItem>
{/** BUTTON */}
<ListItem>
<Button
variant="contained"
type="submit"
fullWidth
color="primary"
>
Update
</Button>
{loadingUpdate && <CircularProgress />}
</ListItem>
</List>
</form>
</ListItem>
)}
</Box>
);
}
AdminProductEditScreen.auth = { adminOnly: true };
i have tried in the uploadHandler function to loop through the e.target.files but only keeps sending a single image to the cloudinary api and its uploaded there and i can see it, even though i have selected multiple image files
It looks like you're trying to send a single request to Cloudinary's API, where that single request has multiple values for file.
That's not supported by the Cloudinary API - each call to the API should specify a single file: https://cloudinary.com/documentation/image_upload_api_reference#upload_required_parameters
You should change your code so that when you loop through the array of files, you make a separate call to the API for each one, then continue after all files are uploaded.
I creating a BlogSite using Reactjs
I have trouble when I update a post. After I clicked the update icon, the form data fill according to the post id. but I can't input anything it's like as readonly
Get data according to id
<Button style={{ color: 'white' }} size='small' onClick={() =>updatePost(post._id)}>
<MoreHorizIcon />
</Button>
dispatch action
const updatePost = (id) => {dispatch(getPost(id))}
action code
try {
dispatch({ type: POST_GET_REQUEST })
const { data } = await axios.get(`http://localhost:5000/posts/get/${id}`)
dispatch({
type: POST_GET_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: POST_GET_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
})
}
}
reducer code
switch (action.type) {
case POST_GET_REQUEST:
return { loading: true, post: [] }
case POST_GET_SUCCESS:
return { loading: false, success: true, post: action.payload }
case POST_GET_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
store code
const reducer = combineReducers({postGet: postGetReducer})
FormScreen.js
import { useDispatch, useSelector } from 'react-redux'
import { TextField, Typography, Paper } from '#mui/material'
import { createPost } from '../actions/postsAction'
const FormScreen = () => {
const dispatch = useDispatch()
const postGet = useSelector((state) => state.postGet)
const [newUser, setNewAuthor] = useState({
creator: '',
title: '',
message: '',
tags: '',
photo: '',
})
const handleChange = (e) => {
setNewAuthor({ ...newUser, [e.target.name]: e.target.value })
}
const handlePhoto = (e) => {
setNewAuthor({ ...newUser, photo: e.target.files[0] })
}
const handleSubmit = (e) => {
e.preventDefault()
const formData = new FormData()
formData.append('photo', newUser.photo)
formData.append('creator', newUser.creator)
formData.append('title', newUser.title)
formData.append('message', newUser.message)
formData.append('tags', newUser.tags)
dispatch(createPost(formData))
}
return (
<Paper>
<form
onSubmit={handleSubmit}
encType='multipart/form-data'
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
margin: '20px',
padding: '10px',
}}
>
<Typography variant='h5'>Creating a Memory</Typography>
<TextField
type='text'
label='Creator'
fullWidth
variant='outlined'
placeholder='Creator'
name='creator'
value={postGet.post.creator} // value={newUser.creator}
onChange={handleChange}
/>
<TextField
type='text'
fullWidth
variant='outlined'
label='Title'
placeholder='Title'
name='title'
value={postGet.post.title} // value={newUser.title}
onChange={handleChange}
/>
<TextField
type='text'
placeholder='Message'
label='Message'
fullWidth
variant='outlined'
name='message'
value={postGet.post.message} // value={newUser.message}
onChange={handleChange}
/>
<TextField
type='text'
placeholder='Tags'
label='Tags'
fullWidth
variant='outlined'
name='tags'
value={postGet.post.tags} // value={newUser.tags}
onChange={handleChange}
/>
<TextField
type='file'
accept='.png, .jpg, .jpeg'
fullWidth
name='photo'
onChange={handlePhoto}
/>
<div className='bg'></div>
<TextField type='submit' />
<TextField type='button' value='Clear' />
</form>
</Paper>
)
}
export default FormScreen
Create post success. I create post and update post in same form
I have this project and in this project I have these two files, the first file is the form file, and the second file is the service file, and I want to access the answer contained within the service file, but I have this error:
Uncaught TypeError: Object(...)(...).then is not a function
how can i solve it?
And this file contains a form, and through this form, I want to call the getjobs function
import InputAdornment from "#material-ui/core/InputAdornment";
import TextField from "#material-ui/core/TextField";
import { useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { getJobs } from "../../store/salaryScalesSlice";
import { useDispatch, useSelector } from "react-redux";
import { Autocomplete } from "#material-ui/lab";
function ShippingTab(props) {
const methods = useFormContext();
const { control } = methods;
const dispatch = useDispatch();
useEffect(() => {
dispatch(getJobs());
}, [dispatch]);
const [jobs, setJobs] = useState([]);
useEffect(() => {
getJobs().then((response) => setJobs(response));
}, []);
console.log("jobs: ", jobs);
return (
<div>
<div className="flex -mx-4">
<Controller
name="job"
control={control}
defaultValue={[]}
render={({ field: { onChange, value } }) => (
<Autocomplete
disablePortal
id="combo-box-demo"
style={{ width: 900 }}
className="mt-8 mb-16"
options={jobs || []}
getOptionLabel={(option) => option.name || ""}
value={value}
onChange={(event, newValue) => {
onChange(newValue);
}}
renderInput={(params) => (
<TextField
{...params}
placeholder="Select Job Name"
label="Job"
variant="outlined"
fullWidth
InputLabelProps={{
shrink: true,
}}
/>
)}
/>
)}
/>
<Controller
name="employeeLevel"
control={control}
defaultValue={[]}
render={({ field: { onChange, value } }) => (
<Autocomplete
id="employeeLevel"
style={{ width: 900 }}
className="mt-8 mb-16 mx-4"
options={employeeLevels || []}
getOptionLabel={(option) => option.employeeLevel || ""}
value={value}
onChange={(event, newValue) => {
onChange(newValue);
}}
renderInput={(params) => (
<TextField
{...params}
placeholder="Select Employee Level"
label="Employee Level"
variant="outlined"
fullWidth
InputLabelProps={{
shrink: true,
}}
/>
)}
/>
)}
/>
<Controller
name="amount"
control={control}
render={({ field }) => (
<TextField
{...field}
className="mt-8 mb-16"
label="Amount"
id="amount"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">$</InputAdornment>
),
}}
fullWidth
/>
)}
/>
</div>
</div>
);
}
export default ShippingTab;
const employeeLevels = [
{ employeeLevel: "senior" },
{ employeeLevel: "junior" },
{ employeeLevel: "mid_level" },
];
service.js:
export const getJobs = createAsyncThunk("jobsRequests/getJobs", async () => {
const response = await axios.get("/jobs").then((res) => {
const jobsRequestsData = res.data.data;
console.log("jobsRequestsData: ", jobsRequestsData);
return jobsRequestsData;
});
return response;
})
You are mixing promises and async/await toghether which leads to unexpected behaviour and therefore should be avoided.
The await keyword returns the response on which you cannot call .then()
If you want to stick with the async/await method change getjobs function to something as follows (not tested, just to give an idea)
const getJobs = async () => {
const response = await axios.get("/jobs");
return response.data.data;
}
otherwise you could remove the async/await keyowrds and return a promise.
The second problem is the createAsyncThunk.
The error you get is in the useEffect hook.
As the documentation says:
The thunks generated by createAsyncThunk will always return a resolved
promise
You should implement the full redux flow for setting the props. See documentation or remove it.
You dont need to return twice, just remove the return response:
export const getJobs = createAsyncThunk("jobsRequests/getJobs", async () => {
const response = await axios.get("/jobs").then((res) => {
return res.data.data
});
})
I have a react from made with MUI (I'm using MUI v5))
Everything works ok, until I hit submit.
The submitted values are ok except for the select, which is delayed, I mean, suppose I select the first value, and the default value is 0, then hitting submit will say that the selected value is 0, then, if I hit submit once more, immediately, then it says the correct answer, see, it’s kind of delayed by one render of the component.
I wanted to have all values in one object state, but I had to separate some of them, like a date value (uploadDate) and the select value (category value)
This is the simplified code:
const AddVideoAndCategories = ({
addVideo,
getAllCategoriesWithoutVideos,
categories,
categoriesLoaded,
resetDrawerMenuItems,
addDrawerItem,
setCurrentDrawerMenuItems
}) => {
const [ formValues, setFormValues ] = useState(defaultValues);
const [ uploadDate, setUploadDate ] = React.useState(today());
const [ selectedCategory, setSelectedCategory ] = useState('');
const { handleSubmit, reset, control } = useForm();
const navigate = useNavigate();
useEffect(() => {
if (!categoriesLoaded) {
/// Load the categories from the back end to redux
}
}, []);
const handleCategoryChange = (e) => {
setSelectedCategory(e.target.value);
};
const handleInputChange = (e) => {
let { name, value } = e.target;
setFormValues({
...formValues,
[name]: value
});
};
async function handleCtrlClick(target) {
/// allow pasting the clipboard with just ctrl click
}
const handleClick = (event) => {
// In that case, event.ctrlKey does the trick.
if (event.ctrlKey) {
event.stopPropagation();
handleCtrlClick(event.target);
}
};
const myHandleSubmit = () => {
let arr = selectedCategory.split('^'); /// This trick allowed me to show the selected category name instead of the categoryId
setFormValues({
...formValues,
uploadDate: uploadDate,
categoryId: arr[0]
});
alert(JSON.stringify(formValues, null, 4));
};
const handleReset = () => {
/// resets all fields
};
return (
<form onSubmit={handleSubmit}>
<Box
component="form"
sx={{
'& > :not(style)': { m: 1, width: '30ch', marginTop: '50px', marginBottom: '0' }
}}
noValidate
autoComplete="off"
>
<Stack spacing={3}>
<TextField
…
/>
<TextField
…
/>
<TextField
…
/>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
id="author-uploadDate"
name="uploadDate"
label="Video UploadDate"
value={uploadDate}
defaultValues={uploadDate}
onChange={(newValue) => {
setUploadDate(newValue);
}}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
<TextField
…
/>
<FormControl sx={{ minWidth: 120, maxWidth: '500px', width: '270px' }}>
<InputLabel id="video categories">Video Category</InputLabel>
<Select
labelId="video categories"
id="simple-select"
value={selectedCategory}
label="Video Category"
onChange={handleCategoryChange}
>
{categories.map((category) => {
return (
<MenuItem value={category.id.toString() + '^' + category.name}>
{category.name}
</MenuItem>
);
})}
</Select>
</FormControl>
<TextField
…
/>
</Stack>
<Button variant="contained" endIcon={<SendIcon />} size="medium" onClick={myHandleSubmit}>
Submit
</Button>
<Button variant="outlined" startIcon={<DeleteIcon />} size="medium" onClick={handleReset}>
Reset
</Button>
</Box>
</form>
);
};
// title, origin, author, uploadDate, url, excerpt
const defaultValues = {
title: '',
origin: 'Vimeo',
author: '',
uploadDate: today(),
categoryId: 0,
url: '',
excerpt: ''
};
function mapStateToProps(state) {
…
}
function mapDispatchToProps(dispatch) {
…
}
export default connect(mapStateToProps, mapDispatchToProps)(AddVideoAndCategories);
Can somebody see why that happens?
I want to use onChange event on Autocomplete component to get current selected values.
The problem is that it does not working as expected, so when I click to check/uncheck value checkbox is still unchecked but in console i can see that new value was added
uncoment this part to make it works:
value={myTempVal}
onChange={(event, newValue) => {
setMyTempVal(newValue);
console.log(newValue);
}}
online demo:
https://codesandbox.io/embed/hardcore-snowflake-7chnc?fontsize=14&hidenavigation=1&theme=dark
code:
const [myTempVal, setMyTempVal] = React.useState([]);
<Autocomplete
open
multiple
value={myTempVal}
onChange={(event, newValue) => {
setMyTempVal(newValue);
console.log(newValue);
}}
disableCloseOnSelect
disablePortal
renderTags={() => null}
noOptionsText="No labels"
renderOption={(option, { selected }) => {
return (
<>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.title}
</>
);
}}
options={option2}
// groupBy={option => option.groupName}
getOptionLabel={option => option.title}
renderInput={params => (
<div>
<div>
<SearchIcon />
</div>
<TextField
variant="outlined"
fullWidth
ref={params.InputProps.ref}
inputProps={params.inputProps}
/>
</div>
)}
/>
You need to get donors receivers and options variables out of the function. Those variables get re-created at each render, this means that their reference changes at each render, and as Autocomplete makes a reference equality check to decide if an option is selected he never finds the options selected.
const donors = [...new Set(data.map(row => row.donor))].map(row => {
return {
groupName: "Donors",
type: "donor",
title: row || "null"
};
});
const receivers = [...new Set(data.map(row => row.receiver))].map(row => {
return {
groupName: "Receivers",
type: "receiver",
title: row || "null"
};
});
const option2 = [...donors, ...receivers];
export const App = props => {
const [myTempVal, setMyTempVal] = React.useState([]);
return (
<Autocomplete
open
multiple
...
You can also add getOptionSelected to overwrite the reference check :
<Autocomplete
open
multiple
disableCloseOnSelect
disablePortal
renderTags={() => null}
noOptionsText="No labels"
getOptionSelected={(option, value) => option.title === value.title}
renderOption={(option, { selected }) => {
return (
<>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.title}
</>
);
}}
options={option2}
// groupBy={option => option.groupName}
getOptionLabel={option => option.title}
renderInput={params => (
<div>
<div>
<SearchIcon />
</div>
<TextField
variant="outlined"
fullWidth
ref={params.InputProps.ref}
inputProps={params.inputProps}
/>
</div>
)}
/>
This can help:
Replace
checked={selected}
To
checked={myTempVal.filter(obj=>obj.title===option.title).length!==0}
The complete solution
import React from "react";
import "./styles.css";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import CheckBoxOutlineBlankIcon from "#material-ui/icons/CheckBoxOutlineBlank";
import CheckBoxIcon from "#material-ui/icons/CheckBox";
import Checkbox from "#material-ui/core/Checkbox";
import SearchIcon from "#material-ui/icons/Search";
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
const data = [
{ donor: "Trader Joe's", receiver: "Person-to-Person" },
{ donor: "Trader Joe's", receiver: "Homes with Hope" },
{ donor: "Santa Maria", receiver: "Gillespie Center" },
{ donor: "Santa Maria", receiver: null }
];
export const App = props => {
const donors = [...new Set(data.map(row => row.donor))].map(row => {
return {
groupName: "Donors",
type: "donor",
title: row || "null"
};
});
const receivers = [...new Set(data.map(row => row.receiver))].map(row => {
return {
groupName: "Receivers",
type: "receiver",
title: row || "null"
};
});
const option2 = [...donors, ...receivers];
const [myTempVal, setMyTempVal] = React.useState([]);
return (
<Autocomplete
open
multiple
value={myTempVal}
disableCloseOnSelect
disablePortal
renderTags={() => null}
noOptionsText="No labels"
renderOption={(option, { selected }) => {
return (
<>
<Checkbox
onClick={
()=>{
if(myTempVal.filter(obj=>obj.title===option.title).length!==0){
setMyTempVal([...myTempVal.filter(obj=>obj.title!==option.title)],console.log(myTempVal))
}else{
setMyTempVal([...myTempVal.filter(obj=>obj.title!==option.title),option],console.log(myTempVal))
}
}
}
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={myTempVal.filter(obj=>obj.title===option.title).length!==0}
/>
{option.title}
</>
);
}}
options={option2}
// groupBy={option => option.groupName}
getOptionLabel={option => option.title}
renderInput={params => (
<div>
<div>
<SearchIcon />
</div>
<TextField
variant="outlined"
fullWidth
ref={params.InputProps.ref}
inputProps={params.inputProps}
/>
</div>
)}
/>
);
};
export default App;
It is bit late to Answer this question but it might help someone.
In your code you have added onChange event in Autocomplete. When you click on checkbox it will trigger 2 times, one for checkbox and one for Autocomplte. Hence 2nd time trigger makes again checkbox unchecked so u get value in console but still checkbox is empty.
You can remove your checkbox in renderOption and use checked and uncheked icon instaed of checkbox.
renderOption={(option, { selected }) => {
return (
<React.Fragment>
{selected ? <CheckedIcon> : <uncheckedIcon>}
<div>
{option.title}
</div>
</React.Fragment>
</>
);
}}