How can I take a value from an input tag in a TSX component, and use that value in a Node JS file in a different directory?

Viewed 407

I'm working on a personal project where I'm pulling an API through Fetch; at the moment I can send the call from my index.js file to a TSX component that calls the API URL when my SearchButton component is clicked, but the search term needs to be declared in index.js.

Here's my SearchButton code (TSX):

import React, { useState } from 'react'

function SearchButton() {
  const [ newsResponse, setNewsResponse ]= useState(null);

  function queryOnClick() {
    fetch(`http://localhost:4000/news-api`, {
      headers: { 'Content-Type': 'application/json' }
    })
      .then((response) => response.json())
      .then((result) => {
        console.log('result:', result);
        setNewsResponse(result);
      })
      .catch((ex) => {
        console.log('error:', ex);
      });
  }

  return (
    <div className="theme--white">
      <button className="search__button padding-1 margin-1 margin-left-6" onClick={queryOnClick}>
        Click to search
      </button>
    
      {newsResponse && newsResponse.articles ? (
        <div className="results__container padding-2 theme--mist">
          {newsResponse.articles.map((article: { 
            title: React.ReactNode;
            author: string;
            content: string;
            url: string;
          }) => (
            <div className="article__container box-shadow padding-2 margin-4 margin-left-6 margin-right-6 theme--white">
              <h2 className="article__title padding-bottom-2 margin-bottom-2">{article.title}</h2>
              <h3 className="article__author padding-bottom-2 margin-bottom-2">Written by: {article.author || 'An uncredited author'}</h3>
              <p className="article__content">
                {article.content.length > 150 ?
                  `${article.content.substring(0, 150)}... [Article shortened - Click the URL below to read more]` : article.content
                }
              </p>
              <div className="article__url margin-top-2">
                <p>
                  <p>Source:</p>
                  <a href={article.url}>{article.url}</a>
                </p>
              </div>
            </div>
          ))}
        </div>
      ) : null}
    </div>
  );
}

export default SearchButton;

I want to change that so a user can search for an article from the API by using a HTML input to submit a topic which would amend the API URL. For instance, if I searched Bitcoin, it would search https://API-${Bitcoin}.com. Due to CORS policy blocking, I can't just call the API in my TSX file as it has to go from localhost:3000 > localhost:4000 via the Node JS file.

At the moment, my input renders the user's query into the console, but I can't seem to get it over to my index.js file. How can I pass a value that's either in the console.log, or from the input's value, through to my Node JS index.js file?

Here's my SearchBar file that handles my Input (TSX):

import React, { Component } from 'react';

type SearchBarProps = {
  searchNews: (text: string) => void;
}

type SearchBarState = {
  searchString: string;
}

class SearchBar extends Component<SearchBarProps, SearchBarState> {
    static defaultProps = {
      searchNews: (text: string) => {}
    }

    state = {
      searchString: ''
    }

    searchNews = (e: any) => {
      const { searchString } = this.state

      if(e.key === 'Enter' && searchString !== '') {
      e.preventDefault();
      e.stopPropagation();
        this.props.searchNews(searchString)
        console.log(searchString)
      }
    }

    onSearchTextChange = (e: any) => {
      this.setState({
          searchString: e.target.value.trim()
      })
    }

    render() {
        return (
          <div>
              <form>
                  <div>
                      <input
                      id="search"
                      type="search"
                      value={this.state.searchString}
                      onChange={this.onSearchTextChange}
                      onKeyPress={e => this.searchNews(e)} placeholder="Search" />
                  </div>
              </form>
          </div>
        );
    }
}

export default SearchBar;

...And here's my index.js Node JS file (JS):

/*
 * Libs
 */
const express = require('express');
const fetch = require('node-fetch');
const cors = require('cors');
const app = express();

/*
 * Constants
 */
const PORT = 4000;
const API_KEY = 'x';
const SEARCH_QUERY = "Bitcoin";
const SORT_BY = "popularity";
const PAGE_SIZE = 10;

/*
 * Setup CORS - This is needed to bypass NewsAPI CORS Policy Blocking by rerouting request to localhost
 */
const corsOptions = {
  origin: 'http://localhost:3000',
  optionsSuccessStatus: 200
};

app.use(cors(corsOptions));

/*
 * Setup to request NewsAPI data using Fetch API
 */
app.get('/news-api', function (req, res) {
  fetch(`https://newsapi.org/v2/everything?q=${SEARCH_QUERY}&sortBy=${SORT_BY}&pageSize=${PAGE_SIZE}&apiKey=${API_KEY}`, {
    headers: { 'Content-Type': 'application/json' }
  })
    .then((response) => response.json())
    .then((result) => {
      console.log('result:', result);
      res.json(result);
    })
    .catch((ex) => {
      console.log('error:', ex);
      res.status(400).send({
        message: 'This is an error!',
        error: ex
     });
    });
})

/*
 * Start Backend API Proxy server
 */
