Home > Software design >  Refactor class to function components in React
Refactor class to function components in React

Time:11-17

Results after attempting to search a term and location resulting in random resultsI'm having a hard time converting these 3 class components to function components, the class components are working i just am trying to convert them for learning purposes.

the API call: yelp.js

const { default: SearchBar } = require("../components/SearchBar/SearchBar");

const Yelp = {
    searchYelp(term, location) {
        return fetch(`/api/hello?term=${term}&location=${location}`)
        .then((response) => {
            // console.log(response)
            return response.json()
        }).then((jsonResponse) => {
            // console.log(jsonResponse)
            if (jsonResponse.businesses) {
                return jsonResponse.businesses.map((business) => {
                    return {
                        id: business.id,
                        imageSrc: business.image_url,
                        name: business.name,
                        address: business.location.address1,
                        city: business.location.city,
                        state: business.location.state,
                        zipCode: business.location.zip_code,
                        category: business.categories.title,
                        rating: business.rating,
                        reviewCount: business.review_count,
                    }
                })
            }
        })
    }
}

export default Yelp

The Home component as a function that renders a SearchBar and BusinessList component: Home.js

import React, { useState } from "react";
import BusinessList from '../../../src/components/BusinessList/BusinessList';
import SearchBar from '../../../src/components/SearchBar/SearchBar';
import Yelp from '../../util/yelp';

const Home = (term, location) => {
    const [businesses, setBusinesses] = useState([]);

    const searchYelp = Yelp.searchYelp(term, location).then(businesses => {
        setBusinesses(businesses)
    })

    return (
        <>
            <SearchBar searchYelp={searchYelp} />
            <BusinessList business={businesses} />
        </>
    )
}

export default Home;

The Home component as a class: Home.js

// import React from 'react';
// import BusinessList from '../../../src/components/BusinessList/BusinessList';
// import SearchBar from '../../../src/components/SearchBar/SearchBar';
// import Yelp from '../../util/yelp';

// class Home extends React.Component {
//     constructor() {
//         super();
//         this.state = {
//             businesses: [],
//         };

//         this.searchYelp = this.searchYelp.bind(this);
//     }

//     searchYelp(term, location, sortBy) {
//         Yelp.searchYelp(term, location, sortBy).then((businesses) => {
//             this.setState({ businesses: businesses })
//         })
//     }
//     render() {
//         return (
//             <>
//                 <SearchBar searchYelp={this.searchYelp} />
//                 <BusinessList businesses={this.state.businesses} />
//             </>
//         )
//     }
// }

// export default Home;

The BusinessList component as a function that renders a Business component: BusinessList.js

import React, { useState } from "react";
import './BusinessList.css';
import Business from '../Business/Business';

function BusinessList(businesses) {
    console.log(businesses)
    return (
            <div className="BusinessList">
                {
                businesses.map(business => {
                    <Business key={business.id} business={business} />
                    })
                }
            </div>
    )
};

export default BusinessList;

The BusinessList component as a class: BusinessList.js

// import React from 'react';
// import './BusinessList.css';
// import Business from '../Business/Business';

// class BusinessList extends React.Component {
//     constructor(props) {
//         super(props)
//         console.log(props.businesses)
//     }

//     render() {
//         return (
//             <div className="BusinessList">
//                 {
//                     this.props.businesses.map((business) => {
//                         return <Business key={business.id} business={business} />
//                     })
//                 }
//             </div>
//         )
//     }
// };

// export default BusinessList;

The Business component as a function: Business.js

import React from "react";
import './Business.css';

const Business = (business) => {
    return (
        <div className="Business">
            <div className="image-container">
                <img src={business.business.imageSrc} alt={business.imageSrc} />
            </div>
            <h2>{business.business.name}</h2>
            <div className="Business-information">
                <div className="Business-address">
                    <p>{business.business.address}</p>
                    <p>{business.business.city}&nbsp;{business.state}&nbsp;{business.zipCode}</p>
                </div>
                <div className="Business-reviews">
                    <h3>{business.business.category}</h3>
                    <h3 className="rating">{business.business.rating}</h3>
                    <p>{business.business.reviewCount} reviews</p>
                </div>
            </div>
        </div>
    )
};

