I have a custom Input Box and when I type inside a custom input component It'll re-render the typed input inside.
import {
Badge,
Box,
CloseButton,
Grid,
GridItem,
Input,
Text
} from "#chakra-ui/react";
import React, { useEffect, useState } from "react";
function InputTag(props) {
const [tags, setTags] = useState(props.values);
const removeTag = (index) => {
setTags(tags.filter((_, i) => i !== index));
};
const addTag = (event) => {
if (event.target.value !== "") {
setTags([...tags, event.target.value]);
props.setFieldValue("tags", [...tags, event.target.value]);
event.target.value = "";
}
};
useEffect(() => {
props.show === false && setTags([]);
}, [props.show]);
//update values based on click suggestions
useEffect(() => {
setTags([props.values, props.suggTag]);
}, [props.suggTag, props.values]);
return (
<Box
display={"flex"}
border="1px"
borderColor={"gray.200"}
borderRadius={"md"}
padding={2}
>
<Grid templateColumns="repeat(3, 1fr)" gap={2} overflow="visible">
{tags &&
tags.map((tag, index) => (
<GridItem key={index}>
<Badge
variant="solid"
colorScheme={"purple"}
display={"flex"}
borderRadius="full"
justifyContent="space-between"
alignItems="center"
gap={2}
>
<Text>{tag}</Text>
<CloseButton onClick={() => removeTag(index)} />
</Badge>
</GridItem>
))}
</Grid>
<Input
type="text"
name="tags"
id="tags"
variant={"unstyled"}
placeholder="Add Tag"
_placeholder={{ fontsize: "md" }}
onChange={props.handleChange}
onBlur={props.handleBlur}
onError={props.errors}
onKeyUp={(event) =>
event.key === "Enter" ? addTag(event) && event.preventDefault() : null
}
/>
</Box>
);
}
export default InputTag;
Here, when I hit enter It'll render them inside the custom Input Box
I Inserted a custom array of strings as "ex_Tag" inside Previewer.js so that when I click on the word in array, it'll also get rendered inside custom input as well.
function NewUploader({ isOpen, onClose }) {
const cancelRef = useRef();
const ex_tags = ["Design", "Strategy", "Human Centered Design"];
const [show, Setshow] = useState(true);
const [suggTag, setSuggTag] = useState();
const initialValues = {
files: null,
tags: []
};
const validationSchema = yup.object({
files: yup.mixed().required("File is Required"),
tags: yup.mixed().required("tags required")
});
const onSubmit = (values, actions) => {
const formData = new FormData();
formData.append("files", values.files[0]);
formData.append("tags", values.tags);
for (var pair of formData.entries()) {
console.log(pair[0] + ", " + pair[1]);
}
actions.setSubmitting(false);
actions.resetForm();
Setshow(!show);
onClose();
};
const handlethis = (e) => {
e.preventDefault();
};
//insert suggested word to useState so i can pass it to custom input
const handleClick = (tag) => {
setSuggTag(tag);
};
return (
<Modal isOpen={isOpen} onClose={onClose} isCentered>
{/* update code on model here */}
<ModalOverlay />
<ModalContent>
<ModalHeader>
<Text fontWeight={"bold"} color="gray.900">
Upload Buddy
</Text>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex direction="column" gap={3}>
<Box>
<Text fontWeight={"normal"} color="gray.700">
This learning contentwill not be summarised. to summarize your
content, use{" "}
<Link color={"purple.400"}>Create Knowledge Nugget</Link> option
instead.
</Text>
</Box>
<Box>
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={validationSchema}
>
{(formik) => (
<Form
onSubmit={handlethis}
autoComplete="off"
encType="multipart/form-data"
>
<FormLabel htmlFor="file">
<Text
fontSize="sm"
fontWeight="normal"
color="gray.900"
fontFamily={"body"}
>
Upload files
</Text>
</FormLabel>
{/* drag droop sec */}
{formik.isSubmitting ? (
<>
<Grid
templateColumns="repeat(3, 1fr)"
gap={2}
overflow="hidden"
>
{formik.values.files &&
formik.values.files.map((file, index) => (
<GridItem key={index}>
<Badge
variant="solid"
borderRadius="xl"
colorScheme={"gray"}
w={file.name.length * 4}
h="8"
display="flex"
justifyContent="center"
alignItems="center"
my={2}
>
<Text fontFamily={"body"}>{file.name}</Text>
<CloseButton colorScheme={"blackAlpha"} />
</Badge>
</GridItem>
))}
</Grid>
<Progress colorScheme={"yellow"} isIndeterminate />
</>
) : (
<>
<Dragdrop setFieldValue={formik.setFieldValue} />
<Grid
templateColumns="repeat(3, 1fr)"
gap={2}
overflow="hidden"
>
{formik.values.files &&
formik.values.files.map((file, index) => (
<GridItem key={index}>
<Badge
variant="solid"
borderRadius="xl"
colorScheme={"gray"}
w={file.name.length * 4}
h="8"
display="flex"
justifyContent="space-between"
alignItems="center"
my={2}
>
<Text fontFamily={"body"}>{file.name}</Text>
<CloseButton colorScheme={"blackAlpha"} />
</Badge>
</GridItem>
))}
</Grid>
{formik.errors.files && formik.touched.files && (
<Text fontFamily={"body"} color="red">
{formik.errors.files}
</Text>
)}
</>
)}
<FormErrorMessage>
<ErrorMessage name="file" />
</FormErrorMessage>
<FormLabel htmlFor="tags">
<Text
fontSize="sm"
fontWeight="normal"
color="gray.900"
fontFamily={"body"}
>
Tags
</Text>
</FormLabel>
<InputTag
setFieldValue={formik.setFieldValue}
handleChange={formik.handleChange}
handleBlur={formik.handleBlur.call}
values={formik.values.tags}
show={show}
suggTag={suggTag}
/>
{formik.errors.tags && formik.touched.tags && (
<Text fontFamily={"body"} color="red">
{formik.errors.tags}
</Text>
)}
<FormErrorMessage>
<ErrorMessage name="tags" />
</FormErrorMessage>
<Box
aria-invalid="true"
display={"flex"}
flexDir="row"
gap={2}
my={2}
>
<Text fontFamily={"body"}>Suggested</Text>
<Grid
templateColumns="repeat(3, 1fr)"
gap={2}
overflow="hidden"
>
{ex_tags.map(
(tag, index) => (
<GridItem key={index}>
//I inserted on click call here
<Box onClick={handleClick(tag)}>
<Badge
variant={"subtle"}
borderRadius="lg"
colorScheme={"gray"}
_hover={{
cursor: "pointer",
bgColor: "gray.200"
}}
>
<Text fontFamily={"body"}>{tag}</Text>
</Badge>
</Box>
</GridItem>
),
this
)}
</Grid>
</Box>
<Box display={"flex"} justifyContent="center" my={3}>
<Button
type="button"
ref={cancelRef}
colorScheme="yellow"
isLoading={formik.isSubmitting}
onClick={formik.handleSubmit}
>
<Text
fontWeight="bold"
fontSize="18px"
color="gray.900"
fontFamily={"body"}
>
Submit
</Text>
</Button>
</Box>
</Form>
)}
</Formik>
</Box>
</Flex>
</ModalBody>
</ModalContent>
</Modal>
);
}
export default NewUploader;
but It seems when I render them to the screen it will come out as I triggered the onClick even though I didn't.
For now I commented out the useEffect func inside input component
I have uploaded it to code sandbox Bellow.
https://codesandbox.io/s/amazing-heyrovsky-9kr0ex?file=/src/Previewer.js
Related
So I've created a get request, which shows all the exercises and the options to edit (which will redirect to another url) and delete. I wanted to use material ui's Dialog, so there will be a confirmation before deleting. The problem is that, say I have 3 exercises and want to delete exercise with index 1, when click the on delete button it appears the last index in the element i.e the last exercise, although I want to delete the second. How can I fix that?
And also can I move Dialog in another file for more clear coding?
Exercises.js
const Exercises = () => {
const { categories, deletedCategories, error, isLoading, exercises, open, handleClickOpen, handleClose, deleteExercise } = useFormExercises();
const [showCategories, setShowCategories] = useState(false);
if (isLoading) {
return <div>Loading...</div>
}
if (error) {
return <div>There was an error: {error}</div>
}
return (
<Grid container className='content' >
<Card className='exerciseCard'>
<h1>Exercises</h1>
<FormControlLabel sx={{ width:'500px'}}
label="Show Categories"
control={
<Checkbox
sx={{ml:2}}
checked={showCategories}
onChange={(event) => setShowCategories(event.target.checked)}
/>
}
/>
<Button sx={{pr:6}} variant='fab' href={('/exercises/add')}>
<Add />
</Button>
<Divider />
<Grid className="exerciseContent" >
{exercises.map(exercise => (
<Card>
<tr key={exercise.id}>
<List>
<ListItem
key={exercise.id}
secondaryAction={
<IconButton onClick={handleClickOpen}>
<Delete />
</IconButton>
}>
<Button
sx={{mr:4}}
href={(`/exercises/edit/${exercise.id}`)} >
<Edit />
</Button>
<ListItemText sx={{justifyContent:'center', width:'400px'}}
primary={exercise.exercise_name}
secondary={showCategories ? exercise["exerciseCategories.category_name"] : null}
/>
</ListItem>
<Dialog
open={open}
onClose={handleClose}
style={{ borderColor: 'red' }}
>
<Box sx={{ borderTop: 3, color: 'red' }}>
<DialogTitle sx={{ color: 'black', backgroundColor: 'gainsboro', pl: 11 }}>
Delete Exercise
</DialogTitle>
<DialogContent>
<DialogContentText color='black' >
<Warning fontSize='large' color='error' id='warning' />
Are you sure you want to delete the exercise: {exercise.exercise_name} ?
</DialogContentText>
</DialogContent>
<DialogActions >
<Button variant='contained' onClick={() => deleteExercise(exercise.id)} autoFocus>
Yes
</Button>
<Button variant='outlined' onClick={handleClose}>No</Button>
</DialogActions>
</Box>
</Dialog>
</List>
</tr>
</Card>
))}
</Grid>
</Card>
</Grid>
)
}
export default Exercises
My delete function
useFormExercises.js
...
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
}
const deleteExercise = async (id) => {
try {
await fetch(`${BASE_URL}/exercises/${id}`, {
method: "DELETE",
}).then(response => {
setExercises(exercises.filter(exercise => exercise.id !== id))
return response.json()
})
} catch (error) {
console.log(error)
}
}
And if another thing seems of, please share your opinion!
Thanks in advance!
I have a popup dialog where I get a bunch of values from the user and then get a response after making an API request. I put an inline conditional rendering on the dialog box as it should only render once chart data is updated from the response. However, the dialog never appears even if console.log shows the data is updated. I tried to use useEffect() with many functions but it did not work. Any idea how to refresh the data again?
Edit: Added only relevant code
const [barGraphData, setBarGraphData] = useState([]);
const funcSetBarGraphData = (newBarGraphData) => {
setBarGraphData(newBarGraphData);
};
const sendChartData = async () => {
let bar_response = await axios.post(
"http://localhost:8080/h2h-backend/bardata",
bar_data,
{headers: {'Content-Type': 'application/json'}}
).then(res=>{
const resData = res.data;
const resSubstring = "[" + resData.substring(
resData.indexOf("[") + 1,
resData.indexOf("]")
) + "]";
const resJson = JSON.parse(resSubstring);
console.log(typeof resJson, resJson);
funcSetBarGraphData(barGraphData);
}).catch(err=>{
console.log(err);
});
chartClickOpen();
};
Returning popup dialog with charts when button is clicked:
<StyledBottomButton onClick={sendChartData}>Submit</StyledBottomButton>
{barGraphData.length > 0 && <Dialog
fullScreen
open={openChart}
onClose={chartClickClose}
TransitionComponent={Transition}
>
<AppBar sx={{ position: 'relative' }}>
<Toolbar>
<Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
Analytics View
</Typography>
<IconButton
edge="start"
color="inherit"
onClick={chartClickClose}
aria-label="close"
>
<CloseIcon />
</IconButton>
</Toolbar>
</AppBar>
<Grid container spacing={2}>
<Grid item xs={8} sx={{ pt: 2 }}>
<BarChart width={730} height={250} data={barGraphData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="business_name" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="num_of_customers" fill="#8884d8" />
<Bar dataKey="sum_total_amount" fill="#82ca9d" />
</BarChart>
{/* <Bar options={set_bar.bar_options} data={set_bar.bar_data} redraw={true}/> */}
</Grid>
<Grid item xs={4} sx={{ pt: 2 }}>
{/* <Pie data={data2} /> */}
</Grid>
</Grid>
</Dialog>}
<StyledBottomButton onClick={handleClose}>Cancel</StyledBottomButton>
I have two autocomplete components in my form where one depends on the other. I want that at a choice of a field in the first component values in the second were reset. I tried a bunch of ways to do it, but nothing worked. Please, tell me how can I solve this problem?
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmitAppealForm)}>
<Grid item container xs={12} sx={{ mt: 2 }} columnSpacing={3}>
<Grid item xs={6}>
<SelectControl
required
control={control}
options={[
{ id: 1, name: "1 - Тематика" },
{ id: 2, name: "2 - Категория" },
{ id: 3, name: "3 - Подкатегория" },
]}
placeholder="Уровень тематики"
label="Уровень тематики *"
name="themeLevel"
readonly
/>
</Grid>
<Grid item xs={6}>
<SelectControl
control={control}
options={getProjectThemesArray() ?? []}
disabled={!themeLevel || themeLevel === 1}
label="Родительская тематика"
defaultValue=""
name="parentThemeId"
loading={isParentThemeFetching}
/>
</Grid>
</Grid>
</form>
</FormProvider>
Autocomplete component:
return (
<>
<Controller
name={name}
control={control}
render={({ field: { value, onChange: handleChange } }) => (
<>
<div className="form-label">{label}</div>
<Autocomplete
onChange={(e, value) => {
handleChange(e, value);
onChange && onChange();
}}
options={options}
noOptionsText="Нет данных"
clearText=""
closeText=""
openText=""
autoHighlight
disabled={loading || disabled}
renderInput={(params) => (
<OutlinedInput
name={name}
{...params.InputProps}
inputProps={{
...params.inputProps,
}}
value={options.length > 1 ? value?.name : ""}
readOnly={readonly}
className={input}
required={required}
placeholder={
defaultValue && disabled
? defaultValue
: placeholder ?? label
}
fullWidth
/>
)}
isOptionEqualToValue={(option, value) => option.id === value.id}
getOptionLabel={(option) => option.name || ""}
renderOption={(props, option) => (
<MenuItem {...props} key={option.id} value={option.id}>
{option.name}
</MenuItem>
)}
popupIcon={
<div className={icon}>
{loading ? <Spinner width={20} height={20} /> : <Arrow />}
</div>
}
/>
</>
)}
/>
</>
I used reset and setField from useForm to my component but it didn't work. Also, resetting the state of the input did not work either.
I am building a category adder and previously I was only allowing the "add category chip" to show for the nooptions render, but I realized that if there was a category "software ngineer" and someone wanted to add "Software" you couldn't do it.
Ultimately I want to add the Add chip (which is different then the normal chips) to the end of all of the options with the search term in it. I tried adding it to the render option, but then it showed up after every option and I tried adding it to filter options but then I can't customize that chip.
Any suggestions on solving this?
My code:
<Autocomplete
id="Categories"
// set it to the left of the cell
style={{ width: '100%', marginTop: '1rem' }}
key={autocompleteKey}
options={
user.categories.length > 0
? user.categories.filter((c) => {
return !category.includes(c || c.charAt(0).toUpperCase() + c.slice(1));
})
: []
}
onChange={(event, value) => (handleAddCategoryToCompany(value))}
onInputChange={(event, value) => setNewCategory(value)}
popupIcon=""
filterOptions={(options) => {
console.log(options);
const result = [...options];
if (search.length > 0) result.push(search);
return result;
}}
renderOption={(event, value: string) => (
<>
<Chip
variant="outlined"
sx={{
m: '2px',
}}
onClick={() => handleAddCategoryToCompany(value)}
key={Math.random()}
label={value.charAt(0).toUpperCase() + value.slice(1)}
onDelete={() => removeFromGlobal(value)}
/>
{/* {search.length > 0
? (
<Chip
variant="outlined"
onClick={handleAddCategory}
key={Math.random()}
sx={{
m: '2px',
}}
label={search}
icon={<AddCircleIcon />}
/>
) : null} */}
</>
)}
renderInput={(par) => (
// Textfield where the options have a delete icon
<TextField
{...par}
fullWidth
label="Add Tag(s)"
margin="normal"
size="small"
name="Name"
onChange={(event) => (setSearch(event.target.value))}
key={autocompleteKey}
type="company"
value={search}
variant="outlined"
/>
)}
noOptionsText={search.length > 0
? (
<Chip
variant="outlined"
onClick={handleAddCategory}
key={Math.random()}
sx={{
m: '2px',
}}
label={search}
icon={<AddCircleIcon />}
/>
) : null}
/>
I figured it out by adding a modifier to the search option in the filter options and then had a conditional statement in render to look for this option. Seems to be working.
<Autocomplete
id="Categories"
// set it to the left of the cell
style={{ width: '100%', marginTop: '1rem' }}
key={autocompleteKey}
options={
user.categories.length > 0
? user.categories.filter((c) => {
return !category.includes(c || c.charAt(0).toUpperCase() + c.slice(1));
})
: []
}
onChange={(event, value) => (handleAddCategoryToCompany(value))}
onInputChange={(event, value) => setNewCategory(value)}
popupIcon=""
filterOptions={(options) => {
console.log(options);
const result = [...options];
if (search.length > 0) result.push(`${search} (new)`);
return result;
}}
renderOption={(event, value: string) => (
<>
{value.indexOf(' (new)') > -1
? (
<Chip
variant="outlined"
onClick={handleAddCategory}
key={Math.random()}
sx={{
m: '2px',
}}
label={search}
icon={<AddCircleIcon />}
/>
)
: (
<Chip
variant="outlined"
sx={{
m: '2px',
}}
onClick={() => handleAddCategoryToCompany(value)}
key={Math.random()}
label={value.charAt(0).toUpperCase() + value.slice(1)}
onDelete={() => removeFromGlobal(value)}
/>
)}
</>
)}
renderInput={(par) => (
// Textfield where the options have a delete icon
<TextField
{...par}
fullWidth
label="Add Tag(s)"
margin="normal"
size="small"
name="Name"
onChange={(event) => (setSearch(event.target.value))}
key={autocompleteKey}
type="company"
value={search}
variant="outlined"
/>
)}
noOptionsText={search.length > 0
? (
<Chip
variant="outlined"
onClick={handleAddCategory}
key={Math.random()}
sx={{
m: '2px',
}}
label={search}
icon={<AddCircleIcon />}
/>
) : null}
/>
I can't create a new Collection in Firebase. I created a collection called User to store Profildata which works, but now I need another to store something else.
Everything is working but it don't creates the collection on backend.
And I nearly used the same code which I used for the first collection.
function Waffen(){
const [open, setOpen] = React.useState(false);
const handleClose = () => setOpen(false);
const handleOpen = () => setOpen(true);
const [Hersteller, setHersteller] = useState(['Baretta',
'Walther']);
const [Modell, setModell] = useState();
const [Kaliber, setKaliber] = useState();
const history = useHistory("");
const [data] = useState({loading: false,});
const { loading } = data;
const handleSubmit = async (e) => {
e.preventDefault();
await setDoc(doc(db, 'Waffen' ,auth.currentUser.uid),
{ Hersteller: Hersteller, Modell: Modell, Kaliber: Kaliber
});
setOpen(false)
history.replace("/Profil");
};
return (
<div >
<Button onClick={handleOpen}>Meine Waffen</Button>
<Modal
open={open}
onClose={handleClose}>
<Box sx={style} >
<form >
<Grid
container
justifyContent="center"
alignItems="center"
spacing={2}
>
<Autocomplete
disablePortal
id="combo-box-demo"
options={Hersteller}
sx={{ width: 300 }}
renderInput={(params) =>
<TextField {...params} label='Hersteller'
value={Hersteller} onChange={event =>
setHersteller(event.target.value)}
/>}
/>
<Grid item>
<TextField sx={{ width: 300}} variant="outlined"
color="secondary" label="Modell" type="text"
name="Modell" value={Modell} onChange={event =>
setModell(event.target.value)}>
</TextField>
</Grid>
<Grid item >
<TextField sx={{ width: 300}} variant="outlined"
color="secondary" label="Kaliber" type="text"
name="Kaliber" value={Kaliber} onChange={event =>
setKaliber(event.target.value)}>
</TextField>
</Grid>
<Grid item>
<button onSubmit={handleSubmit}
className="AnmeldenButton" >
{loading ? "Bearbeitet ..." : "Speichern"}
</button>
</Grid>
</Grid>
</form>
</Box>
</Modal>
</div>
) ;
}
export default Waffen;