简体   繁体   中英

Component cannot be used as a JSX component. Its return type 'Element[]' is not a valid JSX element

I'm currently getting the following error on the Todos component inside TodoApp.tsx : 'Todos' cannot be used as a JSX component. Its return type 'Element[]' is not a valid JSX element. Type 'Element[]' is missing the following properties from type 'Element': type, props, key

And this is my folder structure

TodoApp.tsx

function TodoApp() {
  return (
    <Body>
      <AppDiv>
        <Form />
        <Todos />
        <Footer />
      </AppDiv>
    </Body>
  );
}

Todos.tsx

function Todos(): JSX.Element[] {
  const todos = useSelector((state: RootState) => state.todos);
  const footer = useSelector((state: RootState) => state.footer);

  if (footer.hideAll) {
    if (footer.showCompleted) {
      return todos
        .filter((todo) => !todo.completed)
        .map((todo: any) => (
          <>
            <ul>
              <Todo todo={todo} />
            </ul>
          </>
        ));
    }
    return todos.map((todo) => (
      <>
        <div>
          <Todo todo={todo} />
        </div>
      </>
    ));
  }

  return todos.map(() => (
    <>
      <div></div>
    </>
  ));
}

Todo.tsx

type Todo = {
  todo: TodoProps;
};

const Todo = ({ todo }: Todo) : JSX.Element => {
  const [isEditing, edit] = useState<boolean>(false);
  const dispatch = useDispatch();

  if (!isEditing) {
    return (
      <TodoDiv>
        <Li
          key={todo.id}
          completed={todo.completed}
          onClick={() => dispatch(completeTodo(todo.id))}
          // style={{
          //   textDecoration: todo.completed ? "line-through" : "none"
          // }}
        >
          {todo.text}
        </Li>
        <TodoBttns>
          <Button edit onClick={() => edit(!isEditing)}>
            <img src={editBttn} alt="Edit Button" />
          </Button>
          <Button delete onClick={() => dispatch(deleteTodo(todo.id))}>
            <img src={deleteBttn} alt="Delete Button" />
          </Button>
        </TodoBttns>
      </TodoDiv>
    );
  } else {
    return (
      <FormEdit>
        <InputForm key={todo.id} {...{ todo, edit }} />
      </FormEdit>
    );
  }
};

and the TodoProps interface is the following:

interface TodoProps {
  text: string;
  completed: boolean;
  id: string;
}

already tried the fix of wraping the map items with fragments, but I still can't make it work. The only thing that as of now is fixing the issue is declaring at the top of Todos.tsx as this function Todos(): any

As a side note: I'm using Styled Components, but I don't think the issue is related to the library.

A component needs to return a single root element. You can use fragments to package an array of elements as a single element, by using the fragment as that single root element.

So this does nothing:

