Search code examples
javascriptreactjsfunctional-programmingcomponents

Setting default value in ReactJS component


I have a component which renders "react-icons" object. However, if no icon is supplied I want to display "NoIcon" string on the UI.

The component A is as such.

component A

import React from 'react';
import { FaHeart} from "react-icons/fa";

const Icon = ({FaIcon = "NoIcon"}) => {
  return (
    <div>
    <FaIcon/>
    </div>
    );
};

export default Icon;

and calling this component from another components B works :

component B

import React from 'react';
import Icon from './components/icons/icon';
import { FaHeartBroken } from "react-icons/fa";

class App extends React.Component {  
    render(){
        return(
              <Icon FaIcon={ FaHeartBroken }/>
        )      
    }   
}
export default App;

this returns a "Broken Heart Icon" i.e 💔.


But, if no icon is supplied in component B it should return a text i.e "NoIcon" in the UI.

Component B

....
....
      <Icon FaIcon/>
....

however this raises an error

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: boolean.

Check the render method of `Icon`.

I also tried to set an or method on component A for value assignment, which did not work either. Why providing no argument within Icon component, calling for a Boolean check?

component A

const Icon = ({FaIcon = FaHeart || "NoIcon"}) => {
    ....

Also tried these variations. These do not raise any error but do not return any value on the UI either.

component B

...
 <Icon/>
 <Icon FaIcon = "Test"/>
 <Icon FaIcon = {"Test"}/>

How do we resolve this?


Solution

  • A prop without a value <Foo bar /> is actually shorthand for <Foo bar={true} />.

    So this:

     <Icon FaIcon/>
    

    Is not correct. And if you read that error again, it probably makes more sense now:

    Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: boolean.

    This means that it was expecting a a string/class/function, but got a boolean value instead. In this case true from the prop shorthand value.

    This means that how you omit that prop, then you get the default.

     <Icon />
    

    Secondly, this won't work:

    const Icon = ({FaIcon = "NoIcon"}) => {
      return (
        <div>
        <FaIcon/>
        </div>
        );
    };
    

    To render something custom as JSX it must be a component. The import from react-icons/fa is a component, but the string "NoIcon" is not.

    I think in this case it may be simpler to have a conditional that renders an icon if the prop is present, or a string if it is not.

    Something like:

    const Icon = ({FaIcon}) => {
      return (
        <div>
          { FaIcon ? <FaIcon/> : "NoIcon" }
        </div>
      );
    };
    

    If you want one prop to handle an icon component or a string you could do this in a few ways. But if you want to be able to pass in icon component, or a string, then you do something like:

    const Icon = ({FaIcon = "NoIcon"}) => {
      return (
        <div>
          { typeof FaIcon === 'string' ? FaIcon : <FaIcon/>  }
        </div>
      );
    };
    

    In this case if the prop is a string (which applies to the default of "NoIcon" as well) then render it as a string. Otherwise assume it's a component and render it.