Say that I create a custom component like this:
const MyComponent = (props) => (
<div
className={props.name}
id={props.id}>
{props.children}
</div>
)
And I need to make sure that props contain the variables name and id, because otherwise what I want to do is not going to work (now I know that this code will work anyhow, but hypothetically say that it won't).
Is there a way in React to demand that certain props are passed to a component?
Maybe this is something that is obvious, but I couldn't find any information about it.
You can use PropTypes. It was earlier a part of React but now it has its own npm package, https://www.npmjs.com/package/prop-types. This will give you a runtime error if ther props are not provided. Its also useful, because linters can warn you if you miss them.
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md
import React from 'react';
import PropTypes from 'prop-types';
const MyComponent = (props) => (
<div
className={props.name}
id={props.id}>
{props.children}
/>
);
MyComponent.propTypes = {
name: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
element: PropTypes.arrayOf(PropTypes.element).isRequired
};
Related
I want to define a bunch of data in a constants file which will be used to render a series of React components, including some HTML, which I'd like to be able to write in JSX. Below is a simplified example of what I'd like to do:
// constants.ts
export interface ItemInfo {
title: string;
description: React.ReactElement; // Or whatever this type should be. Just trying to get it working for now, can figure out correct typing later.
}
export const DATA: ItemInfo[] = [
{
title: 'Foo',
// Pseudo code below, how can I get this working?
description: (
<>
<p>Some JSX.</p>
<p>To be rendered in a React component.</p>
</>
),
},
{
title: 'Bar',
description: (
<>
<p>More JSX.</p>
<p>To be rendered in a React component.</p>
</>
),
},
// etc
];
// ItemComponent.tsx
import React from 'react';
import { ItemInfo } from './constants';
const ItemComponent: FC<ItemInfo> = ({title, description}) => (
<div>
<h2>{title}</h2>
<div>{description}</div>
</div>
);
// ListComponent.tsx
import React from 'react';
import { ItemInfo } from './constants';
const ListComponent: FC<ItemInfo[]> = ({items}) => (
<div>
{items.map((item) => <ItemComponent {...item} />)}
</div>
);
I'm using TypeScript, so I've done the simplified example above in TS as well, though I don't think it should matter. I've tried importing React in the constants.ts file, and using React.createElement() on the JSX, but to no avail. I can just move the DATA constant inside of the ListComponent, in which case everything works, but I want to decouple the data from the component, so that it can be used to render different lists of data in different places.
I'm open to suggestions about avoiding using this pattern (in which case please offer reasons why and alternatives), but if it is possible to do this I'd still also like to know how in addition to knowing why I shouldn't and what I should do instead :)
Any insights appreciated, thanks!
Oh, actually I just figured it out. All I had to do was change the constants.ts file to a constants.tsx file.
I'll leave this question up in case it's helpful to anyone else since I didn't find great results when Googling this question (probably because it was such an obvious mistake haha).
If anyone does have comments on whether and why this pattern should or shouldn't be used, I am also still interested.
For example i have library/package which exports some component:
function SomeComponent() {
...
return (
<View>
...
<TextInput ... />
...
</View>
);
}
Is there any way I can use SomeComponent but instead of TextInput I somehow inject my custom MyCustomTextInput?
I know that is possible to create SomeComponent with CustomTextInput prop (it does not matter if CustomTextInput default value is specified like this or with defaultProps static initialization):
function SomeComponent({ CustomTextInput = TextInput }) {
...
return (
<View>
...
<CustomTextInput ... />
...
</View>
);
}
What I need is easy way to tell to the whole application (all packages, all our modules/code) "where ever you see TextInput component use my custom MyCustomTextInputComponent". Is this possible in react/react-native?
You can make index for component export package with conditions:
Look at example project:
Example: https://snack.expo.io/#djalik/thrilled-donut
index.js:
import React from 'react';
import {TextInput} from 'react-native';
const MyTextInput=()=>{
return <TextInput value={'custom'}/>
}
const MyTextInput2=()=>{
return <TextInput value={'custom2'}/>
}
let custom1 = false;
export default (custom1?MyTextInput:MyTextInput2);
and import in app where you need it:
import {MyTextInput} from './components'
What you want to achieve (substitute react-native's TextInput with your own component across the entire app) is not possible to do. You can't force library to render another component either (unless it specifically lets you pass that component as a prop). Solution here would be forking this library on github, making necessary changes to support custom component prop and using github link in your package.json file
I have some difficulties to understand the new way to use react Context API.
I have an app with a custom class Firebase. Now I want to make a hook to pass it. Before I used HOC (higher-order Component) and context.
My questions
Do I need to use HOC or it's a new way to do this?
Do I need the Context.Provider or it's new Hook?
Do I need to declare default value as a null or I can pass my Object
right from context.js
How can I use a new Hook instead of HOC in mine code?
Here is my code with some comments related to questions
// context.js this is my hoc
// index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
export const withFirebase = Component => (props) => {
// I don't need to wrap it to the FirebaseContext.Consumer
// 1 But do I need this HOC or it's a new way?
const firebase = useContext(FirebaseContext)
return <Component {...props} firebase={firebase} />
}
ReactDOM.render(
// 2 Here I'm lost. Do I need the FirebaseContext.Provider or not?
// 3 Do I need to declare value her or I should do it in context.js as a default?
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
// App.jsx
// 4 Can I use a new Hook instead of HOC here and how?
import { withFirebase } from './components/Firebase/context'
const App = () => {
const firebase = this.props.firebase // But should be useContext(FirebaseContext) or something like this?
return(...)
}
export default withFirebase(App) // I don't need this with the Hook
Any help appreciated.
You should understand it first that, useContext is just to make use of Context and acts like a consumer and not Provider.
To answer your questions
Do I need to use HOC or it's a new way to do this?
You don't need an HOC with hooks. Hooks are meant to replace HOCs and render props pattern.
Do I need the Context.Provider or it's new Hook?
There is no hooks equivalent of Context.Provider. You have to use it as is.
Do I need to declare default value as a null or I can pass my Object
right from context.js
The default value to createContext is only used if you don't pass a value props to the Context.Provider. If you pass it the default value is ignored.
How can I use a new Hook instead of HOC in mine code?
Instead of using useContext in the component returned by HOC use it directly within the component
Sample code
/ context.js this is my hoc
// index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
App.jsx
const App = () => {
const firebase = useContext(FirebaseContext)
return(...)
}
export default App;
Do I need to use HOC or it's a new way to do this?
No, you don't need to use HOC as best technique.
Why?
Starting from React v7.0, you can use functional-based components.
From this version efficient is to use the the latest
technique named HOOKS, which were designed to replace class and
provide another great alternative to compose behavior into your
components.
Do I need the Context.Provider or it's new Hook?
Hook like useContext() has a relation with Context.Provider.
Context is designed to share data that can be considered “global”.
The Provider component accepts a
value prop to be passed. Every Context come with a Provider.
Context.Provider component available on the context instance is used to provide the context to its child components, no matter how deep they are.
Do I need to declare default value as a null or I can pass my Object right from context.js?
No, you don't need necessarily to declare a default value.
Example of defining the context in one corner of the codebase without defaultValue.
const CountStateContext = React.createContext() // <-- define the context without defaultValue
How can I use a new Hook instead of HOC in mine code?
index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
Root Component: App.js, where will be used data comes form context:
const App = () => {
const firebase = useContext(FirebaseContext)
return(...)
}
export default App;
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;
Following up the issue on Github, I have a component Comp that when exported, is wrapped with injectSheet from reactjss. Please see the setup on codesandbox.
In a unit test, I'd like to assert that that component contains <a>, which it does (see codesandbox), but the test fails regardless:
describe("<Comp /> component", () => {
const wrapper = shallow(<Comp />);
it("should render a <a>", () => {
expect(wrapper.find('a')).to.have.length(1);
});
});
I get Error: [undefined] Please use ThemeProvider to be able to use WithTheme. So my natural (perhaps not the correct?) reaction was to wrap the component with ThemeProvider:
const wrapper = shallow(
<ThemeProvider theme={{}}>
<Comp />
</ThemeProvider>
)
Then I get AssertionError: expected { length: 0 } to have a length of 1 but got 0.
I tried a whole slew of approaches, including calling dive, find or first with an extra shallow call, but I would always end up with Please use ThemeProvider to be able to use WithTheme:
// 1. dive(), as suggested in
// https://github.com/cssinjs/react-jss/issues/30#issuecomment-268373765
expect(wrapper.dive('Comp')).to.have.length(1);
expect(wrapper.dive('Comp').find('a')).to.have.length(1);
expect(wrapper.dive().find('a')).to.have.length(1);
// 2. find() / first(), as suggested in https://github.com/airbnb/enzyme/issues/539
expect(wrapper.find(Comp).shallow().find('a')).to.have.length(1);
expect(wrapper.first().shallow().find('a')).to.have.length(1);
Any ideas here? I am a bit new to unit testing with React, so I would appreciate if someone could enlighten me on this ;)
For anyone still struggling with this, one viable approach was suggested on GitHub. Instead of testing the styled component wrapped with injectSheet HOC, you export your stand-alone component and test it in isolation
// Component.js
import React from 'react'
import injectSheet from 'react-jss'
const styles = {
color: 'burlywood'
}
// named export for unit tests
export const Component = props => <h1>Component</h1>
// default export to be used in other components
export default injectSheet(styles)(Component)
which would work for most use cases, since more often than not, you need to unit test the plain component and its logic, and not any of its associated styling. So in your unit test just do
import { Component } from './Component'
instead of (which you would do in the rest of your codebase)
import Component from './Component'