【问题标题】:Fetch data from API when form's search button clicked and show data on another page in React JS单击表单的搜索按钮时从 API 获取数据并在 React JS 的另一个页面上显示数据
【发布时间】:2019-06-16 13:36:51
【问题描述】:

我正在开发一个 React JS Web 应用程序,其中有一个包含四个选择字段(品牌、型号、最低价格和最高价格)和一个搜索按钮的表单。搜索结果的数据将根据选项的选择从 API 中获取。当用户单击搜索按钮时,我想在卡片的另一个页面上显示该数据(页面路径路径:/search)。我正在使用反应路由器。 API url/端点是https://mysterious-journey-51969.herokuapp.com/api/search-vehicle/?q=mercedes&m=sprinter&pf=0&pt=100000,其中“q”字段匹配车辆制造,“m”字段匹配模型,“pf”字段匹配最低价格,“pt”字段匹配最高价格。我该怎么做?

这是我的表单组件代码:

import React, { Component } from 'react';
import { Form, FormGroup, Input } from 'reactstrap';
import { veh_data } from '../shared/vehicle_make_and_models';

const defaultValues = [
    { value: 0, text: 0, key: 1 },
    { value: 500, text: 500, key: 2 },
    { value: 1000, text: 1000, key: 3 },
    { value: 1500, text: 1500, key: 4 },
    { value: 2000, text: 2000, key: 5 },
    { value: 2000, text: 2000, key: 6 }
];

const MIN_TITLE = { selected: true, disabled: true, text: 'Min Price' };
const MAX_TITLE = { selected: true, disabled: true, text: 'Max Price' };

class ImgAndForm extends Component {
    constructor(props) {
        super(props);

        this.handleSearch = this.handleSearch.bind(this);
        this.keyToOption = this.keyToOption.bind(this);
        this.renderOptions = this.renderOptions.bind(this);
        this.handleModelChange = this.handleModelChange.bind(this);

        this.state = {
            minData: [MIN_TITLE, ...defaultValues],
            maxData: [MAX_TITLE, ...defaultValues],

            minValue: null,
            maxValue: null,
            modelSelected: null
        };

    }

    renderOptions(data) {
        return data.map(datum => {
            // this allows us to indicate whether we are selecting or disabling
            const selected = datum.selected || false;
            const disabled = datum.disabled || false;

            return (
                <option key={datum.key} value={datum.value} selected={selected} disabled={disabled}>
                    {datum.text}
                </option>
            );
        });
    }

    handleModelChange(event) {
        console.log(event.target.value);
        this.setState({ modelSelected: event.target.value });
    }

    handleSearch(event) {
        alert("Search button clicked");

    }

    keyToOption(key) {
        return key.split("-")
            .map(word => word.slice(0, 1).toUpperCase() + word.slice(1))
            .join(" ");
    }

    handleMinSelect = event => {
        const value = event.target.value;
        const newMaxValues = [];

        defaultValues.forEach(datum => {
            if (datum.value >= Number.parseInt(value, 10)) {
                newMaxValues.push(datum);
            }
        });

        this.setState({
            maxData: [MAX_TITLE, ...newMaxValues],
            minValue: value
        });
    };

    handleMaxSelect = event => {
        const value = event.target.value;
        this.setState({ maxValue: value });
    };