export default Business;

The Business component as a class: Business.js

// import React from "react";
// import './Business.css';

// class Business extends React.Component {
//     render() {
//         const { business } = this.props

//         return (
//             <div className="Business">
//                 <div className="image-container">
//                     <img src={business.imageSrc} alt={business.imageSrc} />
//                 </div>
//                 <h2>{business.name}</h2>
//                 <div className="Business-information">
//                     <div className="Business-address">
//                         <p>{business.address}</p>
//                         <p>{business.city}&nbsp;{business.state}&nbsp;{business.zipCode}</p>
//                     </div>
//                     <div className="Business-reviews">
//                         <h3>{business.category}</h3>
//                         <h3 className="rating">{business.rating}</h3>
//                         <p>{business.reviewCount} reviews</p>
//                     </div>
//                 </div>
//             </div>
//         )
//     }
// };

// export default Business;

EDIT ** My attempt at SearchBar component as function: SearchBar.js

import React, { useState, useEffect } from "react";
import './SearchBar.css';

const SearchBar = (props) => {
    const [term, setTerm] = useState('')
    const [location, setLocation] = useState('')
    const [sortBy, setSortBy] = useState('best_match')

    const sortByOptions = {
        'Best Match': 'best_match',
        'Highest Rated': 'rating',
        'Most Reviewed': 'review_count'
    };

    const handleSortByChange = () => {
        setSortBy(sortBy)
        // console.log(sortByOption)
        console.log(sortBy)
    }

    const renderSortByOptions = (sortByOptions) => {
        // console.log(Object.keys(sortByOptions))
        return Object.keys(sortByOptions).map(sortByOption => {
            let sortByOptionValue = sortByOptions[sortByOption]
            // console.log(sortByOptionValue)
            return <li
                className={sortBy === sortByOption ? 'active' : ''}
                onClick={handleSortByChange}
                key={sortByOptionValue}>
                {sortByOption}
            </li>;
        })
    }

    const handleTermChange = (event) => {
        setTerm(event.target.value)
    }

    const handleLocationChange = (event) => {
        setLocation(event.target.value)
    }

    const handleSearch = (event) => {
        event.preventDefault()
        props.searchYelp(term, location)
    }

    return (
        <div className="SearchBar">
            {props.searchYelp}
            <div className="SearchBar-sort-options">
                <ul>
                    {renderSortByOptions(sortByOptions)}
                </ul>
            </div>
            <div className="SearchBar-fields">
                <input
                    onChange={handleTermChange}
                    placeholder="Search Businesses"
                />
                <input
                    onChange={handleLocationChange}
                    placeholder="Where?"
                />
                <button className="SearchBar-submit" onClick={handleSearch}>Let's Go</button>
            </div>
        </div>
    )
}

export default SearchBar;

EDIT** SearchBar component as a class: SearchBar.js

import React from 'react';
import './SearchBar.css';

