Home > Enterprise >  React.cloneElement & Typescript
React.cloneElement & Typescript

Time:09-22

I'm trying to make a dashboard with React and Typescript. I use Card component to push children components into it by React.cloneElement and add Card's setStates to add classes and title to Card from children. And now there is an error with added setState functions. In example I removed unnecessary code and types to make code more readable. Example:

import React, {useState} from 'react'

function App() {
 return (
  <div>
   <Block
    id="users-component"
    title="Users Table"
   >
    <MyTable class="users" />
   </Block>

   <Block
    id="status-component"
    title="Status Component"
   >
    <Status class="status" />      {/* Property 'renewTitle' is missing in type '{ class: string; }' but required in type 'IStatus'. */}
   </Block>

   <Block
    id="bdays-component"
    title="Bdays Component"
   >
    <Bdays class="bdays" />      {/* Property 'addClasses' is missing in type '{ class: string; }' but required in type 'IBDays'. */}
   </Block>
  </div>
  
 )
}

interface IBlock {
    children: JSX.Element,
    id: string,
    title: string
}

function Block(props: IBlock) {
 const [classes, addClasses] = useState<string[]>([""])
 const [title, renewTitle] = useState<string>(props.title)
 return (
  <div id={props.id} className={classes.join(" ")}>
   <h2>{props.title}</h2>
   {React.cloneElement(props.children, {addClasses, renewTitle})}
  </div>
 )
}

function MyTable(props) {
 return (
  <table> </table>
)
}

interface IStatus {
 class: string,
 renewTitle: (title: string) => void     // ?
}

function Status(props: IStatus) {
 const handleClick = (title) => {
  props.renewTitle(title)     // changes title in Block
 }
 return (
  <div>
   <button onClick={() => handleClick("newTitle")}>New Title</button>
  </div>
)
}

interface IBDays {
 class: string,
 addClasses: (classes: string[]) => void     // ?
}



function Bdays(props: IBDays) {
 const handleClick = (newClass: string) : void => {
  props.addClasses([newClass])     // add new class to array "classes" in it's block
 }
 return (
  <div className={props.class}>
   <button onClick={() => handleClick("newClass")}>New Class</button>
  </div>
)
}

How can I solve that problems?

CodePudding user response:

UPDATE

You need to make a small refactor. TypeScript is unable to figure that you are cloning component children. I have changed a bit the logic. SInce it is allowed for children property to be a function I just passed required props to this function.

Now TS is able to figure out that props argument inside {(props) => <Status class="status" {...props} />} is actually valid one

Please see comments

import React, { useState } from 'react'

function App() {
  return (
    <div>
      <Block
        id="users-component"
        title="Users Table"
      >{() => <MyTable class="users" />}
      </Block>

      <Block
        id="status-component"
        title="Status Component"
      >{(props) => <Status class="status" {...props} />}
      </Block>

      <Block
        id="bdays-component"
        title="Bdays Component"
      >
        {(props) => <Bdays class="bdays" {...props} />}

      </Block>
    </div>

  )
}

interface IBlock {
  children: (
    props: {
      addClasses: React.Dispatch<React.SetStateAction<string[]>>,
      renewTitle: React.Dispatch<React.SetStateAction<string>>
    }
  ) => React.ReactNode
  id: string,
  title: string
}

const Block: React.FC<IBlock> = (props: IBlock) => {
  const [classes, addClasses] = useState<string[]>([""])
  const [title, renewTitle] = useState<string>(props.title)
  return (
    <div id={props.id} className={classes.join(" ")}>
      <h2>{props.title}</h2>
      {props.children({ addClasses, renewTitle })}
    </div>
  )
}

function MyTable(props: { class: string }) {
  return (
    <table> </table>
  )
}

interface IStatus {
  class: string,
  renewTitle: (title: string) => void
}

function Status(props: IStatus) {
  const handleClick = (title: string) => {
    props.renewTitle(title)
  }
  return (
    <div>
      <button onClick={() => handleClick("newTitle")}>New Title</button>
    </div>
  )
}

interface IBDays {
  class: string,
  addClasses: (classes: string[]) => void
}



function Bdays(props: IBDays) {
  const handleClick = (newClass: string): void => {
    props.addClasses([newClass])
  }
  return (
    <div className={props.class}>
      <button onClick={() => handleClick("newClass")}>New Class</button>
    </div>
  )
}

Playground

  • Related