    render() {

        const vehicles = veh_data.reduce((acc, veh, i) => {
            let make = Object.keys(veh)[0],
                vehModels = veh[make];

            return {
                makes: [
                    ...acc.makes,
                    <option key={make + i} value={make}>{this.keyToOption(make)}</option>
                ],
                models: {
                    ...acc.models,
                    [make]: vehModels.map((model, i) => {
                        return (
                            <option key={make + model + i} value={model}>
                                {this.keyToOption(model)}
                            </option>
                        );
                    })
                }
            };
        }, { makes: [], models: [] });

        const selectedModels =
            this.state.modelSelected && this.state.modelSelected.length ? (
                vehicles.models[this.state.modelSelected]
            ) : (
                    <option value="">Model (select make first)</option>
                );

        return (
            <div>
                <header className="headerbg d-flex">
                    <div className="container my-auto">
                        <div className="row">
                            <div className="offset-1 col-10 offset-lg-0 col-lg-4">
                                <div id="search-form-div" className="container">
                                    <div className="row">
                                        <div className="col-12 my-4">
                                            <h3>Search</h3>
                                            <Form onSubmit={this.handleSearch}>
                                                <FormGroup>
                                                    <Input
                                                        onChange={e => this.handleModelChange(e)}
                                                        type="select"
                                                        name="q"
                                                        id="q"
                                                    >
                                                        <option value="">Make</option>
                                                        {vehicles.makes}
                                                    </Input>
                                                </FormGroup>
                                                <FormGroup>
                                                    <Input type="select" name="m" id="m">
                                                        {selectedModels}
                                                    </Input>
                                                </FormGroup>
                                                <FormGroup>
                                                    <Input type="select"
                                                        name="pf"
                                                        id="pf"
                                                        value={this.state.minValue}
                                                        onChange={this.handleMinSelect}>
                                                        {this.renderOptions(this.state.minData)}
                                                    </Input>
                                                </FormGroup>
                                                <FormGroup>
                                                    <Input
                                                        type="select"
                                                        name="pt"
                                                        id="pt"
                                                        value={this.state.maxValue}
                                                        onChange={this.handleMaxSelect}>
                                                        {this.renderOptions(this.state.maxData)}
                                                    </Input>
                                                </FormGroup>
                                                <FormGroup>
                                                    <Input type="submit" name="search" id="search" className="btn btn-primary" value="Search" />
                                                </FormGroup>
                                            </Form>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </header>

            </div>
        );
    }
}

export default ImgAndForm;

这是我的搜索结果组件代码:

import React, { Component } from 'react';
import Smallheader from './SmallHeader';
import { Card, CardImg, CardTitle, CardSubtitle } from 'reactstrap';

class SearchResult extends Component {
constructor(props) {
    super(props);

    this.state = {
    };
}


render() {

    return (
        <div>
            <Smallheader />
            <div className="my-5">
                <div className="container text-center" id="contactContainer">
                    <div className="row">
                        <div className="col-lg-12 mx-auto">
                            <h2 className="text-center">Search Results</h2>
                            <hr className="my-4 thick-hr" />
                        </div>
                    </div>
                    <div className="row">
                        <div className="col-6 col-lg-3 mt-4">
                            <Card>
                                <a href="#">
                                    <CardImg src="" className="img-fluid" />
                                    <CardTitle>Title Here</CardTitle>
                                    <CardSubtitle>Price Here</CardSubtitle>
                                </a>
                            </Card>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}
}

export default SearchResult;

【问题讨论】:

标签: javascript reactjs api django-rest-framework fetch


【解决方案1】:

这是一个可行的解决方案...

https://codesandbox.io/s/lrv2w3qxlq?moduleview=1

我已经 imported 你的 SearchResults 组件并将它直接放在你的 ImgAndForm 下方,但你可以在该渲染函数中将它移动到任何位置。

对于这种特定情况,您需要一种在新“页面”上呈现它的方法,您需要一种管理共享应用程序状态的方法,例如 Redux 或至少像 @MikeZinn 提到的容器组件,但要正确地做到这一点将需要大量的工作来实施路由并重新设计您的整个程序。 (如果你愿意,我可以向你展示一个小技巧,暂时不用它来产生相同的结果,但我建议你寻找更持久的解决方案。)

由于SearchResults 组件可以是“无状态”的,因此我删除了构造函数,但我暂时将其保留为一个类,因为该组件最终可能需要状态。

我添加了 axios 库来从 API 中获取数据,但是您的程序中已经使用的任何其他 XHR 模块都可以。

注意:由于您的表单当前能够查询的特定 API 端点不可用,我对您提供的“mercedes”示例进行了硬编码,但程序将同时记录“realQuery”和“dummyQuery”,因此您会看到无论何时修复它,它都会生成正确的查询结构。

表单组件

import React, { Component } from "react";
import { Form, FormGroup, Input } from "reactstrap";
// import { veh_data } from '../shared/vehicle_make_and_models';
import SearchResult from "./result";
import axios from "axios";

const veh_data = [
  { "alfa-romeo": ["145", "90", "Alfa 6", "Alfasud"] },
  { "aston-martin": ["15", "2-Litre", "AM Vantage", "Atom", "Cygnet", "DB2"] },
  { audi: ["100", "200", "A1", "A2", "A3", "A4", "A5", "A6", "A7"] }
];
const defaultValues = [
  { value: 0, text: 0, key: 1 },
  { value: 500, text: 500, key: 2 },
  { value: 1000, text: 1000, key: 3 },
  { value: 1500, text: 1500, key: 4 },
  { value: 2000, text: 2000, key: 5 },
  { value: 2000, text: 2000, key: 6 }
];

const MIN_TITLE = { selected: true, disabled: true, text: "Min Price" };
const MAX_TITLE = { selected: true, disabled: true, text: "Max Price" };

class ImgAndForm extends Component {
  constructor(props) {
    super(props);

    this.handleSearch = this.handleSearch.bind(this);
    this.keyToOption = this.keyToOption.bind(this);
    this.renderOptions = this.renderOptions.bind(this);
    this.handleModelChange = this.handleModelChange.bind(this);

    this.state = {
      minData: [MIN_TITLE, ...defaultValues],
      maxData: [MAX_TITLE, ...defaultValues],

      minValue: "",
      maxValue: "",
      modelSelected: "",
      makeSelected: "",
      searchResults: ""
    };
  }

