DRY jsx render method - javascript

I wonder if it is a better way to DRY this code, have you guys any ideas?
The props are the same, just the component change...
render() {
const { input: { value, onChange }, callback, async, ...rest } = this.props;
if (async) {
return (
<Select.Async
onChange={(val) => {
onChange(val);
callback(val);
}}
value={value}
{...rest}
/>
);
}
return (
<Select
onChange={(val) => {
onChange(val);
callback(val);
}}
value={value}
{...rest}
/>
);
}

With:
let createElement = function(Component) {
return (
<Component onChange={(val) => {
onChange(val);
callback(val);
}}
value={value}
{...rest}
/>
);
};
you can do
let selectAsync = createElement(Select.Async);
let select = createElement(Select);
You can render them in the jsx part with {{select}} and {{selectAsync}}
P.S.: I didnt test this directly, but did something very similar a few days ago, so this approach should work. Note that Component must start with a capital letter.

Related

React native typeScript and forwardRef in a functional component

I'm in react native app an I use typeScript too.
I have a functional component :
const Input: React.FunctionComponent<IInputProps> = ({
inputStyle,
placeHolderColor = EAppColors.DARK_GREY,
placeHolder,
value,
onChangeText,
autoFocus,
onFocus,
onBlur,
onSubmitEditing,
ref,
keyboardType = EKeyboardType.DEFAULT,
}) => {
return (
<StyledInput
testID="TextInputID"
placeholderTextColor={placeHolderColor}
placeholder={placeHolder}
...
I create some ref for different input before my render:
const firstNameRef = React.createRef<TextInput>();
const lastNameRef = React.createRef<TextInput>();
const birthDateRef = React.createRef<TextInput>();
and I use after this component in a class like that :
<StyledTextInput
label={I18n.t('auth.heading.firstNameLabel')}
errorText={errorText}
ref={firstNameRef}
autoFocus={true}
placeHolder={I18n.t('auth.placeHolder.firstName')}
isFocused={focusOnFirstFields}
hasError={hasError}
onFocus={() => this.setState({ focusOnFirstFields: true })}
onBlur={() => this.setState({ focusOnFirstFields: false })}
showClearButton={showFirstClearButton}
value={firstName}
onClearText={() => this.onClearText(1)}
onChangeText={(value: string) =>
this.setState({
firstName: value,
disabled: false,
showFirstClearButton: true,
})
}
onSubmitEditing={() => {
if (lastNameRef !== null && lastNameRef.current !== null) {
lastNameRef.current.focus();
}
}}
keyboardType={EKeyboardType.DEFAULT}
/>
But when I want to use onSubmitEditing for focus the next input, I have this error :
How can I resolve this issue ?
Thank You!
Like this:
const FancyButton = React.forwardRef</* type of ref*/HTMLButtonElement, /* component props */ComponentProps>((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>))
It will be correctly typed as
const FancyButton: React.ForwardRefExoticComponent<React.RefAttributes<HTMLButtonElement>>
(You don't need to use React.FunctionComponent when using forwardRef).
const Input = React.forwardRef<TextInput, IInputProps>(({
inputStyle,
placeHolderColor = EAppColors.DARK_GREY,
placeHolder,
value,
onChangeText,
autoFocus,
onFocus,
onBlur,
onSubmitEditing,
keyboardType = EKeyboardType.DEFAULT,
}, ref /* <--- ref is passed here!!*/) => {
// assuming this is a TextInput
return (
<StyledInput
ref={ref}
testID="TextInputID"
placeholderTextColor={placeHolderColor}
placeholder={placeHolder}
...
})
I faced a similar problem a few months ago. I solved it by doing:
import {TextInputProps, TextInput} from 'react-native';
type IProps = TextInputProps & {
labelText?: string;
};
const TextInputStd: React.FC<IProps> = React.forwardRef(
(
{
labelText,
...textInputProps
}: IProps,
ref: React.Ref<TextInput>,
) => {
const {styles} = useStyles(_styles);
return (
<>
<View style={[styles.textInputContainer, styles2.textInputContainer]}>
<Text style={styles.labelText}>{labelText || ''}</Text>
<View style={styles.inputWrapper}>
<TextInput style={styles.input} {...textInputProps} ref={ref} />
</View>
</View>
</>
);
},
);
Hope this gives you an idea.
not 100% sure what the question is here, but
<StyledInput
ref={ref}
testID="TextInputID"
placeholderTextColor={placeHolderColor}
placeholder={placeHolder}
...
should work, then you need to pass the ref in when calling this input.

How to simplify overriding props in React?

I have an If statement and returning the same component with the extra props based on the state. Any idea how to simplify this? Can I use recursion? Any idea?
iconRight is only difference.
renderInput = () => {
if (isLoading) {
return (
<Input
iconRight={(
<Spinner />
)}
autoComplete="off"
id="unique-id-2"
aria-autocomplete="both"
/>
);
}
return (
<Input
autoComplete="off"
id="unique-id-2"
aria-autocomplete="both"
/>
);
}
}
You can spread props onto the component:
renderInput = () => {
const props = {
autoComplete: 'off',
id: 'unique-id-2',
'aria-autocomplete': 'off'
};
if (isLoading) {
return (
<Input
iconRight={(
<Spinner />
)}
{...props}
/>
);
}
return (
<Input {...props} />
);
}
}
But i'd suggest changing your Input component to accept a loading prop and let the Input component handle that logic. It'll make your consuming code a lot easier to read also.
I think your function can be shortened to the following;
renderInput = () => (
<Input
iconRight={isLoading ? (<Spinner />) : null}
autoComplete="off"
id="unique-id-2"
aria-autocomplete="both"
/>
)
If you don't already, inside your Input component, you should check if the iconRight prop is not null, and only render it then.
You can try this:
renderInput = () =>(
<Input
iconRight={
isLoading && (
<Spinner />
)}
autoComplete="off"
id="unique-id-2"
aria-autocomplete="both"
/>
)

How to do conditional check twice in React component

I have a React component as shown. I am passing prop hasItems and based on this boolean value, i am showing PaymentMessage Component or showing AddItemsMessage component.
export const PayComponent = ({
hasItems
}: props) => {
return (
<Wrapper>
{hasItems ? (
<PaymentMessage />
) : (
<AddItemsMessage />
)}
<Alerts
errors={errors}
/>
</Wrapper>
);
};
This works well. Now, i need to pass another prop (paymentError). So based on this, i modify the JSX as below. I will highlight the parts i am adding by using comment section so it becomes easy to see.
export const PayComponent = ({
hasItems,
paymentError //-----> added this
}: props) => {
return (
<Wrapper>
{!paymentError ? ( //----> added this. This line of code errors out
{hasItems ? (
<PaymentMessage />
) : (
<AddItemsMessage />
)}
):( //-----> added this
<Alerts
errors={errors}
/>
) //-----> added this
</Wrapper>
);
};
Basically, i am taking one more input prop and modifying the way my JSX should look. But in this case, i am not able to add one boolean comparison one after the error. How do i make it working in this case. Any suggestions please ???
I recommend you to create a function to handle this behavior. It's easier to read and to mantain
export const PayComponent = ({
hasItems,
paymentError
}: props) => {
const RenderMessage = () => {
if (hasItems) {
if (paymentError) {
return <PaymentMessage />
}
return <AddItemsMessage />
}
return <Alerts errors={errors}/>
};
return (
<Wrapper>
<RenderMessage />
</Wrapper>
);
};

slightly adapted HOC in react DRY

I have a simple HOC which injects a react context as a prop in the wrappedcomponent.
function withTranslate(WrappedComponent) {
//we forward the ref so it can be used by other
return React.forwardRef((props, ref) => (
<TranslatorContext.Consumer>
{context => (<WrappedComponent {...props} translate={context} ref={ref} />)}
</TranslatorContext.Consumer>)
)
}
Now I want a secondary HOC which uses the same context, but changes some predefined props using this context. I succeed with following code:
export function withTranslatedProps(WrappedComponent,propsToBeTransLated) {
//propsToBetranslated is array with all props which will be given via keys
const translateProps=(translate,props)=>{
const ownProps=Object.assign({},props)
propsToBeTransLated.forEach(p=>{
if(ownProps.hasOwnProperty(p)){
ownProps[p]=translate(ownProps[p])
}
})
return ownProps
}
return React.forwardRef((props, ref) => {
console.log("render contextconsumer")
return (
<TranslatorContext.Consumer>
{context => (
<WrappedComponent {...translateProps(context,props)} ref={ref} />
)}
</TranslatorContext.Consumer>)
})
}
But I almost exactly use the same HOC as withTranslate. Is there a better option (without repeating myself) to do this?
edit
I think i solved it:
const _translateProps=(propsToBeTransLated,translate,props)=>{
const ownProps=Object.assign({},props)
propsToBeTransLated.forEach(p=>{
if(ownProps.hasOwnProperty(p)){
ownProps[p]=translate(ownProps[p])
}
})
return ownProps
}
export function withTranslatedProps(WrappedComponent,propsToBeTransLated) {
//propsToBetranslated is array with all props which will be given via keys
let retrieveProps=propsToBeTransLated?_translateProps.bind(null,propsToBeTransLated):(context,props)=>({translate:context,...props})
return React.forwardRef((props, ref) => {
console.log("render contextconsumer")
return (
<TranslatorContext.Consumer>
{context => (
<WrappedComponent {...retrieveProps(context,props)} ref={ref} />
)}
</TranslatorContext.Consumer>)
})
}
Anyone with other possibly better solutions?
You can reuse withTranslate HOC or use the same HOC adding options.
Reusing withTranslate HOC:
/* function that translate the propsToBeTransLated */
const translateProps = (propsToBeTransLated, translate, props) =>
propsToBeTransLated.reduce((translatedProps, prop) => {
if(props.hasOwnProperty(prop))
translatedProps[prop] = translate(props[prop]);
return translatedProps;
}, {});
export function withTranslatedProps(WrappedComponent, propsToBeTransLated = []) {
// HOC inside HOC
const addTranslationsToProps = WrappedComponentWithContext =>
React.forwardRef((props, ref) => (
<WrappedComponentWithContext
{...props}
{...translateProps(propsToBeTransLated, props.translate, props)}
ref={ref}
/>
)
);
// first call withTranslate to add the context
return addTranslationsToProps(withTranslate(WrappedComponent));
}
Adding options to withTranslate HOC
const translateProps = (propsToBeTransLated, translate, props) =>
propsToBeTransLated.reduce((translatedProps, prop) => {
if(props.hasOwnProperty(prop))
translatedProps[prop] = translate(props[prop]);
return translatedProps;
}, {});
export function withTranslate(WrappedComponent, options) {
const { propsToBeTransLated = [] } = options;
return React.forwardRef((props, ref) => (
<TranslatorContext.Consumer>
{context => (
<WrappedComponent
{...props}
{...translateProps(propsToBeTransLated, context, props)}
translate={context}
ref={ref}
/>
)}
</TranslatorContext.Consumer>
));
}

Handle 'lambda in JSX attribute' case

There is an existing code:
const FooRoute: React.SFC<RouteProps> =
({ component: Component, ...rest }) => {
if (!auth.isFoo()) {
return <Redirect to={{ pathname: '/404' }} />;
}
if ('render' in rest) {
return (
<Route {...rest} render={rest.render} />
);
} else {
return (
Component
?
<Route
{...rest}
render={
(props: RouteComponentProps<{}>) => <Component {...props} />}
/>
: null
);
}
};
There is an error here:
Now how does the getComponent function will look like in this case?
Thought about smth like:
const getComponent = (props: RouteComponentProps<{}>) => () => (
<Component {...props} />
)
Then one can simply:
<Route
{...rest}
render={getComponent}
/>
But in this case Component is undefined. Any clues?
EDIT: Important Note - using TypeScript. So have to pass the Component somehow down into getComponent.
EDIT2: the reason I've used double lambda is because it allows handling situations like this:
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => (foo: string) => {
...
}
with
<button onClick={handleClick('bar')} ... // foo === 'bar'
In your case getComponent is a lambda function which returns another lambda function but not component because of (props) => () => .... Modify your getComponent function to receive Component from render function like below.
const getComponent = (Component: Component) => (props: RouteComponentProps<{}>) => (
<Component {...props} />
)
and modify this 'render' function too.
<Route
{...rest}
render={getComponent(Component)}
/>
Note: You are receiving component as component with small c and using it as Component with capital C.

Categories

Resources