Refactoring a function to take an object instead of a single key:value pair as arguements in typescript - javascript

I am working on a component for a friends icon library, but I am having troubles with their set() function.
The issue is, I have two functions I want to call, setRandomColor() and setColor(), they should both be updating two values in this modelValue computed prop. However the set function only takes one argument (the prop (key) name and the value of that prop(key) ) and is spreading the rest of the props.modelValue values in the emit function.
Since my two setRandomColor() and setColor() are setting two values and each time the function is called, it's spreading the default modelValue, the first modelValue changed will be reverted.
I know this is a really simple question, but I am not able to figure out how to refactor this set() function to take a single OR multiple key: value pairs to update modelValue and emit it properly to it's parent component.
This should be a really quick fix, but I am having a total brainfart.
Any advice or help would be greatly appreciated.
NOTE:
My attempt to refactor the set() function is below this block of code.
Component not working
<script lang="ts" setup>
import { PropType, ref, defineComponent } from 'vue'
import DialogWrapper from '../components/DialogWrapper.vue'
import IconButton from './IconButton.vue'
import Stack from './Stack.vue'
import { getRandomColor, cssVar } from '../helpers/colorHelpers'
import { defaultsIconConfig, IconConfig } from '../types'
import Tooltip from './Tooltip.vue'
import { ColorPicker } from 'vue-color-kit'
import 'vue-color-kit/dist/vue-color-kit.css'
const props = defineProps({
/**
* #example 'type'
*/
kind: {
type: String as PropType<'type' | 'color' | 'stroke' | 'modelValueisDarkMode' | 'background'>,
required: true,
},
/**
* #type {{ name?: string, type: 'pop' | 'print', color: string, stroke: string} & { isDarkMode: boolean }}
*/
modelValue: {
type: Object as PropType<Partial<IconConfig> & { isDarkMode: boolean }>,
default: () => ({ ...defaultsIconConfig({ isDarkMode: false }) }),
},
/**
* #type {{ name?: string, type: 'pop' | 'print', color: string, stroke: string }}
*/
configComputed: {
type: Object as PropType<Partial<IconConfig>>,
default: () => ({ ...defaultsIconConfig() }),
},
})
const emit = defineEmits(['update:modelValue'])
function set(
prop: 'type' | 'color' | 'stroke' | 'isDarkMode' | 'randomColor',
value: string | boolean,
) {
emit('update:modelValue', { ...props.modelValue, [prop]: value })
}
function setRandomColor() {
const randomColor = getRandomColor()
set('color', randomColor)
set('randomColor', true)
}
function setColor(c: string) {
set('color', c)
set('randomColor', false)
}
const nightfall = cssVar('nightfall')
const moonlight = cssVar('moonlight')
function openColorPicker() {
colorPickerIsVisible.value = true
}
let colorPickerIsVisible = ref(false)
function changeColor(color) {
const alpha = ''
const newValue = color.hex + alpha
set('color', newValue)
}
const colorSelection = [
cssVar('sig-purple'),
cssVar('sig-green'),
cssVar('sig-yellow'),
cssVar('sig-blue'),
cssVar('sig-pink'),
]
</script>
<script lang="ts">
export default defineComponent({
inheritAttrs: false,
})
</script>
<template>
<Stack v-if="kind === 'type'" v-bind="$attrs" class="picker" classes="justify-center">
<Tooltip text="Print ❏">
<IconButton
:iconConfig="{
name: 'can',
type: 'print',
color: modelValue.isDarkMode ? 'black' : modelValue.color,
stroke: modelValue.isDarkMode ? modelValue.color : 'black',
}"
:backgroundColor="modelValue.isDarkMode ? moonlight : 'white'"
:isActive="modelValue.type === 'print'"
:activeColor="modelValue.color"
animationClass="anime-shake"
#click="set('type', 'print')"
/>
</Tooltip>
<Tooltip text="Pop!">
<IconButton
:iconConfig="{ ...modelValue, name: 'can', type: 'pop' }"
:backgroundColor="modelValue.isDarkMode ? moonlight : 'white'"
:isActive="modelValue.type === 'pop'"
animationClass="anime-shake"
#click="set('type', 'pop')"
/>
</Tooltip>
</Stack>
<Stack v-if="kind === 'color'" v-bind="$attrs" class="picker" classes="justify-center">
<IconButton
v-for="c in colorSelection"
:key="c"
:iconConfig="{ color: modelValue.color }"
:backgroundColor="c"
:isActive="modelValue.color === c && modelValue.randomColor === false"
#click="setColor(c)"
/>
<IconButton
:iconConfig="{ ...configComputed, name: 'color-picker' }"
:backgroundColor="modelValue.isDarkMode ? moonlight : 'white'"
:colorRing="true"
#click="colorPickerIsVisible = true"
/>
<IconButton
:iconConfig="{ ...configComputed, name: 'refresh' }"
:backgroundColor="modelValue.isDarkMode ? moonlight : 'white'"
:colorRing="true"
:isActive="modelValue.randomColor"
#click="setRandomColor"
/>
</Stack>
<Stack v-if="kind === 'background'" v-bind="$attrs" class="picker" classes="justify-center">
<IconButton
backgroundColor="white"
class="_background-picker thin-border--dark"
:iconConfig="{ name: 'sun-filled', type: 'pop', color: 'black' }"
#click="set('isDarkMode', false)"
/>
<IconButton
:backgroundColor="nightfall"
class="_background-picker thin-border--light"
:iconConfig="{ name: 'moon-filled', type: 'pop', color: 'white' }"
#click="set('isDarkMode', true)"
/>
</Stack>
<Stack v-if="kind === 'stroke'" v-bind="$attrs" class="picker" classes="justify-center">
<input type="color" #change="() => set('color', '#e2e2e2')" />
</Stack>
<DialogWrapper :isVisible="colorPickerIsVisible" #close="colorPickerIsVisible = false">
<ColorPicker
:theme="modelValue.isDarkMode === true ? 'dark' : 'light'"
:color="modelValue.color"
#changeColor="(val) => changeColor(val)"
/>
</DialogWrapper>
</template>
<style lang="sass">
#import '../css/variables.sass'
.picker
._background-picker svg
opacity: 0.1
.picker-tooltip
font-size: 1.5em
+pa($md)
+C(background, primary)
border-radius: $md
font-weight: 500
white-space: nowrap
.hu-color-picker
.color-set
justify-content: space-evenly
.color-alpha
display: none
</style>
This is my attempt to refactor the function, but when I'm spreading the array of objects from the set() argument, I'm still just getting two objects and am stuck from this point.
Attempts to refactor the set() function
function set(
object: {
prop: 'type' | 'color' | 'stroke' | 'isDarkMode' | 'randomColor'
value: string | boolean
}[],
) {
console.log(`object → `, ...object)
emit('update:modelValue', { ...props.modelValue, ...object })
}
function setRandomColor() {
const randomColor = getRandomColor()
set([
{ prop: 'color', value: randomColor },
{ prop: 'randomColor', value: true },
])
// set('color', randomColor)
// set('randomColor', true)
}
function setColor(c: string) {
set([
{ prop: 'color', value: c },
{ prop: 'randomColor', value: false },
])
// set('color', c)
// set('randomColor', false)
}

If I understand you correctly you want the set function to accept one OR more objects?
Add spread ”…” before the parameter. Sadly Im on my phone and snippet editor doesnt support touch devices.
function set(
… object: {
prop: 'type' | 'color' | 'stroke' | 'isDarkMode' | 'randomColor'
value: string | boolean
}[],
) {
More reading: https://www.c-sharpcorner.com/UploadFile/c63ec5/use-of-params-in-typescript/
To handle the parameter objects I guess something like:
{ …props.modelValue, …object.reduce((acc, {prop, value}) => {…acc, [prop]: value}, {});

Related

How can we pass the functions inside of single sx properties , say ( color, zIndex, backgroundColor) , etc? Is this possible somehow?

I am making a single search component for all the different places into my application.
And to each component I am passing a prop named search : string to check for the truthfulness of that particular component, so as to specify styles for the same.
I know I could use classNames instead of sx prop, but if I pass the sx prop which contain where every single property received the function , which in turn behaves accordingly to the type of which component is active for that moment. ( or way could say , which component has active), this is because my search bar for homepage has different styling then the search bar for items page.
Here's is my code
import { SearchSharp } from "#mui/icons-material";
import { alpha, FormControl, IconButton, InputAdornment, InputBase, makeStyles, styled, SxProps, TextField, Theme, useTheme } from "#mui/material";
import { Fragment } from "react";
import { useThemeContext } from "../../css/ThemeSettings";
import { useAppDispatch, useAppSelector } from "../../Redux/reduxStore";
import { setSearchTerm } from "./jobSlice";
interface Props {
search? : string;
}
export default function SearchAll ( props : Props ) {
// using redux
const { searchTerm } = useAppSelector ( state => state.jobs);
const dispatch = useAppDispatch();
// using theme
const { darkMode } = useThemeContext();
// background color // this is my function to trigger different backgroundColor based on which component is active...
const setBackgroundColor = () => {
switch ( props.search ) {
case 'home' : return darkMode ? 'rgba(0, 0, 0, 0.54)' : 'rgba(238, 240, 243, 1)';
case 'items' : return 'rgba( 255, 255, 255, 1 )';
}};
// similar to the above, i want to make other functions that I could pass into color, // width, position property etc.
// making and using styles
const searchStyles = {
position : 'relative',
borderRadius : 20,
color : 'text.secondary',
width : props.search === 'home' ? '500px' : '100%',
backgroundColor : setBackgroundColor(),
"& .MuiOutlinedInput-root": {
"& > fieldset": { border : "none" } },
'&:hover': {
backgroundColor : props.search === 'items' ? 'none'
: darkMode ? 'rgba(0, 0, 0, 0.54)' : 'rgba(238, 240, 243, 1)',
},
};
return (
<Fragment>
<TextField
variant = {'outlined'}
size = {'small'}
sx = {searchStyles} // i am getting an error here
placeholder = {"Search…"}
fullWidth = { props.search === 'items' ? true : false }
autoFocus = { true } />
</Fragment>
)
}
below is the error which I am getting when hovering on 'sx' keyword, as it is the thing which is throwing the error ...
The system prop that allows defining system overrides as well as additional CSS styles.
Type '{ position: string; borderRadius: number; color: string; width: string; backgroundColor: string | undefined; "& .MuiOutlinedInput-root": { "& > fieldset": { border: string; }; }; '&:hover': { backgroundColor: string; }; }' is not assignable to type 'SxProps<Theme> | undefined'.
Type '{ position: string; borderRadius: number; color: string; width: string; backgroundColor: string | undefined; "& .MuiOutlinedInput-root": { "& > fieldset": { border: string; }; }; '&:hover': { backgroundColor: string; }; }' is not assignable to type 'CSSSelectorObjectOrCssVariables<Theme>'.
Property 'backgroundColor' is incompatible with index signature.
Type 'string | undefined' is not assignable to type 'SystemStyleObject<Theme> | CssVariableType | ((theme: Theme) => string | number | SystemStyleObject<Theme>)'.
Type 'undefined' is not assignable to type 'SystemStyleObject<Theme> | CssVariableType | ((theme: Theme) => string | number |
Is there any way, we could use to pass functions ( which in turn return string) to these individual sx props?
or is there any another way to achieve this?
your setBackgroundColor you use for setting backgroundColor in your searchStyles not compatible with the property backgroundColor (Property 'backgroundColor' is incompatible with index signature.) as is could possible return undefined while backgroundColor requires a "SystemStyleObject | CssVariableType | ((theme: Theme) => string | number | SystemStyleObject)" and does not accept undefined as a valid type
You should be able to fix this by adding a default case to your setBackGroundColor method
const setBackgroundColor = () => {
switch ( props.search ) {
case 'home': return darkMode ? 'rgba(0, 0, 0, 0.54)' : 'rgba(238, 240, 243, 1)';
case 'items': return 'rgba( 255, 255, 255, 1 )';
default: return 'none'
}};

Typescript Type to accept a React component type with subset of Props

There is a component Button with following props:
ButtonProps = {
variant: 'primary' | 'secondary' | 'tertiary';
label: string;
// a few more props like onChange, size etc.
}
Now, I want to create another component called "ButtonGroup" component that accepts a Button instance as a prop but can only accept primary or secondary variant.
How can I enforce that?
ButtonGroup component looks this:
<ButtonGroup
primaryButton={<Button variant="primary">Submit</Button>}
otherButton={<Button variant="secondary">Cancel</Button>}
/>
Now, the props for ButtonGroup are as follwos:
type PrimaryButtonProps = Omit<ButtonProps, 'variant'> & {
variant: 'primary' | 'secondary';
};
type ButtonGroupProps = BaseComponentProps<'div'> & {
size?: 'small' | 'medium';
primaryButton: React.ReactElement<PrimaryButtonProps, typeof Button>;
otherButton?: React.ReactElement<OtherButtonProps, typeof Button>;
};
I expect primaryButton to be a Button instance will all Button props but restricting variant to be either primary or secondary. But, with this current implementation, typescript doesn't complain if I provide a tertiary variant too.
<ButtonGroup
primaryButton={<Button variant="tertiary">Submit</Button>} // TS SHOULD COMPLAIN BUT IT DOES NOT
/>
In my opinion the cleanest solution would be to separate the implementation of each component to enforce its specific types.
interface ButtonProps {
variant: "primary" | "secondary" | "tertiary";
children?: React.ReactNode;
}
const Button = ({ variant, children }: ButtonProps): React.ReactElement => (
<button>{children}</button> // apply styles based on the variant
);
interface PrimaryButtonProps {
label: string;
variant: "primary" | "secondary";
}
const PrimaryButton = ({ label, variant }: PrimaryButtonProps) => (
<Button variant={{ variant }}>{{ label }}</Button>
);
So, when you create a ButtonGroup, you should pass the specific PrimaryButton type, instead the generic one
type ButtonGroupProps = BaseComponentProps<'div'> & {
size?: 'small' | 'medium';
primaryButton: React.ReactElement<PrimaryButtonProps, typeof PrimaryButton>;
// ...
};
<ButtonGroup
primaryButton={<PrimaryButton variant="tertiary">Submit</PrimaryButton>} // TS should complain here
/>
Hope this helps!
You can use PrimaryButton component instead of Button and it will highlight the variant other than primary or secondary.
Checkout the working CodeSandbox here.
interface ButtonProps {
variant: "primary" | "secondary" | "tertiary";
label: string;
}
function Button({ variant, label }: ButtonProps) {
return (
<button style={{ color: variant === "primary" ? "blue" : "red" }}>
{label}
</button>
);
}
type PrimaryButtonProps = Omit<ButtonProps, "variant"> & {
variant: "primary" | "secondary";
};
interface ButtonGroupProps {
primaryButton: React.ReactElement<PrimaryButtonProps>;
}
function ButtonGroup({ primaryButton }: ButtonGroupProps) {
return <div>{primaryButton}</div>;
}
// See below type assertion. It's a hack but it works :)
const PrimaryButton = Button as (props: PrimaryButtonProps) => JSX.Element;
export default function App() {
return (
<div className="App">
<ButtonGroup
primaryButton={<PrimaryButton variant="tertiary" label="Primary" />}
/>
</div>
);
}

How to infere a type from a generic type in Typescript

I know the title might confusing, but I didn't know how to put it.
The thing is, I have a React component that receives an array of objects with the key/values of an input, like this:
[{id: 'my-input', label: 'my input', defaultValue: 'foo'}, ...and so on]
The label value is custom of course, because I'm extending the InputHTMLAttributes<HTMLInputElement> interface.
Now, what I do when an Input changes is pass that value to a state called formData, that looks like this:
{
myInput1: 'value',
myInput2: 'anotherValue',
myCheckbox: false,
myOtherCheckbox: true
}
And finally, the submit function. I think it explains itself if I just write it:
<Form
onSubmit={(data) => mySubmitFunction(data)}
id='my-form'
inputs={formInputs}
/>
I define the data param in the FormProps interface to be like {[key: string]: BaseInput['value'] | BaseInput['checked']}, which is fine, but I want it to do something better. I want the data param to infere that every key should be an input label (or if it isn't specified, use the id), and the value should be that input value type, so if I do something like:
<Form
onSubmit={(data) => mySubmitFunction(data)}
id='my-form'
inputs={[
{
id: 'input-1',
type: 'text',
},
{
id: 'input-2',
label: 'myInputCheck',
type: 'checkbox'
}
]}
/>
The data param should look like this:
data: {
'input-1': string, // it knows that I didn't specify a label, so it takes the id instead
myInputCheck: boolean
}
The chain of events is like this:
First, I call the component:
const myInputs: FormInputs = [
{
id: 'text',
type: 'text',
key: createStr(),
},
{
id: 'number',
type: 'number',
key: createStr(),
},
{
id: 'radio',
type: 'radio',
key: createStr(),
},
{
id: 'checkbox',
type: 'checkbox',
key: createStr(),
},
]
<Form
onSubmit={(data) => mySubmitFunction(data)}
id='form-maker'
inputs={myInputs}
/>
Then, inside the component (the bussiness logic)
import { cloneDeep } from 'lodash'
import { useEffect, useState } from 'react'
import { FormData, FormProps } from '../../interfaces'
const useForm = ({ id: myFormID, inputs }: Omit<FormProps, 'onSubmit'>) => {
const [formData, setFormData] = useState<FormData>({})
const formID = `form__${myFormID}`
const onInputChange = (key: string, value: any) =>
setFormData({ ...formData, [key]: value })
const getInputValue = (
{
type,
value,
defaultValue = '',
checked,
defaultChecked = false,
}:
| typeof inputs[0]
| typeof inputs[1]
| (EventTarget & HTMLInputElement),
useDefault?: boolean
) => {
switch (type) {
case 'checkbox':
case 'radio':
return useDefault ? defaultChecked : checked
default:
return useDefault ? defaultValue : value
}
}
const createFormData = () => {
const newFormData = cloneDeep(formData)
inputs.forEach((input) => {
newFormData[input.id] = getInputValue(input, true)
})
setFormData(newFormData)
}
useEffect(() => {
createFormData()
}, [])
return { formID, onInputChange, getInputValue, formData }
}
export default useForm
When the component mounts, it creates the formData state from the inputs array, givin it its default value/checked and rendering the Form component using the inputs array (nothing relevant, just rendering from that array so you don't need to see that). >>>
Then, when an input changes, it sets its value/checked to the formData state. >>>
Then, when the form submits onSubmit={() => onSubmit(formData)} it just calls the 'onSubmit' function passed when I call the component, with the formData arg, so I can do stuff with that data.
Here, the interfaces:
// I rewrite this one because I want to specifically use those and nothing else
type BaseInputType =
| 'button'
| 'checkbox'
| 'color'
| 'date'
| 'datetime-local'
| 'email'
| 'file'
| 'hidden'
| 'image'
| 'month'
| 'number'
| 'password'
| 'radio'
| 'range'
| 'reset'
| 'search'
| 'submit'
| 'tel'
| 'text'
| 'time'
| 'url'
| 'week'
interface BaseInput
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'type'> {
id: string
key: string
label?: string
type: BaseInputType
}
interface InputSelect extends Omit<BaseInput, 'type'> {
type: 'select'
options: SelectOption[]
}
type FormInputs = (BaseInput | InputSelect)[]
interface FormData {
[key: string]: BaseInput['value'] | BaseInput['checked']
}
interface FormProps
extends Omit<FormHTMLAttributes<HTMLFormElement>, 'id' | 'onSubmit'> {
id: string
inputs: FormInputs
onSubmit: (data: FormData) => void
}
I hope I made myself clear: I want Typescript to take the inputs array I pass to the Form, and on the formData state, specifically on the submit function, infere every key/type pair based on the inputs array I first passed.
If and only if possible, I'd like to be able to transform the key to camelCase. I have a function that does just that, but I don't know if I can use it inside an interface or something like that, or if in the chain of events I can call it and typescript will know that the output will be camelCased.

Map Formik Text-Fields

I am using material ui text-fields and validating them with Formik. Instead of typing everything down multiple times, I want to map items but I am unable to do so.
return (
<div>
<Formik
initialValues={{ email: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}>
{props => {
const {
values: { email },
errors,
touched,
handleChange,
isValid,
setFieldTouched,
} = props;
const change = (name: string, e: FormEvent) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
};
return (
<div className="main-content">
<form
style={{ width: '100%' }}
onSubmit={e => {
e.preventDefault();
submitForm(email);
}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="email"
name="email"
helperText={touched.email ? errors.email : ''}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, 'email')}
/>
{/* {[{ variant:"outlined", margin:"normal", id:"email", name:"email", label: "Email", value: email, onChange:{change.bind(null, 'email')}
].map((item, index) => (
<TextField></TextField>
))} */}
<br></br>
<CustomButton
text={'Remove User'}
/>
</div>
</form>
</div>
);
}}
</Formik>
</div>
);
Currently in the commented out part, I get errors on change.bindthat (property) change: (name: string, e: React.FormEvent<Element>) => void
',' expected.ts(1005)
Similarly, If I try to add helperText:{touched.email ? errors.email : ''}in the list of parameters to be mapped onto the text-field, I get this:
(property) touched: FormikTouched<{
email: string;
}>
',' expected.ts(1005)
on touched.email. Same goes for when I try to use errors.
(property) touched: FormikTouched<{
email: string;
}>
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'FormikTouched<{ email: string; }>'.
No index signature with a parameter of type 'string' was found on type 'FormikTouched<{ email: string; }>'.ts(7053)
Thank you for the sandbox.
Based on your sandbox I've managed to make the fields iterations through the map function.
First separate the unique field props
The props that belong to the input, like name id label etc.
The props that belong to the component, like variant margin handlers etc.
You will end up with 2 objects.
(1)One that should be abstract to the component and saved in a separate file, you may use the fields in another component later.
Here because you use formik and you need to have a reference key to formik, I've added the formikRef in the object.
const fileds = [
{
id: "email",
name: "email",
formikRef: "email",
label: "Email"
},
{
id: "name",
name: "name",
formikRef: "name",
label: "Name"
}
];
(2) The second object that should be stored in the component because it resolves the <TextField/> props
I usually wrap them in useMemo hook so it should not be redeclared if the dependency objects are not changed. This is for performance only, you can use direct object also
const defaultProps = React.useMemo(() => ({
textField: {
variant: 'outlined',
margin: 'normal',
onChange: props => {
formik.handleChange(props);
formik.handleBlur(props);
},
onBlur: formik.handleBlur
}
}),
[formik]
);
//without useMemo
const defaultProps = {
textField: {
variant: 'outlined',
margin: 'normal',
onChange: props => {
formik.handleChange(props);
formik.handleBlur(props);
},
onBlur: formik.handleBlur
}
}
The final step is to map the props in the render
{
fileds.map(({ formikRef, ...input }) => (
<TextField
key={formikRef}
helperText={getIn(formik.touched, formikRef) ? getIn(formik.errors, formikRef) : ''}
error={getIn(formik.touched, formikRef) && Boolean(getIn(formik.errors, formikRef))}
value={getIn(formik.values, formikRef)}
{...input}
{...defaultProps.textField}
/>
))
}
I've also created a working sandbox HERE
LE: Type checking was an issue but the solution could be found with a little search... It was discussed here
I've made the corrections and added getIn helper function from Formik in order to pass the type checking issues.

React : Warning: Failed prop type: Cannot read property 'apply' of undefined

I am working on a navbar in react and i have been getting this error, and i have not found a working solution yet. Here is the piece of my code. I am trying to use react prop-types library to validate my navbar props with either a link or a dropdown. Below is the piece of code i have written.
NavBar.js
const NavBar = ({ navItems }) => {
return (
<nav role="navigation" className={styles.navBar}>
<Logo type="text">
ABCD
</Logo>
<div className={[styles.collapse, styles.noCollapse].join(' ')}>
<SearchBox />
<NavItems navItemsData={navItems} />
</div>
</nav>
);
};
NavItems.js
const NavItems = ({ navItemsData }) => {
return (
<ul className={styles.navItems}>
{navItemsData.map(navItemData => {
let navItem = <NavItem {...navItemData} key={navItemData.id} />
if (navItemData.type === 'dropdown') {
navItem = <NavDropDownItem {...navItemData} key={navItemData.id} />
}
return navItem;
})}
</ul>
);
};
PropTypes Checker(in same file as NavItems) :-
NavItems.propTypes = {
navItemsData: PropTypes.arrayOf(PropTypes.shape({
id : PropTypes.number,
type: PropTypes.oneOf(['link', 'dropdown']).isRequired,
linkText: requiredIf(PropTypes.string.isRequired, props => props.type === 'link'),
title : requiredIf(PropTypes.string.isRequired, props => props.type === 'dropdown'),
dropDownList: requiredIf(PropTypes.arrayOf(PropTypes.shape({ linkText: PropTypes.string.isRequired })), props => props.type === 'dropdown')
}))
};
I keep getting this warning in the console. As follows :-
Warning: Failed prop type: Cannot read property 'apply' of undefined
in NavItems (at NavBar.js:15)
in NavBar (at App.js:35)
in div (at App.js:34)
in App (at src/index.js:7)
The props i am passing :
const navItems = [{
id : 1,
type : 'link',
linkText : 'Link1'
},{
id : 2,
type : 'link',
linkText : 'Link2'
}, {
id : 3,
type : 'link',
linkText : 'Link3'
}, {
id : 4,
type : 'link',
linkText : 'Link4'
},{
id : 5,
type : 'link',
linkText : 'Link5'
},{
id : 6,
type : 'dropdown',
dropDownList : [{
linkText : 'LinkText'
}]
}]
Any help would be appreciated.
When you are using requiredIf, the first parameter should be the expected type. But it should not have the isRequired part. For example, your validation should be as follows.
NavItems.propTypes = {
navItemsData: PropTypes.arrayOf(PropTypes.shape({
id : PropTypes.number,
type: PropTypes.oneOf(['link', 'dropdown']).isRequired,
linkText: requiredIf(PropTypes.string, props => props.type === 'link'),
title : requiredIf(PropTypes.string, props => props.type === 'dropdown'),
dropDownList: requiredIf(PropTypes.arrayOf(PropTypes.shape({ linkText: PropTypes.string.isRequired })), props => props.type === 'dropdown')
}))};
There is no need to do PropTypes.string.isRequired inside requiredIf as it already covers the required case.
NavItems.propTypes = {
navItemsData: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number,
type: PropTypes.oneOf(["link", "dropdown"]).isRequired,
linkText: requiredIf(PropTypes.string, props => props.type === "link"),
title: requiredIf(PropTypes.string, props => props.type === "dropdown"),
dropDownList: requiredIf(
PropTypes.arrayOf(PropTypes.shape({ linkText: PropTypes.string })),
props => props.type === "dropdown"
)
})
)
};

Categories

Resources