  renderOptions(data) {
    return data.map(datum => {
      // this allows us to indicate whether we are selecting or disabling
      const selected = datum.selected || false;
      const disabled = datum.disabled || false;

      return (
        <option
          key={datum.key}
          value={datum.value}
          selected={selected}
          disabled={disabled}
        >
          {datum.text}
        </option>
      );
    });
  }

  handleModelChange(event) {
    console.log(event.target.value);
    this.setState({ modelSelected: event.target.value });
  }

  handleMakeChange(event) {
    console.log(event.target.value);
    this.setState({ makeSelected: event.target.value });
  }

  async handleSearch(event) {
    event.preventDefault();

    alert("Search button clicked");
    let { makeSelected, modelSelected, minValue, maxValue } = this.state;
    let realQuery =
      "https://mysterious-journey-51969.herokuapp.com/api/search-vehicle/?" +
      `q=${makeSelected.split("-").join("")}` +
      `&m=${modelSelected.split("-").join("")}` +
      `&pf=${minValue}` +
      `&pt=${maxValue}`;
    let dummyQuery =
      "https://mysterious-journey-51969.herokuapp.com/api/search-vehicle/?q=mercedes&m=sprinter&pf=0&pt=100000";
    console.log("realQuery (was not run)", realQuery);
    console.log("dummyQuery (was run)", dummyQuery);
    let res = await axios.get(dummyQuery).catch(err => console.log(err));
    console.log("res", res.data);
    if (res && res.data) {
      this.setState(prevState => {
        return {
          ...prevState,
          searchResults: res.data
        };
      });
    }
  }

  keyToOption(key) {
    return key
      .split("-")
      .map(word => word.slice(0, 1).toUpperCase() + word.slice(1))
      .join(" ");
  }

  handleMinSelect = event => {
    const value = event.target.value;
    const newMaxValues = [];

    defaultValues.forEach(datum => {
      if (datum.value >= Number.parseInt(value, 10)) {
        newMaxValues.push(datum);
      }
    });

    this.setState({
      maxData: [MAX_TITLE, ...newMaxValues],
      minValue: value
    });
  };

  handleMaxSelect = event => {
    const value = event.target.value;
    this.setState({ maxValue: value });
  };

