0

I am trying to convert this class based component into a function component. For some reason the Query (setQ) is not being read by the handleFormSubmit function. When I make the param in the setQ an object the value of the input form becomes [object, Object]. What am I doing wrong?

class component

import React, { Component } from "react";
import Jumbotron from "../components/Jumbotron";
import Card from "../components/Card";
import Form from "../components/Form";
import Book from "../components/Book";
import Footer from "../components/Footer";
import API from "../utils/API";
import { Col, Row, Container } from "../components/Grid";
import { List } from "../components/List";

class Home extends Component {
  state = {
    books: [],
    q: "",
    message: "Search For A Book To Begin!"
  };

  handleInputChange = event => {
    const { name, value } = event.target;
    this.setState({
      [name]: value
    });
  };

  getBooks = () => {
    API.getBooks(this.state.q)
      .then(res =>
        this.setState({
          books: res.data
        })
      )
      .catch(() =>
        this.setState({
          books: [],
          message: "No New Books Found, Try a Different Query"
        })
      );
  };

  handleFormSubmit = event => {
    event.preventDefault();
    this.getBooks();
  };

  handleBookSave = id => {
    const book = this.state.books.find(book => book.id === id);

    API.saveBook({
      googleId: book.id,
      title: book.volumeInfo.title,
      subtitle: book.volumeInfo.subtitle,
      link: book.volumeInfo.infoLink,
      authors: book.volumeInfo.authors,
      description: book.volumeInfo.description,
      image: book.volumeInfo.imageLinks.thumbnail
    }).then(() => this.getBooks());
  };

  render() {
    return (
      <Container>
        <Row>
          <Col size="md-12">
            <Jumbotron>
              <h1 className="text-center">
                <strong>(React) Google Books Search</strong>
              </h1>
              <h2 className="text-center">Search for and Save Books of Interest.</h2>
            </Jumbotron>
          </Col>
          <Col size="md-12">
            <Card title="Book Search" icon="far fa-book">
              <Form
                handleInputChange={this.handleInputChange}
                handleFormSubmit={this.handleFormSubmit}
                q={this.state.q}
              />
            </Card>
          </Col>
        </Row>
        <Row>
          <Col size="md-12">
            <Card title="Results">
              {this.state.books.length ? (
                <List>
                  {this.state.books.map(book => (
                    <Book
                      key={book.id}
                      title={book.volumeInfo.title}
                      subtitle={book.volumeInfo.subtitle}
                      link={book.volumeInfo.infoLink}
                      authors={book.volumeInfo.authors.join(", ")}
                      description={book.volumeInfo.description}
                      image={book.volumeInfo.imageLinks.thumbnail}
                      Button={() => (
                        <button
                          onClick={() => this.handleBookSave(book.id)}
                          className="btn btn-primary ml-2"
                        >
                          Save
                        </button>
                      )}
                    />
                  ))}
                </List>
              ) : (
                <h2 className="text-center">{this.state.message}</h2>
              )}
            </Card>
          </Col>
        </Row>
        <Footer />
      </Container>
    );
  }
}

export default Home;

functional conversion

import React from "react";
// import Jumbotron from "react-bootstrap/Jumbotron";
import Row from "react-bootstrap/Row";
import Card from "../components/Card";
import Form from "../components/Form";
import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import Jumbotron from "react-bootstrap/Jumbotron";
import { useState } from "react";
import API from "../utils/API";
import Book from "../components/Book";
import Button from "react-bootstrap/Button";
import { List } from "../components/List";
import Footer from "../components/Footer";
import "./style.css";

