Joining React props with custom props - reactjs

When defining props in React, using Typescript, it seems that the default React props get overwritten by whatever the interface is. Is there a clean way to merge the two without having to specify every prop React already knows about?
Example:
interface IProps { // extends React.???
title: string;
// Generally seen adding children?: any
// But this can get out of hand with onClick, onChange, etc
}
function MyComponent(props: IProps) {
return props.children; // Typescript error: children doesn't exist on props
}

What you're referring to as "React default props" aka "every prop React already knows about" are more properly called "props accepted by any React DOM element wrapper component", i.e. onClick, className, etc.
Default props typically refers to the static defaultProps property on a React.Component, with which you provide default values for any props that were not passed to your component.
onClick, className, etc. are not reserved prop names and you can use them however you want in your own components, for instance you could have your component expect className to be a function (regardless of whether it's a good idea). The only reserved prop names that work on React elements of any kind (at the time of writing) are key and ref, and they're not really true props because they're not available to your component's render method.
Passing onClick to your own component does not automatically register a click handler. It will only do so if you pass the onClick you received to a <div>, <button>, or other React DOM Element wrapper that you render somewhere down the line. If you don't do anything with a prop you were passed, it has no effect (besides possibly causing a pure render component to update when it otherwise wouldn't).
For example, the following component will not respond to clicks:
const ClickFail = props => <button />
render(<ClickFail onClick={...} />, document.getElementById('root'))
But the following will:
const ClickSuccess = props => <button onClick={props.onClick} />
render(<ClickSuccess onClick={...} />, document.getElementById('root'))
And you could pass onClick to only one subelement if you really wanted:
const ClickButtonOnly = props => (
<form>
<input placeholder="ignores clicks" />
<button onClick={props.onClick}>Handles Clicks</button>
</form>
)
Or you could pass in multiple click handlers with different names:
const SimpleForm = props => (
<form>
<button onClick={props.onCancelClick}>Cancel</button>
<button onClick={props.onOKClick}>OK</button>
</form>
)
Also keep in mind that some DOM element wrappers accept props that others do not, for instance readOnly applies only to <input> and <textarea>.
You can even require children to be whatever type you want. For instance, you can pass a function as the children of a component and use it (again, not the best use of React, but just to illustrate what's possible):
type Props = {
value: number,
children: (value: number) => number,
}
const ApplyFunction = (props: Props) => (
<div>{React.Children.only(props.children)(props.value)}</div>
)
render(
<ApplyFunction value={3}>
{value => value * value}
</ApplyFunction>,
document.getElementById('root')
)
// renders <div>9</div>
So you see, IProps does not necessarily have to extend anything.
However, it is common to pass along rest props to a React DOM Element wrapper (e.g. <div {...props}>...</div> and as you were asking, it would be nice to be able to check the type of all of those input properties to your component.
I think you could do the following with Flow to check the types correctly, but unfortunately I don't think there's any Typescript equivalent (someone correct me if I'm wrong):
type Props = React.ElementProps<typeof 'div'> & {
title: string,
}
const MyComponent = (props: Props) => (
<div {...props}>
{props.title}
</div>
)

You should define that your stateless functional component will return React.SFC<YourProps>.
Try this
import * as React from "react";
const MyComponent: React.SFC<IProps> = (props) => {
return props.children;
}
If you want to use class-based component, you can extend your class with React.Component<YourProps(optional), YourState(optional)> instead
For example
import * as React from "react"
class MyComponent extends React.Component<IProps> {
public render(): JSX.Element {
return (
<div>...</div>
);
}
}

type TitleProps = { // your custom props
level?: level;
color?: string;
};
const Title = (props: TitleProps & React.Component['props']) => { // join default props and custom props
const { level = 'h1', color, children } = props; // joined props containing default props
return <Text style={[styles[level], color && { color }]}>{children}</Text>;
}
I was able to solve the problem by this way.

Related

React render specific children

I have looked through other peoples questions relating to this but cant find a suitable answer. I would like to pass children to a component and then pull out the specific children where I want them, most examples I have seen just have the children render in the same place.
My component looks something like this -
<ParentComponent>
<ChildOne/>
<ChildTwo/>
<ParentComponent/>
When I log the props.children inside the parent component I get an array which contains both children as objects. is there a simple way to pull out the specific child where I need it such as {props.children.ChildOne} at the moment I am using props.children[0] which isn't ideal as we will be passing the children dynamically
in the future and the array length may change.
As always any help is greatly appreciated!
Depending on your exact situation and needs, it might make more sense to pass child components as props than using the special children prop. Then you can render them whichever way you like.
<ParentComponent childOne={ChildOne} childTwo={ChildTwo} />
...
const ParentComponent = ({ childOne, childTwo }) => {
return (
<div>
{childOne}
<div>
{childTwo}
</div>
</div>
);
};
But knowing your exact scenario would help a lot with conceptualising the best way to implement this. Perhaps you can refactor your code to avoid passing an array of children like this.
Actually, the ReactChildren API I was mentioning is useless here.
You can do something like this instead:
import React from 'react';
import { ChildOne } from './YourFile';
export function ParentComponent({children}) {
return children.find(child => child.type === ChildOne)
}
You should define the displayName property for the child components and then use the displayName in the parent to find the specific children from children list and place them where you want it to be.
// define displayName for each component, it can be any string
// You can set the displayName for any react component before exporting as shown
// below
const ChildOne = (props) => { return (<div> Child One </div>)}
ChildOne.displayName = "ChildOne";
export default ChildOne;
const ChildTwo = (props) => { return (<div> Child Two </div>)}
ChildTwo.displayName = "ChildTwo";
export default ChildTwo;
Now in parent component you can filter out the specific child by using their displayName.
const ParentComponent = (props) => {
const getChildByDisplayName = (displayName) => {
const child = React.Children.map(props.children, (child => {
// you can access displayName property by child.type.displayName
if (child.type.displayName === displayName) return child;
return null;
}))
return child;
}
return (
<div>
{/* You can change the order here as per your wish*/}
{getChildByDisplayName("ChildOne")}
{getChildByDisplayName("ChildTwo")}
</div>
)
}
That's it, Now even if you put ChildTwo before ChildOne like below example, parent component will still render the ChildOne first and then ChildTwo because we have defined order in parent.
<ParentComponent>
<ChildTwo/>
<ChildOne/>
<ParentComponent/>
Using the key seems simpler:
whatever is using the parent component:
<ParentComponent>
<ChildOne key="title"/>
<ChildTwo key="picture"/>
<ParentComponent/>
parent component:
export default function ParentComponent(props: any) {
const title = props.children.find((o: any) => o.key === 'title')
const picture = props.children.find((o: any) => o.key === 'picture')
return <div>
<jumbobox>
{title}
</jumbobox>
<fancyframe>
{picture}
</fancyframe>
</div>
}
One way is to control the Child Components being passed to ParentComponent through state/props/redux store management in the component containing ParentComponent and its children.
But to have the solution as per your use case, we can avoid using children prop and use our defined prop on ParentComponent.
<ParentComponent
components={[
{key: 1, component: <div>Hello</div>}
]}
/>
So, we can now filter from the key.
Checkout this demo

Create custom component wrapper

I am migrating react code to typescript and have one issue with type custom component wrapper.
I am sending as prop, from which i will create component and use it as JSX. By default it is set to div but also it can be React component, specifically custom Link component, which return RouterLink from react-router-dom or a tag.
Current code
function DropdownMenuItemWrapper({
as ='div',
to,
target,
onClick,
active,
children
}) {
const Wrapper = as ;
return (
<Wrapper
to={to}
target={target}
onClick={onClick}
>
{children}
</Wrapper>
);
}
Issue is, i do not know, how to type as property and how to use Wrapper. If i try something like:
const Wrapper = React.createElement(as); or just const Wrapper = as;
i got error: JSX element type 'Wrapper' does not have any construct or call signatures.
Close :)
type DropdownMenuItemWrapperProps = {
as: Parameters<typeof React.createElement>[0],
// other props type
}
const DropdownMenuItemWrapper = ({
as = "div",
to,
target,
onClick,
active,
children
}: DropdownMenuItemWrapperProps) => React.createElement(as, { to, target, onClick }, children);

set a value to a prop of a component that is passed in as another prop of its parent component

(I am developing a react-native project)
I have a custom component which takes another custom component as its prop:
<MyCustomComponent
data={mydata}
optionalComponent={<MySubComponent />}
/>
MySubComponent looks like this:
const MySubComponent = ({car}) => {
...
}
export default MySubComponent;
As you can see above, this component has a car prop.
MyCustomComponent looks like this:
const MyCustomComponent = ({data, optionalComponent}) => {
const myCar = getCar();
// How can I set myCar to optionalComponent passed in here??
}
export default MyCustomComponent;
My question is in MyCustomComponent, how can I set myCar to the passed-in optionalComponent 's car prop ? (knowing it always has a car prop).
(Please don't suggesting moving myCar to uppper level then pass in as normal case. I wonder the possibilty my question is asking.)
Pass the component's reference to the custom component like so:
<MyCustomComponent data={mydata} optionalComponent={MySubComponent} />
And re-assign the reference to a variable with an upper case:
const MyCustomComponent = ({ data, optionalComponent }) => {
const MyCar= optionalComponent;
// ..
return <MyCar /* Add the required props here */ />
}
Alternatively, you can take advantage of the children prop but you'll have to wrap MySubComponent inside MyCustomComponent like so:
<MyCustomComponent data={mydata}>
<MySubComponent /* Add the required props here */ />
</MyCustomComponent>
And in MyCustomComponent:
const MyCustomComponent = ({ data, children }) => {
// ..
return (
<div>
{/** other stuff */}
{children}
{/** other stuff */}
</div>
);
}
If I understood what you want correctly then I think you're doing that backwards. The passed in component doesn't actually have any props, it only takes props when it's invoked but it hasn't been invoked yet. If you want both your components to use the same car prop then why not pass it to MyCustomComponent and have that component pass it down to MySubComponent? That's the normal react flow. Alternatively, you can use the context API and stores and hook up both components to the same store but It's not clear why you'd want that.
Is MyCustomComponent the component that's actually rendering the instance of MySubComponent whose car prop you want to get?

Rendering a component as the parent of the actual component

I have 2 components, I would like the first component to be a render prop in the second component, which will encapsulate the content of the second component if it is defined. I don't quite understand how to write this, and the documentation I find on render props tends to be difficult to understand. Has anyone written a similar implementation?
The general idea i'm after is that you pass in the props for component1 in the component1 prop for component2, and it renders <Component1> with it's props inside component2.
Rough code example of what i'm trying to do (It isn't meant to work)
interface Component1 {
id?: string;
children: React.ReactNode;
}
const Component1 = (props: Component1) => {
const { children } = props;
return (<div className="component1">{children}</div>)
}
interface Component2 {
component1?: (propsForComponent1) => <Component1 {...propsForComponent1}>
}
const Component2 = (props: Component2) => {
const {component1} = props;
if (component1) {
return {component1({id: 'exampleId', children: <div className="component2">Stuff for component 2</div>)}}
};
return (<div className="component2">Stuff for component 2</div>);
}
Edit: Have changed the example because the intention is confusing people.
Edit: Will just pass the first component as a prop into the second component for now. I think the general answer is to not try to use a component as two components, and just stick to children.
What is actually happening in your code sample, is that even if you are passing down a whole component (Component1) as a prop, you are not rendering it, but using an imported (or accessible inside the scope) Component1.
Anyways - why would you want to pass down a whole component as a prop, even if you can basically just import it?
What I would suggest - use a prop, but not a component, but a boolean flag that determines if the component should be wrapped or not.
interface Component2 {
shouldBeWrapped?: boolean;
}
const Component2 = ({ shouldBeWrapped }: Component2) => {
if (shouldBeWrapped) {
return (
<Component1 with props given in on component1 property>
<div className="component2">Stuff for component 2</div>
</Component1>
);
}
return (<div className="component2">Stuff for component 2</div>);
}

Material UI's withTheme() hides component from ref prop

For the rare times when you need a reference to another JSX element in React, you can use the ref prop, like this:
class Widget extends React.PureComponent {
example() {
// do something
}
render() {
...
<Widget ref={r => this.mywidget = r}/>
<OtherWidget onClick={e => this.mywidget.example()}/>
Here, the Widget instance is stored in this.mywidget for later use, and the example() function can be called on it.
In Material UI, you can wrap components around a withTheme() call to make the theme accessible in their props:
export default withTheme()(Widget);
However if this is done, the ref receives an instance of WithTheme rather than Widget. This means the example() function is no longer accessible.
Is there some way to use ref with a component wrapped by withTheme() so that the underlying object can still be accessed, in the same manner as if withTheme() had not been used?
Here is an example demonstrating the issue. Lines 27 and 28 can be commented/uncommented to see that things only fail when the withTheme() call is added.
In order to get the ref of the component which is wrapped with withStyles, you can create a wrapper around Widget, and use that with withStyles like
const WithRefWidget = ({ innerRef, ...rest }) => {
console.log(innerRef);
return <Widget ref={innerRef} {...rest} />;
};
const MyWidget = withTheme()(WithRefWidget);
class Demo extends React.PureComponent {
constructor(props) {
super(props);
this.mywidget = null;
}
render() {
return (
<React.Fragment>
<MyWidget
innerRef={r => {
console.log(r);
this.mywidget = r;
}}
/>
<Button
onClick={e => {
e.preventDefault();
console.log(this.mywidget);
}}
variant="raised"
>
Click
</Button>
</React.Fragment>
);
}
}
Have a look at this answer to see an other alternative approach
losing functions when using recompose component as ref
This is a shorter alternative based on Shubham Khatri's answer. That answer works when you can't alter the inner component, this example is a bit shorter when you can modify the inner component.
Essentially ref doesn't get passed through withTheme() so you have to use a prop with a different name, and implement ref functionality on it yourself:
class Widget extends React.PureComponent {
constructor(props) {
props.ref2(this); // duplicate 'ref' functionality for the 'ref2' prop
...
const MyWidget = withTheme()(Widget);
...
<MyWidget
ref2={r => {
console.log(r);
this.mywidget = r;
}}
/>

Resources