  render() {
    const vehicles = veh_data.reduce(
      (acc, veh, i) => {
        let make = Object.keys(veh)[0],
          vehModels = veh[make];

        return {
          makes: [
            ...acc.makes,
            <option key={make + i} value={make}>
              {this.keyToOption(make)}
            </option>
          ],
          models: {
            ...acc.models,
            [make]: vehModels.map((model, i) => {
              return (
                <option key={make + model + i} value={model}>
                  {this.keyToOption(model)}
                </option>
              );
            })
          }
        };
      },
      { makes: [], models: [] }
    );

    const selectedModels =
      this.state.makeSelected && this.state.makeSelected.length ? (
        vehicles.models[this.state.makeSelected]
      ) : (
        <option value="">Model (select make first)</option>
      );

    return (
      <div>
        <header className="headerbg d-flex">
          <div className="container my-auto">
            <div className="row">
              <div className="offset-1 col-10 offset-lg-0 col-lg-4">
                <div id="search-form-div" className="container">
                  <div className="row">
                    <div className="col-12 my-4">
                      <h3>Search</h3>
                      <Form onSubmit={this.handleSearch}>
                        <FormGroup key={1}>
                          <Input
                            onChange={e => this.handleMakeChange(e)}
                            type="select"
                            name="q"
                            id="q"
                          >
                            <option value="">Make</option>
                            {vehicles.makes}
                          </Input>
                        </FormGroup>
                        <FormGroup key={2}>
                          <Input
                            onChange={e => this.handleModelChange(e)}
                            type="select"
                            name="m"
                            id="m"
                          >
                            {selectedModels}
                          </Input>
                        </FormGroup>
                        <FormGroup key={3}>
                          <Input
                            type="select"
                            name="pf"
                            id="pf"
                            value={this.state.minValue}
                            onChange={this.handleMinSelect}
                          >
                            {this.renderOptions(this.state.minData)}
                          </Input>
                        </FormGroup>
                        <FormGroup key={4}>
                          <Input
                            type="select"
                            name="pt"
                            id="pt"
                            value={this.state.maxValue}
                            onChange={this.handleMaxSelect}
                          >
                            {this.renderOptions(this.state.maxData)}
                          </Input>
                        </FormGroup>
                        <FormGroup key={5}>
                          <Input
                            type="submit"
                            name="search"
                            id="search"
                            className="btn btn-primary"
                            value="Search"
                          />
                        </FormGroup>
                      </Form>
                      <SearchResult results={this.state.searchResults} />
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </header>
      </div>
    );
  }
}

export default ImgAndForm;

结果组件

import React, { Component } from "react";
// import Smallheader from './SmallHeader';
import { Card, CardImg, CardTitle, CardSubtitle } from "reactstrap";

class SearchResult extends Component {
  renderResults() {
    let { results } = this.props;
    console.log("results", results);
    if (results && results.length) {
      return results.map(({ price, text, title, remote_image }, i) => {
        return (
          <Card key={"card-" + i}>
            <a href="#">
              <CardImg src={remote_image} className="img-fluid" />
              <CardTitle>{title}</CardTitle>
              <CardSubtitle>{price}</CardSubtitle>
            </a>
          </Card>
        );
      });
    }
  }

  render() {
    return (
      <div>
        {/* <Smallheader /> */}
        <div className="my-5">
          <div className="container text-center" id="contactContainer">
            <div className="row">
              <div className="col-lg-12 mx-auto">
                <h2 className="text-center">Search Results</h2>
                <hr className="my-4 thick-hr" />
              </div>
            </div>
            <div className="row">
              <div className="col-6 col-lg-3 mt-4">{this.renderResults()}</div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

export default SearchResult;

【讨论】:

    【解决方案2】:

    这正是 Redux 在不使用 Redux 的情况下解决的问题类型,您需要将状态存储在共享的父组件上。例如,

    class Search extends Component {
      state = {
        searchResult: null,
      };
    
      handleSearch = searchResult => {
        this.setState({
          searchResult,
        });
      }
    
      render(){
        const { searchResult, } = this.state;
        if(searchResult === null){
          return (
            <ImgAndForm handleSearch={this.handleSearch} />
          );
        }
    
        return (
          <SearchResult searchResult={searchResult} />
        );
      }
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-22
      • 1970-01-01
      • 1970-01-01
      • 2020-12-17
      • 1970-01-01
      相关资源
      最近更新 更多