app.listen(PORT, () => {
  console.log(`=================`)
  console.log(`API Connected!`)
  console.log(`Listening at http://localhost:${PORT}`)
  console.log(`=================`)
})

TLDR:

I have a TSX component that is an input (A - value={this.state.searchString}).

I want that input's value to go to a Node JS file to append a URL via a const (B - const SEARCH_QUERY).

I know what to pull from A, and where to put it in B, but don't know how to do so.

Full tech stack

Using Fetch API, React, TypeScript, Node JS and Webpack.

File paths

  • SearchButton: project/frontend/src/components/SearchButton/SearchButton.tsx
  • SearchBar: project/frontend/src/components/SearchBar/SearchBar.tsx
  • Node JS handler: project/backend/index.js
1 Answers

Essentially what you are asking here is how to pass data from the frontend to the backend. The way to do this is by including the user's search term in your fetch request to the backend. You can either include it in the body of a POST request or include it as a query string in the URL. You would need to use the body for passing large amounts of data, but something as simple as a search term can be done with a query string.


Front End

Include the current search term as a query parameter of your fetch request. I am using encodeURIComponent to apply percent-encoding to special characters.

function queryOnClick() {
  // applies percent-encoding to special characters
  const search = encodeURIComponent(searchString);
  const url = `http://localhost:4000/news-api?search=${search}`;
  fetch(url, {
...

You are missing the communication between your SearchButton and SearchBar components. I am not sure where these two components are in relation to each other on your page. If they are siblings then you will need to lift the searchString state and the queryOnClick function up to a shared parent.

I rearranged all of your components so that you have access to the right state in the right places.

import React, { useState } from "react";

function SearchButton({ onClick }: { onClick: () => void }) {
  return (
    <button
      className="search__button padding-1 margin-1 margin-left-6"
      onClick={onClick}
    >
      Click to search
    </button>
  );
}

interface SearchBarProps {
  searchNews: () => void;
  searchString: string;
  setSearchString: (s: string) => void;
}

function SearchBar({ searchNews, searchString, setSearchString }: SearchBarProps) {
  const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === "Enter" && searchString !== "") {
      e.preventDefault();
      e.stopPropagation();
      searchNews();
    }
  };

  const onSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSearchString(e.target.value.trim());
  };

  return (
    <div>
      <form>
        <div>
          <input
            id="search"
            type="search"
            value={searchString}
            onChange={onSearchTextChange}
            onKeyPress={handleKeyPress}
            placeholder="Search"
          />
        </div>
      </form>
    </div>
  );
}


interface Article {
  title: string;
  author: string;
  content: string;
  url: string;
}

interface NewsResponse {
  articles: Article[];
}

function ArticleList({ articles }: NewsResponse) {
  return (
    <div className="results__container padding-2 theme--mist">
      {articles.map((article) => (
        <div className="article__container box-shadow padding-2 margin-4 margin-left-6 margin-right-6 theme--white">
          <h2 className="article__title padding-bottom-2 margin-bottom-2">
            {article.title}
          </h2>
          <h3 className="article__author padding-bottom-2 margin-bottom-2">
            Written by: {article.author || "An uncredited author"}
          </h3>
          <p className="article__content">
            {article.content.length > 150
              ? `${article.content.substring(
                  0,
                  150
                )}... [Article shortened - Click the URL below to read more]`
              : article.content}
          </p>
          <div className="article__url margin-top-2">
            <p>
              <p>Source:</p>
              <a href={article.url}>{article.url}</a>
            </p>
          </div>
        </div>
      ))}
    </div>
  );
}

function SearchPage() {
  const [newsResponse, setNewsResponse] = useState<NewsResponse | null>(null);

  const [searchString, setSearchString] = useState("");

  function queryOnClick() {
    // applies percent-encoding to special characters
    const search = encodeURIComponent(searchString);
    const url = `http://localhost:4000/news-api?search=${search}`;
    fetch(url, {
      headers: { "Content-Type": "application/json" }
    })
      .then((response) => response.json())
      .then((result) => {
        console.log("result:", result);
        setNewsResponse(result);
      })
      .catch((ex) => {
        console.log("error:", ex);
      });
  }

  return (
    <div className="theme--white">
      <SearchBar
        searchNews={queryOnClick}
        searchString={searchString}
        setSearchString={setSearchString}
      />
      <SearchButton onClick={queryOnClick} />

      {newsResponse && newsResponse.articles ? (
        <ArticleList articles={newsResponse.articles} />
      ) : null}
    </div>
  );
}

export default SearchPage;

Back End

You need to access the search term from the search parameter of the request URL. We use the req.params property to get a dictionary of params. We can use your previous search term "Bitcoin" as the default value if there was no search param on the request.

I'm not certain if we need to encode again here or not -- you'll want to play with that.

app.get('/news-api', function (req, res) {
  const searchQuery = req.params.search || "Bitcoin";
  fetch(`https://newsapi.org/v2/everything?q=${searchQuery}&sortBy=${SORT_BY}&pageSize=${PAGE_SIZE}&apiKey=${API_KEY}`, {
...
Related