Toogle between upperCase and LowerCase Switch on a submitted form - reactjs

I am making a website where customers can submit a form with their personal data, like address, phone and email. The form sends the submitted data to an "Answers page" where the company can copy the details into their internal system. The answers page is made as a form which is read only.
The data is sent through an strapi API, then fetched again to the answers page. Everything is working fine.
The problem is that I want a simple Toogle between uppercase and lowercase letters for the submitted answers on the answers page. For some reason I cant get it to work. I have tried with useState and LocalStorage. I can console that the actuall boolean changes between true/false, but the text dosent rerender to uppercase and lowercase. I am using a MUI switch to toogle between uppercase and lowercase.
//Settings Context
export const SettingsContext = createContext();
export const SettingsProvider = props => {
const [upperCase, setUpperCase] = useState(localStorage.getItem("upperCase"));
document.documentElement.setAttribute("upperCase", upperCase);
return (
<SettingsContext.Provider value={[ upperCase, setUpperCase ]}>
{ props.children }
</SettingsContext.Provider>
)
}
//AnswerPage
const [upperCase, setUpperCase] = useContext(SettingsContext);
return (
<>
{answers
.sort((b, a) => a.id - b.id)
.filter((item, idx) => idx < 1)
.map((item) => {
const { country, street, postal, city, phone, email, publishedAt } =
item.attributes;
const phoneFormated = phone ? phone.replace(/\s+/g, "") : "";
let countryFormated;
if(upperCase === true) {
countryFormated = country.toUpperCase();
} else {
countryFormated = country.toLowerCase();
}
<FormTextField
onClick={copyCountry}
InputProps={{
readOnly: true,
endAdornment: (
<InputAdornment position="end">
<IconButton
edge="end"
color="primary"
sx={{ boxShadow: "none" }}
onClick={copyCountry}
>
<ContentCopyIcon
sx={{ height: ".8em", width: ".8em" }}
/>
</IconButton>
</InputAdornment>
),
}}
variant="standard"
label="Country"
type="text"
defaultValue={country ? countryFormated : ""}
inputRef={countryRef}
/>
//switch
function AnswersSettingsModal({ open, handleClose }) {
const [upperCase, setUpperCase] = useContext(SettingsContext);
const handleChange = (event) => {
setUpperCase(event.target.checked);
localStorage.setItem("upperCase", event.target.checked);
document.documentElement.setAttribute("upperCase", event.target.checked);
};
return (
<ModalLayout open={open} handleClose={handleClose}>
<Typography id="transition-modal-title" variant="h6" component="h2">
Show big or small letters
</Typography>
<FormGroup>
<Stack direction="row" spacing={1} alignItems="center">
<FormControlLabel
sx={{ marginLeft: "0" }}
control={
<>
<Typography>Small letters</Typography>
<Switch
checked={upperCase}
onChange={handleChange}
name="upperCase"
/>
<Typography>Big letters</Typography>
</>
}
/>
</Stack>
</FormGroup>
<button className="btn-close" onClick={handleClose}>
Close
</button>
</ModalLayout>
);
}
export default AnswersSettingsModal;

Related

Button to view update logo

Whenever the page loads, it should load a different logo, but when I click on the icon to view the password in the PASSWORD field, it reloads the logo.
I tried to leave asynchronously but it was not the case, maybe it's a problem with react hooks
export default function Login() {
const [showPassword, setShowPassword] = React.useState(false);
const handleClickShowPassword = () => setShowPassword((show) => !show);
const handleMouseDownPassword = (
event: React.MouseEvent<HTMLButtonElement>
) => {
event.preventDefault();
};
const logo = (): string => {
switch (parseInt((Math.random() * (4 - 1) + 1).toString(), 10)) {
case 1:
return "/logo4.png";
case 2:
return "/logo2.png";
case 3:
return "/logo3.png";
default:
return "/logo3.png";
}
};
return (
<Image
src={logo().toString()}
width={150}
height={150}
alt="Página de login"
></Image>
<br />
<FormControl sx={{ m: 1, width: "25ch" }} variant="outlined">
<InputLabel htmlFor="outlined-adornment-password">
Senha
</InputLabel>
<OutlinedInput
id="outlined-adornment-password"
type={showPassword ? "text" : "password"}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
}
label="Senha"
placeholder="**********"
/>
</FormControl>
);
}
The expectation is not to load the logo function when clicking to view the password

How can I fix the following warning: "Can't perform a React state update on an unmounted component"

Edit: I have been trying to implement a similar fix as this in my code, but I am very confused about how this method would translate in my code. I have been trying to apply that fix to my updateDose function, but it isn't working.
I am creating an app using React, Material UI, React Hook Form, and Yup.
I have two dialogs, one for the "edit dose" button and the "delete med" button for each card. On the "edit dose" dialog, the user inputs a new dose into a form.
I am getting the following warning if I try to update a medication's dose more than once (the first update shows no error, and then the second shows this)...
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I have searched other tutorials to try to find a fix based on other examples, but I am not having any luck. Thank you so much in advance and here's my relevant code...
const Medication = ({medication}) => {
const {handleSubmit, control, formState} = useForm({
mode: "onChange",
resolver: yupResolver(validationSchema)
});
// This handles the update medication dialog
const [openUpdate, setOpenUpdate] = useState(false);
const handleClickOpenUpdate = () => {
setOpenUpdate(true);
};
const handleCloseUpdate = () => {
setOpenUpdate(false);
};
// Function for the update dose button
function updateDose(medicationId, parsedMedications, data) {
let medication;
let index;
for (let i = 0; i < parsedMedications.length; i++) {
if (parsedMedications[i].id === medicationId) {
medication = parsedMedications[i];
index = i;
}
}
medication.dose = data.dose;
parsedMedications[index] = medication;
localStorage.setItem("medications", JSON.stringify(parsedMedications));
// This forces the dialog to close
setOpenUpdate(false);
}
return (
<Box>
<Card sx={cardSx}>
<CardContent>
<Typography sx={typographyMedicationSx} variant="h5">
Medication: {medication.medication}
</Typography>
<Typography sx={typographyMedicationSx} variant="h5">
Dose: {medication.dose} mg
</Typography>
</CardContent>
<Box>
<Button onClick={() => handleClickOpenUpdate()} size="large"
sx={buttonSx}
variant="contained">Edit
Dose</Button>
<Button onClick={() => handleClickOpen()} color="error"
size="large"
sx={buttonSx} variant="contained">Delete
Med </Button>
</Box>
</Card>
{/* Update medication dialog */}
<Dialog
open={openUpdate}
onClose={handleCloseUpdate}
TransitionComponent={Transition}
>
<DialogTitle sx={dialogTitleSx}>
{handleCloseUpdate ? (
<IconButton
aria-label="close"
onClick={handleCloseUpdate}
sx={iconButtonSx}
>
<CloseIcon/>
</IconButton>
) : null}
</DialogTitle>
<form
onSubmit={handleSubmit((data) => updateDose(medication.id, parsed, data))}
noValidate>
<Typography sx={updateDoseTypographySx} variant="h4">
Update dose
</Typography>
<Box
sx={boxSx}
>
<Controller
name="dose"
control={control}
defaultValue={""}
render={({field: {ref, ...field}, fieldState: {error}}) => (
<Autocomplete
{...field}
autoHighlight
disableClearable
isOptionEqualToValue={(option, value) => option.id === value.id}
id="dose-autocomplete"
onChange={(event, value) => field.onChange(value.label)}
options={doseSuggestions}
renderInput={(params) => (
<TextField
required
error={!!error}
helperText={error?.message}
id="dose"
label="Dose"
name="dose"
type="numeric"
inputRef={ref}
{...params}
/>
)}
/>
)}
/>
<Button disabled={!formState.isValid} size="large"
sx={formButtonSx} type="submit"
variant="contained">Submit</Button>
</Box>
</form>
</Dialog>
</Box>
)
};
const medications = parsed.map((medication, index) => {
return (<Medication medication={medication} key={"medication" + index}/>)
});
Someone on a slack community helped me figure it out! I needed to add this to the dialog... keepMounted={true}

Multiple autocomplete fields with Material UI in same view

I have 2 autocomplete fields on my view by default, but you can add more fields per click.
Currently I have the problem that every autocomplete field opens every result list of every autocomplete field, because every field uses "open". How do I get it to implement the whole thing dynamically?
const [value, setValue] = useState<string>('')
const [open, setOpen] = useState(false)
<Autocomplete
options={props.results.map((option) => option.name)}
renderOption={(option) => (
<Typography noWrap>
{option}
</Typography>
)}
onClose={() => {
setOpen(false)
}}
open={open}
renderInput={(params) => (
<Paper className={search.root} ref={params.InputProps.ref}>
<IconButton className={search.iconButton} disabled>
<FiberManualRecordIcon color="secondary" />
</IconButton>
<InputBase
{...params.inputProps}
className={search.input}
placeholder="Test"
value={value}
onChange={(event: any) => setValue(event.target.value)}
/>
<IconButton
className={search.iconButton}
disabled={!value}
>
<SearchIcon />
</IconButton>
</Paper>
)}
/>
Probably you already solved this due to the date of this issue. But I'm gonna put here what I did to resolve this problem because I was facing the same issue using FieldArray from Formik.
Material UI documentation sometimes is a little confusing, so try to ignore some stuff in their examples.
Remove the open, onOpen, and onClose props. You just need these props if you want to create some kind of automatic opening/closing mechanic.
Here it's an example of what I was doing and how I solved it.
<Autocomplete
id={`items.${index}.partNumber`}
name={`items.${index}.partNumber`}
freeSolo
style={{ width: 300 }}
open={openPartNumber}
onOpen={() => setOpenPartNumber(true)}
onClose={() => setOpenPartNumber(false)}
options={partNumbers}
clearOnBlur={false}
getOptionLabel={option => (option.label ? option.label : '')}
value={item.partNumber}
inputValue={item.partNumber}
onInputChange={(event, newInputValue) => {
if (event) {
setFieldValue(`items.${index}.partNumber`, newInputValue);
getPartNumberList(newInputValue);
}
}}
onChange={(event, optionSelected, reasson) => {
if (reasson === 'select-option') {
setFieldValue(`items.${index}.partNumber`, optionSelected.partNumber);
setFieldValue(`items.${index}.vendorNumber`, optionSelected.vendorNumber);
setFieldValue(`items.${index}.description`, optionSelected.description);
setFieldValue(`items.${index}.ncm`, optionSelected.ncm);
}
}}
filterOptions={x => x}
...otherPrps...
/>
Then I just remove the props open, onOpen and onClose.
<Autocomplete
id={`items.${index}.partNumber`}
name={`items.${index}.partNumber`}
freeSolo
style={{ width: 300 }}
options={partNumbers}
clearOnBlur={false}
getOptionLabel={option => (option.label ? option.label : '')}
value={item.partNumber}
inputValue={item.partNumber}
onInputChange={(event, newInputValue) => {
if (event) {
setFieldValue(`items.${index}.partNumber`, newInputValue);
getPartNumberList(newInputValue);
}
}}
onChange={(event, optionSelected, reasson) => {
if (reasson === 'select-option') {
setFieldValue(`items.${index}.partNumber`, optionSelected.partNumber);
setFieldValue(`items.${index}.vendorNumber`, optionSelected.vendorNumber);
setFieldValue(`items.${index}.description`, optionSelected.description);
setFieldValue(`items.${index}.ncm`, optionSelected.ncm);
}
}}
filterOptions={x => x}
...otherPrps...
/>
If you want automatic opening/closing mechanics to exist. One suggestion is to control open starting from an array.
In the onOpen and onClose props, your callback must control an array by adding and removing the AutoComplete ID and in open just check the existence of this ID inside the array with array.includes(index).
const [value, setValue] = useState<string>('')
//const [open, setOpen] = useState(false)
const [inputsOpen, setInputsOpen] = useState([])
function automatedOpening(id) {
if (id && inputsOpen.length === 0) {
setInputsOpen([...inputsOpen, id]);
}
}
// lenght must be zero to ensure that no other autocompletes are open and will be true on the Open prop
function automatedClosing(id) {
if (id && inputsOpen.length !== 0) {
setInputsOpen(inputsOpen.filter(item => item !== id));
}
}
<Autocomplete
options={props.results.map((option) => option.name)}
renderOption={(option) => (
<Typography noWrap>
{option}
</Typography>
)}
onClose={(e) => automatedClosing(e.id)}
onOpen={(e) => automatedClosing(e.id)}
open={inputsOpen.includs(id)}
renderInput={(params) => (
<Paper className={search.root} ref={params.InputProps.ref}>
<IconButton className={search.iconButton} disabled>
<FiberManualRecordIcon color="secondary" />
</IconButton>
<InputBase
{...params.inputProps}
className={search.input}
placeholder="Test"
value={value}
onChange={(event: any) => setValue(event.target.value)}
/>
<IconButton
className={search.iconButton}
disabled={!value}
>
<SearchIcon />
</IconButton>
</Paper>
)}
/>
you'll probably need to find a way to make the id/index available to the callbacks, but it's open to what you think is best

Jest/Enzyme: Mock function in Functional Component

I have a functional component in React. Here is the code
import React, { useState, Fragment } from "react";
import { makeStyles } from "#material-ui/core/styles";
import "./K8sIPCalculator.css";
import { initialState, checkBoxLabel } from "./FormMetaData";
import { checkPositiveInteger } from "./FormDataValidation";
const useStyles = makeStyles((theme) => ({
// Styles
}));
const K8sIPCalculator = (props) => {
const classes = useStyles();
let intialStateCopy = JSON.parse(JSON.stringify(initialState));
const [data, setData] = useState(intialStateCopy);
const handleCheckBox = (path, value) => {
const newData = { ...data };
if (path === "nodes" || path === "pods") {
if (!checkPositiveInteger(value)) {
newData[path].value = value;
newData[path].helperText = "It should be a positive integer!";
} else {
newData[path].value = value;
newData[path].helperText = "";
}
} else newData[path] = value;
setData(newData);
};
const calculate = () => {
// Does some calculation and update data state
};
const onSubmit = () => {
if (data.nodes.helperText !== "" || data.pods.helperText !== "") {
alert("Data is not correct");
return;
}
calculate();
};
const onReset = () => {
intialStateCopy = JSON.parse(JSON.stringify(initialState));
setData(intialStateCopy);
};
return (
<Fragment>
<h2 className="name">K8s IP Calculator</h2>
<form className={classes.formRoot}>
<Accordion
defaultExpanded={true}
classes={{ expanded: classes.expanded }}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
id="accordion1"
className={classes.summary}
>
<Typography className={classes.heading}>Results</Typography>
</AccordionSummary>
<AccordionDetails className="container">
<InputLabel className={classes.label}>
Total useable IPs required:
</InputLabel>
<TextField
disabled
className={classes.textDisabledInput}
id="ips-required-output"
variant="outlined"
value={data.total}
/>
<InputLabel className={classes.label} htmlFor="subnet-size-output">
Subnet Size required:
</InputLabel>
<TextField
disabled
className={classes.textDisabledInput}
id="subnet-size-output"
variant="outlined"
value={data.subnet_size}
/>
</AccordionDetails>
</Accordion>
<br />
<Accordion
defaultExpanded={true}
classes={{ expanded: classes.expanded }}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
id="accordion2"
className={classes.summary}
>
<Typography className={classes.heading}>K8s Details</Typography>
</AccordionSummary>
<AccordionDetails className="container">
<InputLabel className={classes.label}>Nodes:</InputLabel>
<TextField
size="small"
type="number"
onChange={(e) => handleCheckBox("nodes", e.target.value)}
className={classes.textInput}
id="nodes-input"
variant="outlined"
value={data.nodes.value}
helperText={data.nodes.helperText}
/>
<InputLabel className={classes.label} htmlFor="pods-input">
Pods:
</InputLabel>
<TextField
size="small"
type="number"
onChange={(e) => handleCheckBox("pods", e.target.value)}
className={classes.textInput}
id="pods-input"
variant="outlined"
value={data.pods.value}
helperText={data.pods.helperText}
/>
<div id="nodes-error"></div>
</AccordionDetails>
</Accordion>
<div className="button-container">
<Button
id="reset-button"
className="button"
variant="outlined"
color="primary"
size="small"
onClick={onReset}
startIcon={<UndoIcon />}
>
Reset
</Button>
<Button
id="submit-button"
className="button"
variant="contained"
color="primary"
size="small"
startIcon={<SaveIcon />}
onClick={onSubmit}
>
Submit
</Button>
</div>
</form>
</Fragment>
);
};
export default K8sIPCalculator;
Things, I am trying to test,
When input changes, I want to check if handleCheckBox function has been called
When submit button is called, I want to check if calculate function has been called
How can I call the setData function to update data through Jest/Enzyme
I tried something like this
const spy = jest.spyOn(K8sIPCalculator, "calculate");
But I got
Cannot spy the calculate property because it is not a function; undefined given instead
PS: I am able to assert data change after submit is called. But I want to check if calculate function is called.
const submit = wrapper.find("button#submit-button");
submit.simulate("click");
expect(wrapper.find("input#ips-required-output").props().value).toEqual(35);
expect(wrapper.find("input#subnet-size-output").props().value).toEqual(
"/26"
);

How to Increment and Decrement in ReactJS using Formik

I have a very simple problem incrementing and decrementing the value in my TextField.
I'm using Formik and Material UI in React.
Pls see this codesandbox link
CLICK HERE
<TableCell>
{" "}
<TextField
variant="outlined"
fullWidth
type="number"
name={`data.${idx}.returnValue`}
value={
values.data[idx] &&
values.data[idx].returnValue
}
onChange={handleChange}
onBlur={handleBlur}
inputProps={{
min: 0,
style: {
textAlign: "center"
}
}}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<IconButton>
<RemoveCircleOutlineIcon />
</IconButton>
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton>
<AddCircleOutlineIcon />
</IconButton>
</InputAdornment>
)
}}
/>
</TableCell>
Use setValues to update your values object. Every time the user clicks the increment/decrement button you call setValues to update the current record.
This is what your RemoveCircleOutlineIcon onClick function would look like for returnValue's textfield.
...
onClick={() => {
// decrement current record's returnValue
const newData = values.data.map((currData) => {
if (currData.name !== record.name) return currData;
return { ...currData, returnValue: currData.returnValue - 1 };
});
setValues({ ...values, data: newData });
}}
...this one for AddCircleOutlineIcon, basically the same we're just incrementing the currrent record's returnValue.
onClick={() => {
// increment current record's returnValue
const newData = values.data.map((currData) => {
if (currData.name !== record.name) return currData;
return { ...currData, returnValue: currData.returnValue + 1 };
});
setValues({ ...values, data: newData });
}}
The onClicks for current record's dispatchValue textfield would be the same above except you'll update dispatchValue instead of returnValue. You get the idea.
You could use the replace function from arrayHelpers.
<IconButton
onPress={() => {
replace(idx, { ...record, returnValue: record.returnValue + 1})
}}
>
</IconButton>
You could check the working example here
You have to use States to render the changes, I modified your logic using useState. and added an event to change value each time you change press a button (+ or -).
const [products, setProduct] = useState(data);
const initialValues = {
customer_id: null,
reason: "",
products
};
// an example of the events
<InputAdornment position="start">
<IconButton
onClick={() => {
decrement(
idx,
"returnValue" // the key value
);
}}
>
<RemoveCircleOutlineIcon />
</IconButton>
</InputAdornment>
Check you code here to see it more clearly.

Resources