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.

nipy
  • 5,138
  • 5
  • 31
  • 72

2 Answers2

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.

Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231
Red Baron
  • 7,181
  • 10
  • 39
  • 86
  • can you `console.log(favourites)` and see if it's an array ? – Red Baron Jun 10 '20 at 08:25
  • 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` – nipy Jun 10 '20 at 08:36
  • remember to go through and change all the standalone props. I can still see this: `` when it should be `` – Red Baron Jun 10 '20 at 08:37
  • Thanks. I did this and also fixed my error in `addFavourite` to `setFavourites(newSet)` It works now. Appreciate the help. – nipy Jun 10 '20 at 08:48
  • 1
    @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 :) – Red Baron Jun 10 '20 at 08:49
  • One thing I missed... The clear search button is not working in my functional component `{hasSearch && }`. How do I send an empty string as the `value` please? I tried this but its not correct `filterUpdate(value={""})`. Thanks so much. I struggle with the React syntax. – nipy Jun 10 '20 at 10:55
  • 1
    what about just `filterUpdate('')` – Red Baron Jun 10 '20 at 11:26
  • I tried `filterUpdate('')` but this breaks the filter. – nipy Jun 10 '20 at 13:11
  • 1
    feel free to ask a new question with just the specific code and we can go from there :) – Red Baron Jun 10 '20 at 13:17
  • [Done](https://stackoverflow.com/questions/62305276/search-functionality-no-longer-working-after-converting-from-react-class-to-func) thanks – nipy Jun 10 '20 at 13:50
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}
        />
harisu
  • 1,376
  • 8
  • 17
  • Thanks I had added this after feedback from Red Baron but have marked your answer as useful too. – nipy Jun 10 '20 at 08:39