1

I', trying to create a react component which emulates a native html element.

Problem 1:

Allowing a valid html attribute

Example:

type TAtt = {
  [key: string]: React.HTMLAttributes<HTMLElement>;
};

const NavAnyValidAttribute: React.FC<TAtt> = props => {
  const { children, ...therest } = props;
  return <nav {...therest}>{children}</nav>;
};

<NavAnyValidAttribute className="x">some children</NavAnyValidAttribute>;

Error

Type 'string' has no properties in common with type 'HTMLAttributes'.(2559) input.tsx(135, 3): The expected type comes from this index signature.

Problem 2:

type TProps = TAtt & {
  TagName: string; //keyof JSX.IntrinsicElements - also doesn't work!;
}

const Tag: React.FC<TProps> = ({ TagName, children, ...props }) =>
  React.createElement(TagName, props, children);

const Nav = <Tag {...{ TagName: 'nav' }} />;

Error: Type '{ TagName: string; }' is not assignable to type 'TAtt'. Property 'TagName' is incompatible with index signature. Type 'string' has no properties in common with type 'HTMLAttributes'.ts(2322)

Any help appreciated. thanks

2
  • 1
    "I'm trying to create a React component which emulates a native HTML element." - why? Commented Aug 21, 2020 at 10:38
  • so i can pass it to this method: stackoverflow.com/questions/63482262/… Commented Aug 21, 2020 at 12:01

1 Answer 1

1

Ok I have created a solution.

I'm sure there is a better way of doing it but in order to keep this strict, and allow passing this component to another unknown component handler I had to ensure I returned the Component<any>.

I also had to ensure I had a strict TagName: React.ElementType. Or "keyof JSX.IntrinsicElements". Not sure which is more appropriate. But the same has to be used for both methods as below.

I also had to return the method GetTagAsComp as a member of the same component TagAsComp.

If anyone has a tidier approach feel free to add.

import React from 'react';

type TElem = React.ElementType; // OR keyof JSX.IntrinsicElements;

// Might have to extend this for specific element attributes - see
// - https://www.saltycrane.com/cheat-sheets/typescript/react/latest/
type TAtt = React.HTMLAttributes<HTMLElement>;

type TCreateElement = {
  TagName: TElem;
  props: TAtt;
};

const CreateElement: React.FC<TCreateElement> = ({ TagName, props, children }) =>
  React.createElement(TagName, props, children);

// technically this is a hoc, so would use prefix - with
// but it doesnt name well
// This existing name: TagAsComp is better describing what it is in shorthand - Tag as a component.
const TagAsComp = (TagName: TElem): React.ComponentType<any> => {
  const GetTagAsComp: React.FC<TAtt> = props => {
    const { children } = props;
    return <CreateElement {...{ TagName, props, children }} />;
  };
  return GetTagAsComp;
};

export default TagAsComp;

// Usecase:
// const Tag = TagAsComp('div')
// <Tag onClick={doThing} className="hello" data-x="extra"> Some children </Tag>

For my usecase I wanted to pass it to styled components like this: See my other post:

  const Nav = Style(TagAsComp('div'), theme);

Going forward for styled components on an element:

The above solution was not needed, though it answer my original question and maybe beneficial for other use cases.

I would prefer this now as its more elegant:

const StyleNav = (props: TTheme): StyledComponent<'nav', {}> => styled.nav`
  background: ${props.grey1};
  display: block;
`;
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.