Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initializing nested component props on server side #1124

Closed
jlabarbera11 opened this issue Feb 14, 2017 · 5 comments
Closed

Initializing nested component props on server side #1124

jlabarbera11 opened this issue Feb 14, 2017 · 5 comments

Comments

@jlabarbera11
Copy link

I'm using next.js with redux, I have a component (table) that I'm populating via an API call that I'd like to be done on the server-side. I've looked at the with-redux example, which relies on the fact that the page by itself can populate the store in getInitialProps. However, I'd like to abstract that away from the page that contains the table, as I don't think the page should have such responsibility. I'd also like to avoid cascading getInitialProps() calls. Is there a way to accomplish this with nextjs, or does it go against convention? It seems like redux-connect can solve my problem, but I'm unsure of how to integrate it into the project.

@angel200sdnot
Copy link

If you want to store the table in the redux store by fetching it from the server then you can have this in the table component

import { Component } from 'react'
import { connect } from 'react-redux'
import 'isomorphic-fetch'
import { updateTable } from '../actions' //dispatch function to update table in redux store

class Table extends Component {

  componentDidMount() {
    fetch("/api/table")
      .then(response => {return response.json()})
      .then(data => {
        this.props.dispatch(updateTable(data))
      })
      .catch(err => console.log(err))
  }

  render() {
    if (!this.props.table) return //return a loading message while the table content gets fetched from the server
    return (
      //your table which can access the contents with {this.props.table}
    )
  }
}

function mapStateToProps(state) {
    return {
      table: state.table
    }
}

export default connect(mapStateToProps)(Table)

You can populate the table the same way without the redux store by storing it in the component state

@jlabarbera11
Copy link
Author

I don't think this is exactly what I want, sorry if I didn't make my use case clear. My table relies on calling an external API to populate its rows. When this table is rendered server-side, I'd like to make the call then, so that it's already populated in the browser.

From what I understand, componentDidMount is only called on the browser, which is the opposite of what I'm trying to accomplish.

@arunoda
Copy link
Contributor

arunoda commented Feb 14, 2017

What you can do is fetch the data and set it the react-context. Anyway, you need to fetch data from the getInitialProps. And then use a higher order component to move the data coming from props to context.

Recompose will be your friend.

@jlabarbera11
Copy link
Author

Ok, so instead of something like this:

class Table extends Component {
  render() {
    ...
  }
}

function mapStateToProps(state) {
    return {
      table: fetch('/api/tableData')
    }
}
export default connect(mapStateToProps)(Table)

I'll need to do this instead?

table.js

class Table extends Component {
  render() {
    ...
  }
}

function mapStateToProps(state) {
    return {
      table: state.table
    }
}
export default connect(mapStateToProps)(Table)

tablePage.js

class TablePage extends Component {
  static async getInitialProps() {
    table: fetch('/api/tableData').then(res => store.dispatch({type: 'TABLE_LOADED', payload: res})
  }

  render() {
    ...
  }
}

export default TablePage

I'm worried that with this pattern there isn't a clear separation of concerns between the table and tablePage. Please let me know if I'm misunderstanding. Also, I'm unfamiliar with Recompose, will it help solve some of this?

@jlabarbera11
Copy link
Author

jlabarbera11 commented Feb 15, 2017

Ok after revisiting this, I figured that it is a good idea to keep my components unaware of redux, and only open it to my pages via an HOC.

Now it's something like this:

page.js:

 export default (Component) => {
   return class extends React.Component {

     constructor (props) {
       super(props)
       this.store = initStore(props.initialState)
     }

     static async getInitialProps() {
       this.store = initStore()
       if (typeof Component.getInitialProps === 'function') Component.getInitialProps(this.store)
       return {initialState: this.store.getState()}
     }

     render () {
       return (
         <Provider store={this.store}>
             <Component {...this.props}/>
         </Provider>
       )
     }
   }
 }

tablePage.js

const mapStateToProps = state => state.tableData
const mapDispatchToProps = dispatch => ({
  toggleRow: (count) => dispatch({type: 'UPDATE_COUNT', payload: count})
})
@connect(mapStateToProps, mapDispatchToProps)
class TablePage extends React.Component {
  static async getInitialProps(store) {
    fetch('/api/tableData').then(res => store.dispatch({type: 'TABLE_LOADED', payload: res})
  }

  render () {
    return (
      <Card>
        <Table {...this.props}/>
      </Card>
    )
  }
}
export default Page(TablePage)

This seems like it works (haven't tested the async part yet), but still is unclean. Not only does the store get created twice on the server per request, but it also seems like it's abusing the getInitialProps() method in the TablePage.

@jlabarbera11 jlabarbera11 reopened this Feb 15, 2017
@lock lock bot locked as resolved and limited conversation to collaborators May 12, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants