Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React.cloneElement & Typescript

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?

like image 201
PoZuTPoH Avatar asked Mar 27 '26 19:03

PoZuTPoH


1 Answers

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

like image 114
captain-yossarian Avatar answered Apr 02 '26 22:04

captain-yossarian



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!