export default function Home() {
  let [books, setBooks] = useState([]);
  let [q, setQ] = useState("");
  let [message, setMessage] = useState("Search For A Book to Begin");

  const handleInputChange = (event) => {
    let { name, value } = event.target;
    setQ(([name] = value));
  };

  let getBooks = () => {
    API.getBooks(q)
      .then((res) => setBooks(res.data))
      .catch(() => setBooks([]));
    setMessage("No New Books Found, Try a Different Query");
  };

  const handleFormSubmit = (event) => {
    event.preventDefault();
    getBooks();
  };

  let handleBookSave = (id) => {
    const book = books.find((book) => book.id === id);

    API.saveBook({
      googleId: book.id,
      title: book.volumeInfo.title,
      subtitle: book.volumeInfo.subtitle,
      link: book.volumeInfo.infoLink,
      authors: book.volumeInfo.authors,
      description: book.volumeInfo.description,
      image: book.volumeInfo.imageLinks.thumbnail,
    }).then(() => getBooks());
  };

  return (
    <div>
      <Container>
        <Row>
          <Col md={12}>
            <Jumbotron className="rounded-3 mt-4">
              <h1 className="text-center ">
                <strong>(React) Google Books Search</strong>
              </h1>
              <h2 className="text-center">
                Search for and Save Books of Interest.
              </h2>
            </Jumbotron>
          </Col>
          <Col md={12}>
            <Card title="Book Search" icon=" fa-book">
              <Form
                handleInputChange={handleInputChange}
                handleFormSubmit={handleFormSubmit}
                q={q}
              />
            </Card>
          </Col>
        </Row>
        <Row>
          <Col md={12}>
            <Card title="Results">
              {books.length ? (
                <List>
                  {books.map((book) => (
                    <Book
                      key={book.id}
                      title={book.volumeInfo.title}
                      subtitle={book.volumeInfo.subtitle}
                      link={book.volumeInfo.infolink}
                      authors={book.volumeInfo.authors.join(", ")}
                      description={book.volumeInfo.description}
                      image={book.volumeInfo.imageLinks.thumbnail}
                      Btn={() => (
                        <Button
                          onClick={() => handleBookSave(book.id)}
                          variant="primary"
                          className="ml-2"
                        >
                          Save
                        </Button>
                      )}
                    />
                  ))}
                </List>
              ) : (
                <h2 className="text-center">{message}</h2>
              )}
            </Card>
          </Col>
        </Row>
        <Footer />
      </Container>
    </div>
  );
}

1 Answers1

1

I'm not sure exactly what you want here, but I see the problem.

First let's look here:

  state = {
    books: [],
    q: "",
    message: "Search For A Book To Begin!"
  };

  handleInputChange = event => {
    const { name, value } = event.target;
    this.setState({
      [name]: value
    });
  };

In a class component, state is an object with properties. You are taking advantage of that fact to allow accessing individual pieces of state with a dynamic name, and then setting that to value.


Now let's look at the functional component:

  let [books, setBooks] = useState([]);
  let [q, setQ] = useState("");
  let [message, setMessage] = useState("Search For A Book to Begin");

  const handleInputChange = (event) => {
    let { name, value } = event.target;
    setQ(([name] = value));
  };

Note that you no longer have a single state object. You have three completely separate pieces of state. This handleInputChange function only sets one of them, q.

And [name] = value is especially strange. What that's actually doing is assigning the first character of the string in value to the variable name, which is never used or read from again. That is certainly not what you want.

In order to do what I think you want you have to call each setMyStateHere() setter explicitly.

So I think you want this:

  const handleInputChange = (event) => {
    let { name, value } = event.target;
    if (name === 'q') {
      setQ(value)
    } else if (name === 'message') {
      setMessage(value)
    } else {
      throw new Error(`Unhandled input name: ${name}`) // or something
    }
  };

Here you inspect name to figure out which state to set, and then call the right setter function.


Or perhaps, if you want it to closer to your class component, you can store an object in state:

  let [books, setBooks] = useState([]);
  let [formValues, setFormValues] = useState({
    q: "",
    message: "Search For A Book to Begin"
  );

  const handleInputChange = (event) => {
    let { name, value } = event.target;
    setFormValues({ ...formValues, [name]: value })
  }

Now you have an object in state like you had before, and can dynamically set properties on that object.


All this assumes that you want to set something besides q from this form, otherwise this becomes trivially just:

const handleInputChange = event => { setQ(event.target.value) }
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • Apologies for the delayed response. Yes I want to set state for the q (Query) and pass it to the handleInputChange func and subsequently pass that value into my form submission which then interacts with my api through routes and controllers. The biggest issue is even with the change in my handlesubmit and handlechange funcs are outputting themselves when I inspect the component. for example handleInputChange: f handleInputChange(){} instead of handleInputChange: f(){} – Kevin Millhouse Sep 06 '21 at 16:58