class SearchBar extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            term: '',
            location: '',
            sortBy: 'best_match'
        }

        this.handleTermChange = this.handleTermChange.bind(this)
        this.handleLocationChange = this.handleLocationChange.bind(this)
        this.handleSearch = this.handleSearch.bind(this)

        this.sortByOptions = {
            'Best Match': 'best_match',
            'Highest Rated': 'rating',
            'Most Reviewed': 'review_count'
        };
    }

    getSortByClass(sortByOption) {
        // console.log(sortByOption)
        if (this.state.sortBy === sortByOption) {
            return 'active'
        }
        return ''
    }

    handleSortByChange(sortByOption) {
        this.setState({
            sortBy: sortByOption
        })
    }

    handleTermChange(event) {
        this.setState({
            term: event.target.value
        })
    }

    handleLocationChange(event) {
        this.setState({
            location: event.target.value
        })
    }

    handleSearch(event) {
        this.props.searchYelp(this.state.term, this.state.location, this.state.sortBy)
        event.preventDefault()
    }

    renderSortByOptions() {
        return Object.keys(this.sortByOptions).map(sortByOption => {
            let sortByOptionValue = this.sortByOptions[sortByOption]
            console.log(sortByOptionValue)
            return <li
                onClick={this.handleSortByChange.bind(this, sortByOptionValue)}
                className={this.getSortByClass(sortByOptionValue)}
                key={sortByOptionValue}>
                {sortByOption}
            </li>;
        })
    }

    render() {
        return (
            <div className="SearchBar">
                {this.searchYelp}
                <div className="SearchBar-sort-options">
                    <ul>
                        {this.renderSortByOptions()}
                    </ul>
                </div>
                <div className="SearchBar-fields">
                    <input onChange={this.handleTermChange} placeholder="Search Businesses" />
                    <input onChange={this.handleLocationChange} placeholder="Where?" />
                    <button className="SearchBar-submit" onClick={this.handleSearch}>Let's Go</button>
                </div>
            </div>
        )
    }
};

export default SearchBar;

I keep getting the error "Cannot read properties of undefined (reading 'map') Or the error "Businesses.map is not a function"

Im also a little confused as to why when everything is converted to function components in my final Business component in order to get things to showup im required to pass things in as business.business.imageSrc instead of just business.imageSrc

CodePudding user response:

First in Home searchYelp should be declared as a function so it can be passed as a callback to the SearchBar component.

const Home = () => {
  const [businesses, setBusinesses] = useState([]);

  const searchYelp = (term, location) => {
    Yelp.searchYelp(term, location)
      .then(businesses => {
        setBusinesses(businesses);
      });
  };

  return (
    <>
      <SearchBar searchYelp={searchYelp} />
      <BusinessList business={businesses} />
    </>
  )
};

Then in BusinessList you need to access the passed business prop. Your current code is naming the props object businesses and then attempts to map it. It could be businesses.business.map, but by convention we name the props object props or simply destructure the props you want to use. You need to also return the Business component you are mapping to.

function BusinessList({ business }) {
  return (
    <div className="BusinessList">
      {business.map(business => {
        return <Business key={business.id} business={business} />;
      })}
    </div>
  )
};

Same issue with the props object name in the Business component.

const Business = (props) => {
  return (
    <div className="Business">
      <div className="image-container">
        <img src={props.business.imageSrc} alt={props.business.imageSrc} />
      </div>
      <h2>{props.business.name}</h2>
      <div className="Business-information">
        <div className="Business-address">
          <p>{props.business.address}</p>
          <p>{props.business.city}&nbsp;{props.business.state}&nbsp;{business.zipCode}</p>
        </div>
        <div className="Business-reviews">
          <h3>{props.business.category}</h3>
          <h3 className="rating">{props.business.rating}</h3>
          <p>{props.business.reviewCount} reviews</p>
        </div>
      </div>
    </div>
  )
};

CodePudding user response:

BusinessList receives props, an object containing the props passed in.

The function parameter would either need to destructure it:

function BusinessList({ businesses }) { ... }

Or reference it off the props object:

function BusinessList(props) {
  console.log(props.businesses)
  // ...
}

CodePudding user response:

Few notes:

  • Right now Yelp.searchYelp returns Promise<any[] | undefined>, i.e undefined is a legitimate value that the consume may get. Up to you to decide if setBusinesses(businesses) when businesses is undefined is useful or not, but in that case, handle it. Otherwise default to an empty array, setBusinesses(businesses ?? []) or throw an error.
  • Do not run side effects in the render phase, i.e call the api inside a useEffect:
React.useEffect(() => {
    const searchYelp = Yelp.searchYelp(term, location).then(businesses => {
        setBusinesses(businesses ?? [])
    })
}, [term, location])
  • Lastly, const Business = (business) => { here business is actually the props object. You can simply destructure it const Business = ({ business }) => { to get the value directly.
  • Related