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);
Related
Trying to dynamically render components in React and having issues with MUI Tooltips and Transitions. I understand I need to pass refs down to child components, but having trouble figuring out how to in my nested functional components.
Render is called with a functional component (Component) - within Component, Tooltip or Transition are optionally rendered though functional wrapper components, then an element (Elem) is optionally rendered, then child elements are rendered through Component recursively.
Nothing I have tried has worked so far - any help on how to use refs and forward them appropriately in my case would be appreciated, thanks!
root.render(
<Component />
)
export default const Component = (props) => {
//each Elem is referencing another functional component (Label, Button, Panel, etc.)
const components = {LABEL: Label, BUTTON: Button, PANEL: Panel};
const Elem = components[component.type];
const id = props.id;
const component = useStoreValue(id);
const children = (component.children.map((child) => (
<Component id={child.id} key={child.id} />
));
return (
<TooltipWrapper showTooltip={component.attributes}>
<TransitionWrapper showTransition={component.attributes}>
<Elem attributes={component.attributes}>
{children}
</Elem>
</TransitionWrapper>
</TooltipWrapper>
)
}
//Thinking a ref needs to be created/used here and somehow passed to the children
const TooltipWrapper = (props) => {
if (props.showTooltip) {
return (
<Tooltip title={props.showTooltip}>
{props.children}
</Tooltip>
);
}
return props.children;
};
//Thinking a ref needs to be created/used here and somehow passed to the children, but this should also receive a forward ref as it can be a child of tooltip
const TransitionWrapper = (props) => {
const attributes = props.showTransition;
const Type = components[props.showTransition['animationtype']];
const components = {Collapse: Collapse, Fade: Fade, Slide: Slide};
if (props.showTransition) {
return (
<Type {...attributes}>
{props.children}
</Type>
);
}
return props.children;
};
//Example Elem - thinking this should receive a forward ref which will be set on the div element.
const Panel = (props) => {
const attributes = props.attributes;
return <div {...attributes}>{props.children}</div>;
};
I am studying the code for react-accessible-accordion, and I don't understand how it renders its children.
From Accordion.tsx:
export default class Accordion extends React.Component<AccordionProps> {
// ... defaults
renderAccordion = (accordionContext: AccordionContext): JSX.Element => {
const {
preExpanded,
allowMultipleExpanded,
allowZeroExpanded,
onChange,
...rest
} = this.props;
return <div data-accordion-component="Accordion" {...rest} />;
};
render(): JSX.Element {
return (
<Provider
preExpanded={this.props.preExpanded}
allowMultipleExpanded={this.props.allowMultipleExpanded}
allowZeroExpanded={this.props.allowZeroExpanded}
onChange={this.props.onChange}
>
<Consumer>{this.renderAccordion}</Consumer>
</Provider>
);
}
}
This component accepts a few levels of nested children. Specifically, I don't understand how they are being passed down.
I can see that the component spreads the rest of props over a self-closing Accordion div element... How does that mechanism manage to render multiple levels of children?
A React context Consumer expects a function as its child to render the content. That function in this example is referenced as this.renderAccordion:
<Consumer>{this.renderAccordion}</Consumer>
Which renders the children in the {...rest} spread attributes:
const {
preExpanded,
allowMultipleExpanded,
allowZeroExpanded,
onChange,
...rest
} = this.props;
return <div data-accordion-component="Accordion" {...rest} />;
The ...rest includes children from this.props (and you can actually render children as an attribute, like <div children={ <p>Hello!</p> } />) from the destructuring assignment -- in other words const { ...rest } = this.props includes this.props.children.
There is Provider for providing and Consumer for rendering the child components because the props are spread to the Consumer and children is a prop of Accordian.
Here is the consumer being used
For individual components such as the AccordianItem, this uses Provider to define components which are meant to be rendered.
This may help you to understand.
Basically, when JSX is compiled to React code, it creates a component using:
React.createElement("div", null, children);, or
React.createElement("div", { children }, null);
Check how Hello, Foo and Bar component are compiled in the link that I sent you. Your case is gonna be the Bar component
I'd like to add a render prop to all of the nested children in my component. When I use:
const doSomething = () => console.log('\\o/')
const ParentElement = ({ children }) => (
<div>
{ children.map(child => React.cloneElement(child, { doSomething })) }
</div>
)
I receive the error:
Warning: React does not recognize the `doSomething` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `dosomething` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
Essentially, I'm trying to recreate something like this for each child:
const ChildElement = ({ doSomething }) => (<div onWhatever={doSomething}>...</div>)
What's the correct way to attach these render props?
You appear to just need to set the correct prop name ({ doSomething } is shorthand for { doSomething: doSomething }), but you technically children are an opaque structure, so you probably also want to use React.children:
<div>
{React.Children.map(children, child =>
React.cloneElement(child, { onWhatever: doSomething })
)}
</div>
So I have the below code to load a custom component called foo. Loading of the component works fine, but props arent passing to it like I would prefer
Container component
....
const id= foo
React.createElement(LoadComponent(id, attributes))
...
Custom component
export const LoadComponent = (id, attributes) => {
/*This will load up foo.js*/
const Component = require(`./${id}`);
return Component;
};
How do I pass attributes prop to the Component in this case? I keep getting render exceptions.
Here is a simplified demo: https://codesandbox.io/s/zrw7x1zrrp
props are being passed to the component as the second parameter of createElement
const id = "foo";
const LoadComponent = props => {
return props.id;
};
ReactDOM.render(
React.createElement(LoadComponent, { id }, null),
document.getElementById("root")
);
React.createElement(component, props, ...children)
https://reactjs.org/docs/react-without-jsx.html
It's a bit confusing what you are trying to do. If you are using JSX you shouldn't need to invoke React.createElement.
If you insist of doing it without JSX though, React.createElement can take 3 parameters as per React's API. So, in your case your code will be React.createElement(LoadComponent, { id, attributes }, null) where the third parameter is the children.
Now, the id and attributes are accessible from within the props object of your custom component. So you have two options:
Destructure the props object:
export const LoadComponent = ({ id, attributes }) => {
/*This will load up foo.js*/
const Component = require(`./${id}`);
return Component;
};
Use the props object directly:
export const LoadComponent = (props) => {
/*This will load up foo.js*/
const Component = require(`./${props.id}`);
return Component;
};
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.