PropTypes validation fails in functional component - javascript

I have a functional component below which has props coming from parent component and I added propTypes for the whole props object. However, the lint fails with the below error message.
9:16 error 'data' is missing in props validation react/prop-types
9:22 error 'data.text' is missing in props validation
Excerpt from code
import PropTypes from 'prop-types'
const Child = (props) => <div>{props.data.text}</div>
Child.propTypes = {
props: PropTypes.object
}
Could anyone please help?

As discussed in the comments, you have a few issues:
PropTypes.object is a very vague type declaration, and most linters will ding you for that
You are referencing properties of that object inside of your functional component that are not declared in your proptypes
You are using your prop declaration to attempt to refer to the props argument as a whole, rather than the properties within.
A more correct way to write all of this would be like so:
import PropTypes from 'prop-types';
/* Note we destructure `data` directly from props in the args */
const Child = ({ data }) => (<div>{data.text}</div>);
Child.propTypes = {
data: PropTypes.shape({ // use `shape` to allow us to declare types of properties within
text: PropTypes.string, // define the text property's type
}),
}
Additionally, you may want to define some of these items as required, or provide default entries.

From Alexander Nied solution, I made it work writing the below way without using shape, I think it is more generic.
const Child = ({ data }) => (<div>{data.text}</div>);
Child.propTypes = {
data: PropTypes.object
}

Related

How to add navigationOptions to a memoized react functional component?

I'm using flow types and have a memo wrapped functional component, however when trying to assign navigationOptions for react-navigation, flow is just going haywire:
On my component
const navigationOptions = ({navigation}) => ({}) // more stuff
const Foo = memo((props: IProps) => {
...
})
/*
Here it breaks down
Cannot assign `navigationOptions` to `Foo['navigation...']` because property `navigationOptions` is missing in `React.AbstractComponentStatics`
*/
Foo.navigationOptions = navigationOptions
export default Foo
Even if I type cast it to any, and then try to use it on my router:
/*
Cannot call `createStackNavigator` with object literal bound to `routeConfigMap` because in property `Foo.screen`: Either property `navigationOptions` is missing in AbstractComponent [1] but exists in `withOptionalNavigationOptions` [2]. Or property `router` is missing in AbstractComponent [1] but exists in `withRouter` [3].Flow(InferError)
*/
const FooStack = createStackNavigator({
Foo: { screen: FooScreen }
}, stackNavOptions)
How am I supposed to properly apply types here?
This is an expected behaviour. If you actually check React$AbstractComponentStatics you'll see that there are very few allowed properties, such as displayName which is valid. They've even removed propTypes which even though it's valid it's not encouraged which is why it has been removed.
To get around this you can use a suppression comments to mark it as a false positive,
// $FlowExpectedError navigationOptions
Foo.navigationOptions = navigationOptions
Flow supports $FlowFixMe and as of version 0.125.0, $FlowIssue and $FlowExpectedError by default to suppress errors.

props.images.map is not a function (function component)

Currently this two components are child components, so what I want to achieve here is to pass the props from Search component to Images component. Because I wrote everything in a function component, I thought this might be a great place for me
When user enters something in the input field it will automatically search for new images. In order to give you a better understanding of my issue, I will put a link to Codesandbox here. So I want to pass the props to the sibling component, so it will output the filtered search result.
https://codesandbox.io/s/patient-dawn-khouk
So only files you need to look at are: Search.js, Images.js
Ensure your images prop is actually an array. Can you do some debugging to figure out what your images object actually is?
Using propTypes is a way to have react help, and if it is nullable (i.e. not isRequired), then use a guard on it:
import React from "react";
import PropTypes from 'prop-types';
import Image from "./Image";
const propTypes = {
images: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired, // used as key
src: PropTypes.string.isRequired, // used for image source
<... other object properties ...>
})
),
};
const Images = ({ images }) => {
const images = images && images.map((image) => {
return <Image key={image.id} image={image}/>
});
return <div className="images">{images}</div>;
}
Images.propTypes = propTypes;
export default Images;

React: how to set up JSON value in defaultProps?

I am new to React and have been tasked with updating an existing component (a form). My task is to read a setting from JSON and show or hide an element based on that setting.
The JSON appears like:
"forms": {
"enquiry": {
"showConfirmCheckbox": "true"
},
},
Using and existing component that already reads this JSON and turns it into context which is called config, I can use the above as such:
In my propTypes I have:
static propTypes = {
config: PropTypes.object.isRequired,
}
And in my render function I just wrap the element in a conditional statement:
{config.forms.enquiry.showConfirmCheckbox &&
<Checkbox
etc, etc, etc
This works fine. However, I want to add a default for this value just in case the original JSON file is not updated correctly or is missing that entry. To that effect I added this to my defaultProps:
static defaultProps = {
config: {
forms: {
enquiry: {
showConfirmCheckbox: 'true',
},
},
},
}
However, this doesn't seem to work.
If I delete the line 'showConfirmCheckbox': 'true', from the JSON file the if condition still validates as false - it doesn't pick up the defaultProps value.
Would anyone know how to amend this? Is there something I am doing wrong here?
Are there other entries to your config, which are being passed while the showConfirmCheckbox isn't? The config prop is treated as a whole, if any object is passed, default props are not applied.
Docs on propTypes
There's no way you can default nested props because under the hood , defaultProps are only shallow merged with first-level props if props are missing.
, click for more information
However, in order to get around this, you may consider adding a few customized default logics in both lifecycle HOOK componentWillReceiveProps and constructor
And maybe the nested propTypes can also be helpful.
propTypes: {
configs: PropTypes.shape({
forms: PropTypes.shape({
enquiry: PropTypes.shape({
showConfirmCheckbox: PropTypes.bool.isRequired
})
})
})
}

Is it possible to create a React component interface?

I have the following react component:
class Cmp extends React.Component {
render () {
return <h3>{this.props.title}</h3>;
}
}
But I would like to expose or say to the consumer of my component to use it with a title otherwise it does not work the component.
Consumer would use it like
<Cmp title='Some fancy title' />
I need the consumer of my component to know that he should provide a title otherwise the component does not have any sense.
You can use PropTypes and set it to isRequired. You can also check if the prop is set at componentWillReceiveProps() and throw your error.
If you return null from a render method, nothing is rendered. You could use this knowledge to conditionally check if the prop is passed, and return null if the prop is not passed. The advantage here over using componentWillReceiveProps() is that you could use a functional component rather than a class component.
In rare cases you might want a component to hide itself even though it
was rendered by another component. To do this return null instead of
its render output.
Preventing Component from Rendering
Realistically you would also use PropTypes.
Cmp.propTypes = {
title: PropTypes.string.isRequired
};
Short Example
import React from 'react';
import PropTypes from 'prop-types';
const Cmp = (props) => props.title ? <h3>{props.title}</h3> : null
Cmp.propTypes = {
title: PropTypes.string.isRequired
}
export default Cmp;

Props is a deprecated symbol in React with Typescript

I have a problem: My IDE is complaining about deprecation.
I use react 16 and typescript 2.5.3
Following code given:
import React, { Props, StatelessComponent } from 'react';
import PropTypes from 'prop-types';
interface IProps {
id: string;
}
const Foo: StatelessComponent<IProps> = (props: IProps) => {
props.ref = nodeRef;
return (<div id={props.id}></div>);
};
Foo.propTypes = {
id: PropTypes.string,
};
Foo.defaultProps = {
id: 'bar',
};
export default Foo;
At this point I am getting at props.ref 'Property ref does not exist on Partial'
When I extend my interface IProps, like this:
interface IProps extends Props {
id: string;
}
At this point my IDE suggest to add an Generic Type
interface IProps extends Props<any> {
id: string;
}
Now I get deprecation warning to consult online docs, BUT I do not find anything. BUT my initial error with ref-property is gone.
Now my question: How to deal with this when I use a StatelessComponent? How to deal with it, when Using Component (or is there no error)? And how can I avoid any?
Thanks for helping me
You've accidentally masked the real problem by extending Props<T>! There's a comment in the type definitions explaining why that interface is deprecated:
This was used to allow clients to pass ref and key to createElement, which is no longer necessary due to intersection types.
In other words, you used to have to extend Props<T> for your prop type definitions so that ref/key/children would be included, but new features in TypeScript have made this unnecessary. You can just pass in a plain interface as you were doing initially.
However, this still leaves you with your 'Property ref does not exist' error - this is because you cannot use refs on stateless functional components. The type definitions are actually doing the right thing here and stopping you from writing code that won't work!

Categories