I am using the following code from a tutorial as a re-usable component for a Material-UI select:
import React from 'react';
import { TextField, MenuItem } from '#material-ui/core';
import { useField, useFormikContext } from 'formik';
const SelectWrapper = ({
name,
options,
...otherProps
}) => {
const { setFieldValue } = useFormikContext();
const [field, meta] = useField(name);
const handleChange = evt => {
const { value } = evt.target;
const { innerText } = evt.nativeEvent.target;
setFieldValue(name, value);
};
const configSelect = {
...field,
...otherProps,
select: true,
variant: 'outlined',
fullWidth: true,
onChange: handleChange
};
if (meta && meta.touched && meta.error) {
configSelect.error = true;
configSelect.helperText = meta.error;
}
return (
<TextField {...configSelect}>
{Object.keys(options).map((item, pos) => {
return (
<MenuItem key={pos} value={item}>
{options[item]}
</MenuItem>
)
})}
</TextField>
);
};
export default SelectWrapper;
When using this Select.js component within my App.js, i.e.:
<Grid item xs={12}>
<Select
name="country"
label="Country"
options={countries}
/>
</Grid>
is there anyway I can also access the text value from
const { innerText } = evt.nativeEvent.target;
within the App.js component as I also need to get the Country name/text in my select list?
I just need to retrieve the text value from the selection the user made and store it in state.
This should work:
Define a state in your app.js file to hold the innerText value from the child component (Select.js in this case).
const [innerTextSync, setInnerTextSync] = useState('')
Define a function that changes the state in your app.js:
const updateInnerTextSync=(input)=>{setInnerTextSync(input)}
Pass this function as a prop into Select.js
<Select
name="country"
label="Country"
options={countries}
updateInnerTextSync={updateInnerTextSync}
/>
Within the handleChange function in Select.js file, update the app state by calling the function you passed in earlier.
const handleChange = evt => {
const { value } = evt.target;
const { innerText } = evt.nativeEvent.target;
setFieldValue(name, value);
updateInnerTextSync(innerText)
};
Now the innerText should be available in ur app file.
Related
I'm trying to store user input in local storage and had it functioning. But because I will need to test it, I have changed my code and made a function that will work as a custom hook, that I will call in my tests also.
Now the page is rendering but I am not able to type into the input box?
When hovered over the box the mouse cursor doesn't even respond as if it isn't an input field.
I believe the problem lies in my useStateWithLocalStorage function:
import { useState, useEffect } from 'react';
const useStateWithLocalStorage = (defaultValue, key) => {
const [value, setValue] = useState(() => {
const storedValues = localStorage.getItem(key);
return storedValues !== '' ? JSON.parse(storedValues) : defaultValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
export default useStateWithLocalStorage;
In particular this line
return storedValues !== '' ? JSON.parse(storedValues) : defaultValue;
The value before anything is parsed into the localstorage should be name: ''
Here is my component:
import React from 'react';
import { Container, Title } from '#mantine/core';
import useStateWithLocalStorage from './Handlers';
const UserForm = () => {
const [inputValue, setInputValue] = useStateWithLocalStorage('', 'form');
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
}
function handleChange(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
setInputValue((previousValues) => ({
...previousValues,
[event.target.name]: event.target.value,
}));
}
return (
<Container>
<Title order={2}>Welcome {inputValue.name}</Title>
<form onSubmit={handleSubmit}>
<label htmlFor="name">
Name
<input
type="text"
name="name"
id="name"
placeholder="enter your name"
onChange={handleChange}
value={inputValue.name}
/>
</label>
</form>
</Container>
);
};
export default UserForm;
I hope I've explained myself well enough and haven't wasted anyone's time. I'd be thankful for any help.
Having a monaco-editor inside a React component:
<Editor defaultValue={defaultValue} defaultLanguage='python' onChange={onChangeCode} />
The defaultValue, the default code inside of the editor, is sent via props to the component:
const MyComponent = ({
originalCode
}: MyComponentProps) => {
const [defaultValue, setDefaultValue] = useState(originalCode);
When the user edits the code, onChange={onChangeCode} is called:
const onChangeCode = (input: string | undefined) => {
if (input) {
setCode(input);
}
};
My question is, how to reset the code to the original one when the user clicks on Cancel?
Initially it was like:
const handleCancel = () => {
onChangeCode(defaultValue);
};
but it didn't work, probably because useState is asynchronous, any ideas how to fix this?
Here is the whole component for more context:
import Editor from '#monaco-editor/react';
import { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Button, HeaderWithButtons } from '../shared/ui-components';
import { ICalculationEngine } from '../../../lib/constants/types';
import { usePostScript } from '../../../lib/hooks/use-post-script';
import { scriptPayload } from '../../../mocks/scriptPayload';
import { editorDefaultValue } from '../../../utils/utils';
export interface ScriptDefinitionProps {
realInputDetails: Array<ICalculationEngine['RealInputDetails']>;
realOutputDetails: ICalculationEngine['RealInputDetails'];
originalCode: string;
scriptLibId: string;
data: ICalculationEngine['ScriptPayload'];
}
const ScriptDefinition = ({
realInputDetails,
realOutputDetails,
originalCode
}: ScriptDefinitionProps) => {
const [defaultValue, setDefaultValue] = useState(originalCode);
const [code, setCode] = useState(defaultValue);
const { handleSubmit } = useForm({});
const { mutate: postScript } = usePostScript();
const handleSubmitClick = handleSubmit(() => {
postScript(scriptPayload);
});
const handleCancel = () => {
onChangeCode(defaultValue);
};
const onChangeCode = (input: string | undefined) => {
if (input) {
setCode(input);
}
};
useEffect(() => {
setDefaultValue(editorDefaultValue(realInputDetails, realOutputDetails));
}, [realInputDetails, realOutputDetails, originalCode]);
return (
<div>
<HeaderWithButtons>
<div>
<Button title='cancel' onClick={handleCancel} />
<Button title='save' onClick={handleSubmitClick} />
</div>
</HeaderWithButtons>
<Editor defaultValue={defaultValue} defaultLanguage='python' onChange={onChangeCode} />
</div>
);
};
export default ScriptDefinition;
If you need the ability to change the value externally, you'll need to use the Editor as a controlled component by passing the value prop (sandbox):
For example:
const defaultValue = "// let's write some broken code 😈";
function App() {
const [value, setValue] = useState(defaultValue);
const handleCancel = () => {
setValue(defaultValue);
};
return (
<>
<button title="cancel" onClick={handleCancel}>
Cancel
</button>
<Editor
value={value}
onChange={setValue}
height="90vh"
defaultLanguage="javascript"
/>
</>
);
}
I have written a re-usable input component for url if a url dont start with http then it will be added http in the beginning.
Here you go for the componet
import React, {useContext, useCallback} from 'react';
const InputURL = ({ name, onChange, ...rest}) => {
const sanitizeURLonChange = React.useCallback((value, actionMeta) => {
if (value.target.value) {
if (!value.target.value.startsWith('http')) {
value.target.value = 'http://' + value.target.value
}
}
}, [onChange])
return (
<>
<input
name={name}
{...rest}
onChange={sanitizeURLonChange}
/>
</>
);
}
export default InputURL;
But when i try to use it in my some component, the onChange doesn't work
I try this way
<InputURL onChange={(e) => console.log(e.target.value)} />
unfortunately the inputURL onChange not working anymore, can you please help me in this case?
I want to achieve. if user input url without http, it will add http,
Like i input it stackoverflow.com/ and then it will return https://stackoverflow.com/ in Onchange
You are closing the bracket right after the event argument : {(e)}. Try like this:
<inputURL onChange={(e, val) => console.log(val)} />
also you have to use the onChange you're passing as props:
const sanitizeURLonChange = (e, actionMeta) => {
let newValue = e.target.value
if (newValue) {
if (!newValue.startsWith('http')) {
newValue = "http://" + newValue
}
}
setVal(newValue);
onChange(event, newValue)
}
but it seems anyway the onChange you are passing as a props to inputURL is not used anywhere so I am not sure what you want to achieve. Also you are calling the component inputURL instead of InputURL and first letter uppercase is very important in JSX.
I think your problem is here:
value.target.value = 'http://' + value.target.value
You are trying to update input value by not using an hook.
Try to rewrite your code in this way:
import React, { useState } from 'react';
const InputURL = ({ name, onChange, ...rest}) => {
const [val, setVal] = useState("");
const sanitizeURLonChange = (value, actionMeta) => {
if (value.target.value) {
if (!value.target.value.startsWith('http')) {
setVal('http://' + value.target.value);
}
else setVal(value.target.value);
}
}
return (
<>
<input
name={name}
{...rest}
value={val}
onChange={sanitizeURLonChange}
/>
</>
);
}
export default InputURL;
Here codesandbox working example.
I'm trying to create a edit form to edit data from database by id. I tried this:
import React, {FormEvent, useEffect, useState} from "react";
import TextField from "#material-ui/core/TextField";
import { createStyles, makeStyles, Theme } from "#material-ui/core/styles";
import {
TicketFullDTO,
TicketStatusTypesDTO,
} from "../../service/support/types";
import {
getTicket,
getTicketStatusTypes,
updateTicket,
} from "../../service/support";
import { useHistory, useParams } from "react-router-dom";
import InputLabel from "#mui/material/InputLabel";
import Select from "#mui/material/Select";
import MenuItem from "#mui/material/MenuItem";
import { FormControl } from "#mui/material";
import { Moment } from "moment";
import { RouteParams } from "../../service/utils";
export default function TicketProfile(props: any) {
const classes = useStyles();
let history = useHistory();
let requestParams = useParams<RouteParams>();
const [status, setStatus] = useState<string>("");
const [submitDate, setSubmitDate] = useState<Moment | null>(null);
const [ticket, setTicket] = useState<TicketFullDTO>();
const formSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(e);
updateTicket(requestParams.id, data)
.then(({ data }) => {
console.log(data.title);
history.replace("/support");
})
.catch((err) => {
console.log(err);
});
};
const [ticketCategoriesList, setTicketCategoriesList] = useState<
TicketCategoryTypesDTO[]
>([]);
const [ticket, setTicket] = useState<TicketFullDTO>();
const getSingleTicket = async () => {
getTicket(requestParams.id)
.then(({ data }) => {
setTicket(data);
})
.catch((error) => {
console.error(error);
});
};
const [ticketStatusList, setTicketStatusList] = useState<
TicketStatusTypesDTO[]
>([]);
useEffect(() => {
ticketStatusData();
getSingleTicket();
}, []);
const ticketStatusData = async () => {
getTicketStatusTypes()
.then((resp) => {
setTicketStatusList(resp.data);
})
.catch((error) => {
console.error(error);
});
};
return (
<Container>
<form onSubmit={onSubmit}>
.........
<TextField
value={ticket?.title}
id="title"
onChange={({ target: { value } }) => {
setTicket({ ...ticket, title: value });
}}
/>
.........
<FormControl>
<TextField
label="Submit Date"
id="submit-date"
type="date"
defaultValue={ticket?.submitDate}
//#ts-ignore
onInput={(e) => setSubmitDate(e.target.value)}
/>
</FormControl>
..........
<Select
labelId="status-label"
id="status-helper"
value={ticket?.status}
onChange={(e) => setStatus(e.target.value)}
required
>
{ticketStatusList.map((element) => (
<MenuItem value={element.code}>
{element.name}
</MenuItem>
))}
</Select>
</FormControl>
...........
<Button
type="submit"
>
Update Ticket
</Button>
</Container>
);
}
.....
export async function updateTicket(
id: string,
data: TicketFullDTO
): Promise<AxiosResponse<TicketFullDTO>> {
return await axios.post<TicketFullDTO>(
`${baseUrl}/management/support/tickets/ticket/${id}`,
{
data,
}
);
}
export interface TicketFullDTO {
id?: number,
title?: string,
status?: string,
submitDate?: Moment | null
}
I get error in Chrome console:
MUI: A component is changing the uncontrolled value state of Select to be controlled. Elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled Select element for the lifetime of the component. The nature of the state is determined during the first render. It's considered controlled if the value is not undefined.
The value for Select should be selected using the value ticket?.status when list ticketStatusList But the data object is not running before rendering the UI content and the value into Select dropdown is not selected.
Do you know how I can fix this issue?
React figures out if a component is controlled or not by checking if value is set on the first render. defaultValue should only be used for uncontrolled components.
Since you're working with controlled components you must provide a default value other than undefined in the value prop:
<Select
labelId="status-label"
id="status-helper"
value={ticket?.status ?? null}
onChange={(e) => setStatus(e.target.value)}
required
>
More on this topic in the docs.
Try this
<Select
labelId="status-label"
id="status-helper"
value={status}
onChange={(e) => setStatus(e.target.value)}
required
>
And to synchronize status ticket with ticket state
const [status, setStatus] = useState<string>("");
const [submitDate, setSubmitDate] = useState<Moment | null>(null);
const [ticket, setTicket] = useState<TicketFullDTO>();
React.useEffect(() => setTicket(previousTicket =>
({ ...previousTicket, status })), [status]);
First, in your MenuItem, set the value prop to object (element) instead of a string (element.code), this is because you pass an object as a value prop to your Select. The current value of the Select must have the same type as the value in MenuItem:
<Select
labelId="status-label"
id="status-helper"
required
value={status} // <------------------------- because value is an object here
onChange={(e) => setStatus(e.target.value)}
>
{ticketStatusList.map((element) => (
<MenuItem
key={element.code}
value={element} // <--- this one should be an object too as a result
>
{element.name}
</MenuItem>
))}
</Select>
Then in your status state declaration, add a null object to change your Select to controlled mode
const [status, setStatus] = useState<TicketStatusTypesDTO>({});
for typescript error: you need to write the same type and just set value only if its not null, so type error won't bother you
const handleChange = (value: string | null) => {
if (!value) {
setStatus(value)
} else setStatus('')
}
<Select
labelId="status-label"
id="status-helper"
value={ticket.status || null} // default value
onChange={(event, value) => handleChange(value)}
required
>
The problem is that the optional chaining operator returns undefined. Try replacing
value={ticket?.status}
with
value={ticket?.status || null}
To solve the TypeScript error, in the useState hook, just declare all possible types of the state variable:
const [status, setStatus] = useState<string | null>("");
I have the below common component
import PropTypes from 'prop-types';
import Input from '../component/Input'; //internal component
const CustomInput = props => {
const { label, updateInputField } = props;
return (
<Input
label={label}
changeField={updateInputField}
)
}
CustomInput.propTypes = {
label: PropTypes.string,
updateInputField: PropTypes.func
}
export default CustomInput;
Now i am using these component at several places like below
<CustomInput
label="401Balance"
updateInputField={inputFieldHandler}
/>
so this one works.. but there are some places where I am not using the updateInputField as a prop. for e.g.
<CustomInput
label="savingsBalance"
/>
How do i not pass the prop and ensure it doesnot fail. Can someone please suggest.
You could either set a default value to secure the case if no function was passed:
const { label, updateInputField = () => {} } = props;
or (probably a better approach) - create a separate function and add a simple condition:
const CustomInput = props => {
const { label, updateInputField } = props;
const fn = () => {
if (updateInputField) {
updateInputField();
}
};
return (
<Input
label={label}
changeField={fn}
/>
);
}