3

I'm trying to create a component where certain HTMLElements or ReactComponents are passed to it like this:

<ContentList>
   <span>Hi</span> // Passed child No. 1
   <span>Hi2</span> // Passed child No. 2
   <CustomComponent prop1={}></CustomComponent> // Passed child No. 3
</ContentList>

Then, it will render the passed children into this structure:

render() {
    let content: React.ReactNode = this.props.children;
    //----------------------
    //Desired Process goes here
    //----------------------
    return (
      <>
        <section className={styles.list}></section>
        <section className={styles.contentPanel}></section>
      </>
    );
}

Where the first section is supposed to be a content list and the second section is going to be the rendered ReactComponents and HTMLElements. My question is How am I supposed to work with props.children (aka content) as an array? I tried to search for it online but there is no solid guide out there. Basically I want to do something like this:

let content: React.ReactNode = this.props.children;

// Creating relevant anchor for each content
let list = content.map((child, i) => {
  return (<a className="content-link" href={"#"+child.id}>{child.title}</a>)
});

// Rendering each content after wrapping them inside proper div
let contents = content.map((child, i) => {
  return (<div className="content">{child}</div>)
});

return (
  <>
    <section className={styles.list}>{list}</section>
    <section className={styles.contentPanel}>{contents}</section>
  </>
);

3 Answers3

0
class App(){
  // Creating relevant anchor for each content
  function list(){
    return this.props.children.map((child, i) => {
      return (<a className="content-link" href={"#"+child.id}>{child.title} </a>)
    });
  }  

  // Rendering each content after wrapping them inside proper div
  function contents(){
    return this.props.children.map(...)
  }

  render() {
    return (
      <>
        <section className={styles.list}>{list()}</section>
        <section className={styles.contentPanel}>{contents()}</section>
      </>
    )
  }
}
kyun
  • 9,710
  • 9
  • 31
  • 66
  • Yeah I got it working after some more attempts. Thanks for the effort! For some reason, my issues get fixed as soon as I post them here :D – Andrew Sharifikia Jan 04 '22 at 02:10
  • 1
    Actually, after implementing your answer, I should say that this error pops up in Typescript compiler: `Property 'map' does not exist on type 'boolean | ReactChild | ReactFragment | ReactPortal'.` – Andrew Sharifikia Jan 04 '22 at 02:13
0

The Typescript compiler is super strict on variable type in declaration and assignment but I managed to get it working by the following process:

render() {
  let content: React.ReactNode = this.props.children;
  let list: Array<JSX.Element> = [], // #1
       contents: Array<JSX.Element> = []; // #1
  if (this.props.children !== undefined && this.props.children !== null) {
    if (Array.isArray(content)) { // #2
      let arr: Array<any> = content; // #3
      list = arr.map((child, i) => {
        return (
          <a
           className={styles.contentAnchor}
           href={"#content_" + child.props.id}
          >
            {child.props.title}
          </a>
        );
      });
      contents = arr.map((child, i) => {
        return (
          <div
            id={"content_" + child.props.id}
            className={styles.contentWrapper}
          >
            {child}
          </div>
        );
      });
    }
  }
  return (
    <>
      <section className={styles.list}>{list}</section>
      <section className={styles.contentPanel}>{contents}</section>
    </>
  );
}

The actual key lines that were missing from the start are the ones I marked with #

0

Array into single (ReactNode[] -> ReactNode)

const child = <>{props.children}</>

Single into array (ReactNode -> ReactNode[])

const children = [props.children]

Single or Array into Array (ReactNode | ReactNode[] -> ReactNode[])

const children = props.children instanceof Array ? props.children : [props.children]
kpostekk
  • 522
  • 5
  • 6