Toggling Font Awesome 5 icon with React

2019-04-05 09:00发布

问题:

I am trying to toggle a Font Awesome icon by clicking on a to-do list item. Here is the entire component...

import React from 'react';

import './TodoItem.scss';

class TodoItem extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      complete: false
    }
    this.toggleComplete = this.toggleComplete.bind(this);
  }

  toggleComplete() {
    this.setState(prevState => ({
      complete: !prevState.complete
    }));
  }

  render() {
    const incompleteIcon = <span className="far fa-circle todo-item-icon"></span>;
    const completeIcon = <span className="far fa-check-circle todo-item-icon"></span>;

    return (
      <div className="todo-item" onClick={this.toggleComplete}>
        {this.state.complete ? completeIcon : incompleteIcon}
        <span className="todo-item-text">{this.props.item}</span>
      </div>
    );
  }
}

export default TodoItem;

Here's my FA 5 CDN (straight from the website)...

<script defer src="https://use.fontawesome.com/releases/v5.0.1/js/all.js"></script>

I've taken a few screenshots of the React dev tool and inspector...

Here is the React component while incomplete (the default)

And while complete...

And the two icon elements in the inspector, I noticed the one not being used by default is commented out.

As you can see, I'm able to manually toggle the complete state and the component changes in the React tool but the change is not rendered. I changed the default state to make sure both icons are loaded correctly and they are. I made a Codepen to try it in a different environment and this one works, but I am using the FA 4.7.0 CDN. With the Codepen and FA 4.7.0, when I inspect the icon, it is just an HTML element not SVG.

I'd like to get this component working with FA 5 so any help would be appreciated!

回答1:

The font-awesome javascript doesn't rerender on a React rerender trigger. If you are okay with not using the new font-awesome svg/javascript icons, you can use font-awesome as a webfont with css.

In your index.html, delete the fontawesome script, and add the font-awesome css stylesheet:

<link href="https://use.fontawesome.com/releases/v5.0.2/css/all.css" rel="stylesheet">

Your code should work now.


The other possibility is to use the official font-awesome react package (it's a bit more of a hassle, but it uses the new svg icons)

Add necessary packages to project:

yarn add @fortawesome/fontawesome @fortawesome/react-fontawesome
yarn add @fortawesome/fontawesome-free-regular @fortawesome/fontawesome-free-solid

Example code:

import fontawesome from '@fortawesome/fontawesome'
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
import { faCircle as fasCircle } from '@fortawesome/fontawesome-free-solid'
import { faCircle as farCircle } from '@fortawesome/fontawesome-free-regular'

const Circle = ({ filled, onClick }) => {

  return (
    <div onClick={onClick} >
      <FontAwesomeIcon icon={filled ? farCircle : fasCircle}/>
    </div>
  );
};

class App extends React.Component {
  state = { filled: false };

  handleClick = () => {
    this.setState({ filled: !this.state.filled });
  };

  render() {
    return <Circle filled={this.state.filled} onClick={this.handleClick} />;
  }
}

See the github repo for more information: https://github.com/FortAwesome/react-fontawesome

This answer is an edited version of my answer here: How can I get Font Awesome 5 to work with React?.



回答2:

Since FA 5 injects svg elements into the DOM, it probably doesn't play nicely with React's virtual DOM. Hopefully it's something they'll fix, but a simple workaround is to just include both icons instead of toggling them, and hide one or the other by wrapping it in a container that applies display: none. For example:

renderChatButton() {
  const unread = this.state.unread
  const normalIcon = <i className='far fa-comment' />
  const unreadIcon = <i className='fas fa-comment' />
  return (
    <div className='header-button' onClick={ this.toggleChat }>
      <span className={ unread ? 'hidden' : '' }>{ normalIcon }</span>
      <span className={ unread ? '' : 'hidden' }>{ unreadIcon }</span>
    </div>
  )
}


回答3:

Looks like another fix is to call window.FontAwesomeConfig = { autoReplaceSvg: 'nest' } somewhere in your app's boot cycle, after the FA JavaScript has been loaded.

See also https://stackoverflow.com/a/48552226/53790 and https://fontawesome.com/how-to-use/svg-with-js#auto-replace-svg-nest.



回答4:

as per discussion above this is likely a fontawesome bug, not a react bug



回答5:

I faced the same problem today with my ReactJS/Material-UI application. After much research I fixed the issue by using React's key property that forces an element to render when its associated state changes.

So, in this example I would rewrite the render method as below:

render() {
    const icon = this.state.complete ? (<span key={this.state.complete} className="far fa-circle todo-item-icon"/>) : (<span key={this.state.complete} className="far fa-check-circle todo-item-icon"/>);

    return (
      <div className="todo-item" onClick={this.toggleComplete}>
        {icon}
        <span className="todo-item-text">{this.props.item}</span>
      </div>
    );
  }

Here I've used this.state.complete as unique value for the key but you could use anything you like so long as they are unique.

Now, the span element is always removed on re-render and replaced by the other one.

Hope this answer helps future seekers.