简体   繁体   中英

React HOC with react-router

I'm trying to re-use one of my react components, both my BlogPost and EditableBlogPost components need to make a fetch to the backend to get the same data. Id like to use a Higher Order Component to do this, but im unsure how to accomplish this with react-router because im am routing directly to those components. The HOC isnt triggered and im not able to make the fetch where i want to. Ideally the router would render the HOC and then pass the data down to the BlogPost and EditableBlogPost components I think?

Code:

Home.js

  render () {
    return (
      <Switch>
        <Route
          exact
          path={`${this.props.match.url}/:id`}
          component={BlogPost} // id like to use a HOC here to fetch data and pass it down to the BlogPost but i cant do it from within this file.
        />
        <Route
          exact
          path={`${this.props.match.url}/:id/edit`}
          component={EditableBlogPost} // id like to use a HOC here to fetch the data as well and pass it down.
        />
        <Route
          exact
          path={`${this.props.match.url}/new/post`}
          render={() => {
            return <EditableBlogPost isNew />
          }}
        />
        <BlogSummaryContainer posts={this.state.blogPosts} />
      </Switch>
    )
  }

BlogPost.js

class BlogPost extends Component {
  render () {
    console.log(this.props.match.params.id)
    console.log('this.props', this.props)
    const { classes, title, content } = this.props

    return (
      <Card className={classes.card}>
        <CardMedia
          className={classes.media}
          image='/static/images/cards/contemplative-reptile.jpg'
          title='bang'
        />
        <CardContent>
          <Typography className={classes.title}>{title}</Typography>
          <Typography variant='headline'>{title}</Typography>
          <Typography className={classes.pos}>adjective</Typography>
          <Typography variant='body1'>{content}</Typography>
        </CardContent>
        <CardActions>
          <Button
            size='small'
            component={Link}
            to={`${this.props.match.url}/edit`}
          >
            Edit
          </Button>
        </CardActions>
      </Card>
    )
  }

EditableBlogPost.js

  render () {
    const { classes } = this.props
    console.log(this.state)
return (
   ...other code...
    </CardActions>
    <Divider />
    <Typography variant='headline'>Preview</Typography>
    <CardMedia
      className={classes.media}
      image={this.state.file[0].preview}
      title='bang'
    />
    <CardContent>
      <Typography className={classes.title}>
        {this.state.title || 'Title'} // id like this to use props from HOC 
      </Typography>
      <Typography variant='headline'>
        {this.state.title || 'Title'}
      </Typography>
      <Typography className={classes.pos}>
        {this.state.catchPhrase || 'CatchPhrase'}
      </Typography>
      <Typography variant='body1'>
        {this.state.content || 'Content'}
      </Typography>
    </CardContent>
    //this is where <BlogPost> should be instead of duplicating code.
   ...
)

BlogPostContainer.js (the HOC im trying to use)

class BlogPostContainer extends Component {
  constructor () {
    super()
    this.state = { title: '', content: '' }
  }

  componentDidMount () {
    fetch(`/api/posts/${this.props.match.params.id}`)
      .then(res => {
        return res.json()
      })
      .then(blogPost => {
        // console.log('blogPost', blogPost)

        this.setState({
          title: blogPost.title,
          content: blogPost.content
        })
      })
  }

  render () {
    return <BlogPost title={this.state.title} />
  }

If im able to pass the fetch data down somehow i can remove a lot of code that is shared between BlogPost and EditableBlogPost. Maybe i'm doing something fundamentally wrong here but im not sure exactly what the best way to do this is, any help is appreciated.

It seems there's some misunderstanding in what an HOC actually is. An HOC is a function which accepts a component, then returns a new one composing that component. See this guide for more info.

// HOCs are usually written with `with` at the beginning
function withBlogPostData(WrappedComponent) {
  return class BlogPostContainer extends React.Component {
    constructor() {
      super()
      this.state = { title: "", content: "" }
    }

    componentDidMount() {
      fetch(`/api/posts/${this.props.match.params.id}`)
        .then(res => {
          return res.json()
        })
        .then(blogPost => {
          // console.log('blogPost', blogPost)

          this.setState({
            title: blogPost.title,
            content: blogPost.content,
          })
        })
    }

    render() {
      return <WrappedComponent title={this.state.title} />
    }
  }
}

// Create a new component like so
// This way, BlogPost can access the prop `title` given to it by the HOC
const BlogPostWithData = withBlogPostData(BlogPost)

// Then use it in your routes:
<Route component={BlogPostWithData} />

Also look into render props as a more flexible alternative.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM