How to make a wrapper interchangable? - javascript

I am facing a problem with my formik fields and need to use FastFields in some situations and Fields in others. Therefor my FormikControl, a class for building formik inputs needs to switch between and as wrappers. I am not sure how to do this.
Here is part of the class:
const FormikControl = (props) => {
const {
name,
type,
label,
disabled,
fullWidth,
isDynamic, // Field (true) or FastField
} = props;
const classes = useStyles();
const inputProps = {};
switch (type) {
case 'text':
return (
<>
<ComplexField name={name} isDynamic={isDynamic} {...props}> // meant to be the wrapper that switches
{({ field }) => {
return (
<TextField
{...field}
variant="standard"
label={label}
fullWidth={fullWidth}
disabled={disabled}
InputLabelProps={{
shrink: true,
}}
InputProps={inputProps}
value={field.value || ''}
/>
);
}}
</ComplexField>
<ErrorMessage error name={name} component={FormHelperText} />
</>
);
ComplexField is meant to be the flexable wrapper and needs be able to switch between Field and FastField based on "isDynamic".
My best try was this:
const ComplexField = ({ name, children, isDynamic, ...props }) => {
return isDynamic ? (
<Field name={name} props={props}>{({ field }) => children({ ...props, field })}</Field>
) : (
<FastField name={name} props={props}>{({ field }) => children({ ...props, field })}</FastField>
);
};
Didn't work :(

<ComplexField name={name}>
You're not passing there the prop "isDynamic"
Is it intentional? Because like that it's obvious that doesnt work
<ComplexField name={name} isDynamic={isDynamic}>
If you want to use a better pattern, read that Conditionally render react components

Related

Is there any way to use mui autocomplete with formik reusably?

I have a mui autocomplete component. I am trying to reuse this autocomplete component with a form where formik validation added.
My autocomplete component is,
const CustomAutoCompleteField = props => {
const {rerenderAutocomplete, data, refetchCategoryData, autoCompleteFieldsData, inputLabel, autoCompleteFieldsInputOnChange , onTouch, onErrors,fieldProps, onBlur} = props
const [textFieldData, setTextFieldData] = useState(null)
const onChangeHandler = (event, value) =>{
}
return (
<>
<Autocomplete
key={rerenderAutocomplete}
// value={onEdit && data}
isOptionEqualToValue={(option, value) => option.name === value.name}
onBlur={onBlur}
onChange={onChangeHandler}
fullWidth
id="tags-outlined"
options={autoCompleteFieldsData ? autoCompleteFieldsData : top100Films }
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (<TextField
required
{...params}
label={inputLabel}
onChange={textFieldInputOnChange}
error={Boolean(onTouch && onErrors)}
helperText={onTouch && onErrors}
{...fieldProps}
/>)}
/>
</>
);
};
Here I am passing formik attributes in side props which are, onTouch, onErrors,fieldProps, onBlur.
In My Parent component, i am using this autocomplete field by giving props, which are,
<CustomAutoCompleteField inputLabel='Select Category'
onBlur={addNewServiceFormik.handleBlur}
onTouch={addNewServiceFormik.touched.selectedCategoryName}
onErrors={addNewServiceFormik.errors.selectedCategoryName}
fieldProps={addNewServiceFormik.getFieldProps('selectedCategoryName')}
/>
I don,t know why, when i click submit on my form, this autocomplete doesn't show any helper text as per formik validation.
I simply created a component for this
In the form your must pass FormikProvider for use with FormikContext
import { TextField } from '#mui/material';
import Autocomplete from '#mui/material/Autocomplete';
import { useFormikContext } from 'formik';
import React from 'react';
type OptionsValues = {
title: string,
value: string
}
type Props = {
id: string,
name: string,
label: string,
options: OptionsValues[]
}
function MuiltSelect(props: Props) {
const { options, name, label, id } = props
const formik = useFormikContext();
return (
<Autocomplete
{...props}
multiple
options={options}
getOptionLabel={(option: any) => option.title}
onChange={(_, value) => formik.setFieldValue(name, value)}
filterSelectedOptions
isOptionEqualToValue={(item: any, current: any) => item.value === current.value}
renderInput={(params) => (
<TextField
{...params}
id={id}
name={name}
label={label}
variant={"outlined"}
onChange={formik.handleChange}
error={formik.touched[name] && Boolean(formik.errors[name])}
helperText={formik.errors[name]}
value={formik.values[name]}
fullWidth
/>
)
}
/>
)
}
export default MuiltSelect
check gist
https://gist.github.com/Wellers0n/d5dffb1263ae0fed5046e45c47a7c4a7

How to hide MUI Autocomplete Popper

I am trying to make a component which has the option of showing a history of selections or hiding the input. This is because the user may want to hide the data if they are in public but want the convenience of seeing past values if they aren't. I have the following component.
const SensitiveTextField: FunctionComponent<any> = ({ ...props }) => {
const [showData, setShowData] = useState(false);
useEffect(() => {
(async () => {
const storage = new ApplicationStorage();
const showFieldData = props.name
? await storage.getShowSensitiveFieldData(props.name)
: false;
setShowData(showFieldData ?? false);
})();
}, []);
const onVisibilityChange = () => {
setShowData((value) => {
const newValue = !value;
if (props.name) {
const storage = new ApplicationStorage();
storage.setShowSensitiveFieldData(props.name, newValue);
}
return newValue;
});
};
const renderInput = (params: any) => {
return (
<TextField
{...params}
type={showData ? 'text' : 'password'}
InputProps={{
...params.InputProps,
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={onVisibilityChange}
onMouseDown={onVisibilityChange}
>
{showData ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>
);
};
return (
<Autocomplete {...props} renderInput={(params) => renderInput(params)} />
);
};
so the idea is that when showData is false, the input will be hidden and so will the options. I tried to do this by filtering the options to nothing but there is still an empty component that comes up. I tried to create a custom renderOptions that would just create an empty component but the same small bit showed up. The autocomplete uses the Popper component, is there a way to set that popper component to not render at all inside of the Autocomplete?
Here is where I invoke SensitiveAutocomplete
<SensitiveAutocomplete
name={item.key}
fullWidth
size="small"
onChange={onUserDefineValueChange(item.key)}
label={inputLabel(item.key)}
value={item.value}
options={item.history}
/>

React typescript onChange is showing error when I try to add it to material UI switch

I am planning to do a show and hide using switch. When I turn on the switch it need to show the component and when I turn odd the switch it need to hide the component.
This is the coding I did.
export const VirtualEventSection = ({
control,
onSearch,
onSearchInputChange,
searchInputValue,
}: VirtualEventSectionProps) => {
const { formatMessage } = useLocale();
const styles = useStyles();
const [state, setState] = useState(false);
const handleSwitchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setState(event.target.checked);
};
return (
<Card className={styles.actionCard}>
<Grid container spacing={1} alignItems="flex-start">
<Grid item sm={12} xs={12}>
<SwitchWithLabel
name="virtualEnabled"
label={formatMessage({ id: 'form_event.event_toggle_lable' })}
control={control}
checked={state}
onChange={handleSwitchChange}
/>
{state && state === true ? (
<LocationSelection
control={control}
onSearch={onSearch}
onSearchInputChange={onSearchInputChange}
searchInputValue={searchInputValue}
/>
) : (
<h1></h1>
)}
</Grid>
</Grid>
</Card>
);
};
But when I add like this I am getting an error in onChange. This is the error I got
Type '(event: React.ChangeEvent<HTMLInputElement>) => void' is not assignable to type '({ name, checked }: OnChangeProps<"virtualEnabled">) => void'. Types of parameters 'event' and '__0' are incompatible.Type 'OnChangeProps<"virtualEnabled">' is missing the following properties from type 'ChangeEvent<HTMLInputElement>': target, nativeEvent, currentTarget, bubbles, and 11 more.
This is the created switchWithLable component
export const SwitchWithLabel = <T extends string = string>({
label,
name,
control,
...switchProps
}: SwitchWithLabelProps<T>) => {
const styles = useStyles();
return (
<Controller
name={name}
control={control}
render={({ value, onChange }) => (
<FormControlLabel
className={styles.label}
label={label}
control={
<Switch
name={name}
onChange={async ({ checked }) => {
onChange(checked);
}}
checked={value}
{...switchProps}
/>
}
/>
)}
/>
);
};
Can any one help me to achieve the goal. I tried multiple time and still showing the error.
You don't need to have event: React.ChangeEvent<HTMLInputElement>. You can change the state of the switch with simple function.
const handleSwitchChange = () => {
setState(!state);
};
Complete code =>
export const VirtualEventSection = ({
control,
onSearch,
onSearchInputChange,
searchInputValue,
}: VirtualEventSectionProps) => {
const { formatMessage } = useLocale();
const styles = useStyles();
const [state, setState] = useState(false);
const handleSwitchChange = () => {
setState(!state);
};
return (
<Card className={styles.actionCard}>
<Grid container spacing={1} alignItems="flex-start">
<Grid item sm={12} xs={12}>
<SwitchWithLabel
name="virtualEnabled"
label={formatMessage({ id: 'form_event.event_toggle_lable' })}
control={control}
checked={state}
onChange={() => handleSwitchChange()}
/>
{state && state === true ? (
<LocationSelection
control={control}
onSearch={onSearch}
onSearchInputChange={onSearchInputChange}
searchInputValue={searchInputValue}
/>
) : (
<h1></h1>
)}
</Grid>
</Grid>
</Card>
);
};

Passing existing prop to component and appending

const LNTextField = props => {
const classes = useStylesReddit();
return (
<TextField
variant="filled"
InputProps={{ classes, disableUnderline: true }}
{...props}
/>
);
};
This is a functional component
Let's say I would like to add something to the InputProps object when using this component, while maintaining the InputProps object inside the functionalComponent
How can I do that?
for example;
<LNTextField
InputProps={{
endAdornment: (
<InputAdornment
className={styles.optionalAppendedText}
position="end"
>
Optional
</InputAdornment>
)
}}/>
Right now its overwriting the InputProps in the component
TIA
How about use different props name for InputProps from the parent? Let say, InputProps from parent is named with InputPropsParent. And, you can try to do this:
const LNTextField = props => {
const classes = useStylesReddit();
return (
<TextField
variant="filled"
InputProps={{ ...props.InputPropsParent, classes, disableUnderline: true }}
{...props}
/>
);
};
updated
As the OP comment, the above code is not working. Below code is the working code, though idk if this is the best approach or not.
const LNTextField = props => {
const classes = useStylesReddit();
return (
<TextField
variant="filled"
InputProps={{ ...props.inputpropsparent, classes, disableUnderline: true }}
{...props}
/>
);
};

React Input losing focus after keystroke

I'm building a an app in React/Redux and I have some input fields that lose focus after each key stroke. My code looks like this:
<General fields={props.general}
onChange={value => props.updateValue('general', value)} />
<FormWrapper>
<Network fields={props.network}
onChange={value => props.updateValue('network', value} />
</NetworkTableForm>
</FormWrapper>
General and Network just contain different sections of the form and FormWrapper can wrap a subsection of the form and provide some interesting visualisations. The problem is that all of the fields in General work fine but the fields in Network lose focus after each keystroke. I've already tried appending keys to a bunch of different elements such as General Network, FormWrapper and my components wrapping my input fields and the reason that I'm not using redux-form is because it doesn't work well with the UI framework that I'm using(Grommet). Any idea what could be causing this?
Edit: Adding more code as per request. Here's the full component:
const InfoForm = (props) => {
let FormWrapper = FormWrapperBuilder({
"configLists": props.configLists,
"showForm": props.showForm,
"loadConfiguration": props.loadConfiguration,
"updateIndex": props.updateIndex,
"metadata": props.metadata})
return (
<CustomForm onSubmit={() => console.log('test')}
loaded={props.loaded} heading={'System Details'}
header_data={props.header_data}
page_name={props.general.name} >
<General fields={props.general}
onChange={(field, value) => props.updateValue('general', field, value)} />
<FormWrapper labels={["Interface"]} fields={["interface"]} component={'network'}>
<Network fields={props.network}
onChange={(field, value) => props.updateValue('network', field, value)} />
</FormWrapper>
<User fields={props.user}
onChange={(field, value) => props.updateValue('user', field, value)} />
</CustomForm>
)
}
export default InfoForm
Here's an example of a form section component:
const Network = ({fields, onChange}) => (
<CustomFormComponent title='Network'>
<CustomTextInput value={fields.interface} label='Interface'
onChange={value => onChange('interface', value)} />
<CustomIPInput value={fields.subnet} label='Subnet'
onChange={value => onChange('subnet', value)} />
<CustomIPInput value={fields.network} label='Network'
onChange={value => onChange('network', value)} />
<CustomIPInput value={fields.gateway} label='Gateway'
onChange={value => onChange('gateway', value)} />
<CustomIPInput value={fields.nat_ip} label='NAT'
onChange={value => onChange('nat_ip', value)} />
</CustomFormComponent>
)
export default Network
Here's an example of one of the custom inputs:
const CustomTextInput = ({label, value, onChange}) => (
<FormField label={<Label size='small'>{label}</Label>}>
<Box pad={{'horizontal': 'medium'}}>
<TextInput placeholder='Please Enter' value={value}
onDOMChange={event => onChange(event.target.value)}
/>
</Box>
</FormField>
)
export default CustomTextInput
And here's the FormWrapperBuilder function:
const FormWrapperBuilder = (props) =>
({component, labels, fields, children=undefined}) =>
<VisualForm
configLists={props.configLists[component]}
showForm={visible => props.showForm(component, visible)}
loadConfiguration={index => props.loadConfiguration(component, index)}
updateIndex={index => props.updateIndex(component, index)}
metadata={props.metadata[component]}
labels={labels} fields={fields} >
{children}
</VisualForm>
export default FormWrapperBuilder
All of these are kept in separate files which may affect the rerendering of the page and it's the functions that are wrapped in the FormWrapper that lose focues after a state change. Also I've tried adding keys to each of these components without any luck. Also here's the functions in my container component:
const mapDispatchToProps = (dispatch) => {
return {
'updateValue': (component, field, value) => {
dispatch(updateValue(component, field, value))},
'showForm': (component, visible) => {
dispatch(showForm(component, visible))
},
'loadConfiguration': (component, index) => {
dispatch(loadConfiguration(component, index))
},
'pushConfiguration': (component) => {
dispatch(pushConfiguration(component))
},
'updateIndex': (component, index) => {
dispatch(updateIndex(component, index))
}
}
}
Edit 2: Modified my custom text input as such as per Hadas' answer. Also slathered it with keys and refs for good measure
export default class CustomTextInput extends React.Component {
constructor(props) {
super(props)
this.state = {value: ""}
}
render() {
return (
<FormField key={this.props.label} ref={this.props.label} label={<Label size='small'>{this.props.label}</Label>}>
<Box key={this.props.label} ref={this.props.label} pad={{'horizontal': 'medium'}}>
<TextInput placeholder='Please Enter' value={this.state.value} key={this.props.label} ref={this.props.label}
onDOMChange={event => { this.props.onChange(event.target.value); this.state.value = event.target.value }} />
</Box>
</FormField>
)
}
}
React is re-rendering the DOM on each key stroke. Try setting value to a variable attached to the component's state like this:
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChangeText={(text) => this.setState({text})}
value={this.state.text}></TextInput

Categories