function Todos(): JSX.Element {
  return todos.map(todo => (
    <>
      <li>{todo.task}</li>
    </>
  )
}

Because it's now returning an array of [<><li/></>, <><li/></>, ...] . That fragment needs to be the single root element.

You need to use the fragment like this:

function Todos(): JSX.Element {
  return <>{
    todos.map(todo => <li>{todo.task}</li>)
  }</>
}

You nest all returned JSX in one single fragment.

Using that pattern you may end up with somehting like this:

function Todos(): JSX.Element {
  const todos = useSelector((state: RootState) => state.todos);
  const footer = useSelector((state: RootState) => state.footer);

  if (footer.hideAll) {
    if (footer.showCompleted) {
      return <>{
        todos
          .filter((todo) => !todo.completed)
          .map((todo: any) => (
            <ul>
              <Todo todo={todo} />
            </ul>
          ))
      }</>
    }
    return <>{
      todos.map((todo) => (
        <div>
          <Todo todo={todo} />
        </div>
      ))
    }</>
  }

  return <>{
    todos.map(() => (
      <div></div>
    ))
  }</>
}

// Works without error
<Todos />

Note how each return statement returns just one JSX.Element : the fragment.

Playground

You need to return a JSX Element, not an array. Wrapping the whole component is a solution, but you need to do it outside of the map/filter function.

Todos.tsx

function Todos(): JSX.Element {
  const todos = useSelector((state: RootState) => state.todos);
  const footer = useSelector((state: RootState) => state.footer);

  if (footer.hideAll) {
    if (footer.showCompleted) {
      return (
        <>
          {todos.filter((todo) => !todo.completed).map((todo: any) => (
            <ul>
              <Todo todo={todo} />
            </ul>
           ));
          }
        </>
    }
    return (
      <>
        {todos.map((todo) => (
          <div>
            <Todo todo={todo} />
          </div>
        ));
        }
      </>
  }

  return (
    <>{todos.map(() => <div />)}</>
  );
}

In case anyone is facing issue with React + Typescript stack, try adding below setting in tsconfig.json. It worked for me.

"allowSyntheticDefaultImports": true

In my case, it was a forgotten import. Basically copy-pasted some of my code and forgot to import one of the components and this was the error message I get.

There's a possibility that you have installed (and probably failed) a new package, which could also cause this error.

If that's the case (that you have recently installed and failed or cancelled a package), you can delete node_modules and run npm i or yarn install then try again.

You need to install types for react npm install @types/react or yarn add @types/react

Sometimes this error can occur when you use the switch case syntax to render your component.

For example:

switch (loading) {
    case "pending":
      return <span>Pending</span>;
    case "succeeded":
      return <span>Succeeded</span>;
    case "failed":
      return <span>Failed</span>;

Sometimes your loading status can mistakenly have a value that you didn't specify in the case , so your component will return undefined . Therefore, you should add a default value:

default: return <span>¯\_(ツ)_/¯</span>;

For a scenario when someone has upgraded to later React , eg, 16 -> 17 it could start happening.

Now, while looking for @types/react in your project, you could notice many npm packages have @types/react: "*", . The solution to get rid off the problem, in that scenario, would be to add into your package.json :

"resolutions": {
  "@types/react": "^17.0.38"
}

I'm facing the same issue about this error. I add the below code to my package.json file.

"resolutions": {
  "@types/react": "17.0.2",
  "@types/react-dom": "17.0.2",
  "graphql": "^16.5.0"
},

Then, Run yarn install on a terminal.

It works for me.

This answer is related to same problem on react-native. Adding typescript to my newly created react-native app and moving App.js to App.tsx, I met the same error for component named Section. The solution for me was a component type React.Fc<{...}> . This is the problematic Section component after the fix when all errors disappeared and my app started as expected.

 import React, { ReactNode } from 'react';
 import {
   SafeAreaView,
   ScrollView,
   StatusBar,
   StyleSheet,
   Text,
   useColorScheme,
   View,
 } from 'react-native';
 
 import {
   Colors,
   DebugInstructions,
   Header,
   LearnMoreLinks,
   ReloadInstructions,
 } from 'react-native/Libraries/NewAppScreen';
 
 const Section: React.FC<{  
  children: ReactNode;   
  title: string; 
}> = ({children, title}) => {
   const isDarkMode = useColorScheme() === 'dark';
   return (
     <View style={styles.sectionContainer}>
       <Text
         style={[
           styles.sectionTitle,
           {
             color: isDarkMode ? Colors.white : Colors.black,
           },
         ]}>
         {title}
       </Text>
       <Text
         style={[
           styles.sectionDescription,
           {
             color: isDarkMode ? Colors.light : Colors.dark,
           },
         ]}>
         {children}
       </Text>
     </View>
   );
 };

Another common cause of the "Component cannot be used as a JSX component" error is when we return anything other than a JSX element or null from a component, or forget to return a value.

The App component returns undefined because we have placed our return statement on one line and the JSX code on the next without using parentheses .

We aren't allowed to return undefined from a component, so the error occurs.

Add the following code

"paths": {
   "react": [ "./node_modules/@types/react" ]
 }

to tsconfig.json file, in compilerOptions

Here is how my code looks like now:

{
  "compilerOptions": {
    "jsx":"react",
    "strict": true,
    "paths": {
      "react": [ "./node_modules/@types/react" ]
    }
  }
}

It is telling the typescript compiler that when it encounters a reference to the "react" module, it should look for the actual implementation in the "./node_modules/@types/react" directory.

In my case I forgot to put back ticks after styled(PreviousStyledElement); After I put them the code started to work.

 export const MyNewStyledElement = styled(PreviousStyledElement)``;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM