1

As per feedback from this question. I'm trying to convert this WrappedApp class component to a function component.

This is the existing (working) class component:

class WrappedApp extends Component {
      constructor(props) {
        super(props);
        this.state = {
          filterText: "",
          favourites: [],
        };
      }

      // update filterText in state when user types
      filterUpdate(value) {
        this.setState({
          filterText: value,
        });
      }

      // add clicked name ID to the favourites array
      addFavourite(id) {
        const newSet = this.state.favourites.concat([id]);
        this.setState({
          favourites: newSet,
        });
      }

      // remove ID from the favourites array
      deleteFavourite(id) {
        const { favourites } = this.state;
        const newList = [...favourites.slice(0, id), ...favourites.slice(id + 1)];
        this.setState({
          favourites: newList,
        });
      }

      render() {
        const hasSearch = this.state.filterText.length > 0;
        return (
          <div>
            <header>
              <Greeting />
              <Search
                filterVal={this.state.filterText}
                filterUpdate={this.filterUpdate.bind(this)}
              />
            </header>
            <main>
              <ShortList
                data={this.props.data}
                favourites={this.state.favourites}
                deleteFavourite={this.deleteFavourite.bind(this)}
              />

              <TagsList
                data={this.props.data}
                filter={this.state.filterText}
                favourites={this.state.favourites}
                addFavourite={this.addFavourite.bind(this)}
              />
              {/* 
                Show only if user has typed in search.
                To reset the input field, we pass an 
                empty value to the filterUpdate method
              */}
              {hasSearch && (
                <button onClick={this.filterUpdate.bind(this, "")}>
                  Clear Search
                </button>
              )}
            </main>
          </div>
        );
      }
    }

    export default WrappedApp;

This code is used/referenced in the WrappedApp component and the new functional component:

const Tag = ({ id, info, handleFavourite }) => (
  <li className={info.count} onClick={() => handleFavourite(id)}>
    {info.label} ({info.tag_related_counts_aggregate.aggregate.count})
  </li>
);
const ShortList = ({ favourites, data, deleteFavourite }) => {
  const hasFavourites = favourites.length > 0;
  const favList = favourites.map((fav, i) => {
    return (
      <Tag
        id={i}
        key={i}
        info={data.find((tag) => tag.id === fav)}
        handleFavourite={(id) => deleteFavourite(id)}
      />
    );
  });
  return (
    <div className="favourites">
      <h4>
        {hasFavourites
          ? "Shortlist. Click to remove.."
          : "Click on a tag to shortlist it.."}
      </h4>
      <ul>{favList}</ul>
      {hasFavourites && <hr />}
    </div>
  );
};


const TagsList = ({ data, filter, favourites, addFavourite }) => {
  const input = filter;

  // Gather list of tags
  const tags = data
    // filtering out the tags that...
    .filter((tag, i) => {
      return (
        // ...are already favourited
        favourites.indexOf(tag.id) === -1 &&
        // ...are not matching the current search value
        !tag.label.indexOf(input)
      );
    })
    // ...output a <Name /> component for each name
    .map((tag, i) => {
      // only display tags that match current input string
      return (
        <Tag
          id={tag.id}
          key={i}
          info={tag}
          handleFavourite={(id) => addFavourite(id)}
        />
      );
    });

  /* ##### the component's output ##### */
  return <ul>{tags}</ul>;
};


// need a component class here
// since we are using `refs`
class Search extends Component {
  render() {
    const { filterVal, filterUpdate } = this.props;
    return (
      <form>
        <input
          type="text"
          ref="filterInput"
          placeholder="Type to filter.."
          // binding the input value to state
          value={filterVal}
          onChange={() => {
            filterUpdate(this.refs.filterInput.value);
          }}
        />
      </form>
    );
  }
}

This is my initial unsuccessful attempt to convert the WrappedApp class component to a function component:

function WrappedApp(props) {
  const [filterText, setfilterText] = useState("");
  const [favourites, setFavourites] = useState([]);

  // update filterText in state when user types
  const filterUpdate = (value) => {
    setfilterText(value);
  };

  // add clicked name ID to the favourites array
  const addFavourite = (id) => {
    const newSet = favourites.concat([id]);
    setFavourites(favourites);
  };

  // remove ID from the favourites array
  const deleteFavourite = (id) => {
    const newList = [...favourites.slice(0, id), ...favourites.slice(id + 1)];
    setFavourites(newList);
  };

  const hasSearch = filterText.length > 0;
  return (
    <div>
      <header>
        <Greeting />
        <Search filterVal filterUpdate />
      </header>
      <main>
        <ShortList data={props.data} favourites deleteFavourite />

        <TagsList
          data={props.data}
          filter={filterText}
          favourites
          addFavourite
        />
        {/* 
            Show only if user has typed in search.
            To reset the input field, we pass an 
            empty value to the filterUpdate method
          */}
        {hasSearch && <button onClick={filterUpdate}>Clear Search</button>}
      </main>
    </div>
  );
}

export default WrappedApp;

What error am I seeing?

Initial issue I am encountering is the TypeError: favourites.map is not a function error message.

4
  • when you say unsuccessful, why was it so? Commented Jun 10, 2020 at 8:09
  • Instead of showing conversion, show us the relevant part of code that you're stuck. Commented Jun 10, 2020 at 8:12
  • @RedBaron I updated the question. Initial issue I am encountering is the TypeError: favourites.map is not a function error message. Commented Jun 10, 2020 at 8:13
  • 1
    @ade1e see answer Commented Jun 10, 2020 at 8:15

2 Answers 2

3

Following your error in the comment,

Instead of this:

<ShortList data={props.data} favourites deleteFavourite />

Use this:

<ShortList data={props.data} 
  favourites={favourities} 
  deleteFavourite={deleteFavourite} />

Only applying favourites will mean it's equal to true rather than the array in the state that you have.

Sign up to request clarification or add additional context in comments.

10 Comments

can you console.log(favourites) and see if it's an array ?
The app now renders but Shortlist no longer works. I get the error filterUpdate is not a function. I also see error in console that 'newSet' is assigned a value but never used no-unused-vars
@ade1e no worries. good job converting it too. this is the right approach to take! if you get stuck again feel free to ask another question and I'll be happy to help :)
what about just filterUpdate('')
feel free to ask a new question with just the specific code and we can go from there :)
|
1

You need to explicitely specify the props when you are passing it. to the child components

<ShortList data={props.data} favourites={favourites} deleteFavourite={deleteFavourite} /> Similar to the taglist

        <TagsList
          data={props.data}
          filter={filterText}
          favourites={favourites}
          addFavourite={addFavourite}
        />

1 Comment

Thanks I had added this after feedback from Red Baron but have marked your answer as useful too.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.