Bind onClick when using higher order component

时间:2018-04-20 21:21:01

标签: javascript reactjs higher-order-components

Probably a newb-ish question. I'm new to react.

Following some blog posts and such, I was able to build a page that higher order components and componentDidMount to load data from an API and render it to the page. It works great and the code looks clean, I can't figure out though, how to pass some kind of onClick through the higher order component, ultimately I'd like to move the guts of the fetch into a function that can be called by both componentDidMount as well as <Button onClick={}>Reload</Button>. Halp pls

import React, { Component } from 'react';
import {Button, CardColumns, Card, CardHeader, CardBody} from 'reactstrap';

const API = 'http://localhost:3000/';
const DEFAULT_QUERY = 'endpoint';

const withFetching = (url) => (Comp) =>
  class WithFetching extends Component {
    constructor(props) {
      super(props);

      this.state = {
        data: {},
        isLoading: false,
        error: null,
      };

      // Goes here?
      this.onClick = () => {
        console.log("Handled!");
      };
    }

    componentDidMount() {
      this.setState({ isLoading: true });

      fetch(url)
        .then(response => {
          if (response.ok) {
            return response.json();
          } else {
            throw new Error('Something went wrong ...');
          }
        })
        .then(data => this.setState({ data, isLoading: false }))
        .catch(error => this.setState({ error, isLoading: false }));
    }

    // Or here maybe??
    this.onClick = () => {
      console.log("Handled!");
    };

    render() {
      // How do I pass it in?
      return <Comp { ...this.props } { ...this.state } onClick={this.onClick} />
    }
  }

// How do I tell this component to expect it to recieve the handler?
const App = ({ data, isLoading, error }) => {
  const hits = data.hits || [];
  console.log(data);

  if (error) {
    return <p>{error.message}</p>;
  }

  if (isLoading) {
    return <p>Loading ...</p>;
  }

  return (
    <div className="animated fadeIn">
      <CardColumns className="cols-2">
        <Card>
          <CardHeader>
            API Card!
            <div className="card-actions">
            </div>
          </CardHeader>
          <CardBody>
            {hits.map(hit =>
              <div key={hit.foo}>
                <h3>{hit.foo}</h3>
                _____
              </div>
            )}

            <Button onClick={props.onClick}>Launch demo modal</Button>

          </CardBody>
        </Card>
      </CardColumns>
    </div>
  );
}

export default withFetching(API + DEFAULT_QUERY)(App);

Here is the blog post that led me to the architecture I'm using:

Edit: I can create a function outside the class so that it is available everywhere, but the reason I originally wanted to leave it in was so that I could actually alter the state and re-render the card with the new data. Trying to figure out the proper use of bind() to make that work... JS makes me feel so dumb sometimes :p

2 个答案:

答案 0 :(得分:0)

Have you considered making the function a root level function outside of all the classes? Then any component can call it.

For example:

import React, { Component } from 'react';
import {Button, CardColumns, Card, CardHeader, CardBody} from 'reactstrap';

const API = 'http://localhost:3000/';
const DEFAULT_QUERY = 'endpoint';

function sharedUtilityFunction(){
   // Do something here
}

const withFetching = (url) => (Comp) =>
  class WithFetching extends Component {
    constructor(props) {
      super(props);

      this.state = {
        data: {},
        isLoading: false,
        error: null,
      };

    }

    componentDidMount() {

      sharedUtilityFunction();

      this.setState({ isLoading: true });

      fetch(url)
        .then(response => {
          if (response.ok) {
            return response.json();
          } else {
            throw new Error('Something went wrong ...');
          }
        })
        .then(data => this.setState({ data, isLoading: false }))
        .catch(error => this.setState({ error, isLoading: false }));
    }

    render() {
      return <Comp { ...this.props } { ...this.state } />
    }
  }

// How do I tell this component to expect it to recieve the handler?
const App = ({ data, isLoading, error }) => {
  const hits = data.hits || [];
  console.log(data);

  if (error) {
    return <p>{error.message}</p>;
  }

  if (isLoading) {
    return <p>Loading ...</p>;
  }

  return (
    <div className="animated fadeIn">
      <CardColumns className="cols-2">
        <Card>
          <CardHeader>
            API Card!
            <div className="card-actions">
            </div>
          </CardHeader>
          <CardBody>
            {hits.map(hit =>
              <div key={hit.foo}>
                <h3>{hit.foo}</h3>
                _____
              </div>
            )}

            <Button onClick={() => sharedUtilityFunction()}>Launch demo modal</Button>

          </CardBody>
        </Card>
      </CardColumns>
    </div>
  );
}

export default withFetching(API + DEFAULT_QUERY)(App);

答案 1 :(得分:0)

我正在寻找的答案确实是将函数作为prop传递给低阶组件。为了做到这一点,我需要改变竞争对手预期的方式:const App = props => { TBD是否有其他的重复,但我认为状态已经被传递给了道具。 ..正确调用函数会导致isLoading渲染,这是一个好兆头。

import React, { Component } from 'react';
import {Button, CardColumns, Card, CardHeader, CardBody} from 'reactstrap';

const API = 'http://localhost:3000/';
const DEFAULT_QUERY = 'endpoint';

const withFetching = (url) => (Comp) =>
  class WithFetching extends Component {
    constructor(props) {
      super(props);

      this.state = {
        data: {},
        isLoading: false,
        error: null,
      };

      this.goFetch = this.goFetch.bind(this);
    }

    goFetch() {
      this.setState({ isLoading: true });
      fetch(url)
        .then(response => {
          if (response.ok) {
            return response.json();
          } else {
            throw new Error('Something went wrong ...');
          }
        })
        .then(data => this.setState({ data, isLoading: false }))
        .catch(error => this.setState({ error, isLoading: false }));
    }

    componentDidMount() {
      this.goFetch();
    }

    render() {
      return <Comp { ...this.props } { ...this.state } goFetch={this.goFetch}/>
    }
  }

const App = props => {
  const hits = props.data.hits || [];

  if (props.error) {
    return <p>{props.error.message}</p>;
  }

  if (props.isLoading) {
    return <p>Loading ...</p>;
  }

  return (
    <div className="animated fadeIn">
      <CardColumns className="cols-2">
        <Card>
          <CardHeader>
            API Card!
            <div className="card-actions">
            </div>
          </CardHeader>
          <CardBody>
            {hits.map((hit, index) =>
              <div key={index}>
                Foo: <h3>{hit.foo}</h3>
              </div>
            )}
            <Button onClick={props.goFetch}>Refresh</Button>
          </CardBody>
        </Card>
      </CardColumns>
    </div>
  );
}



export default withFetching(API + DEFAULT_QUERY)(App);