1
votes

i am currently still trying to wrap my head around redux when using next.js and i am not sure what is the best way to use redux with next. I am used to using mapDispatchToProps for my actions and mapStateToProps for my props. After some research i am now using next-redux-wrapper in my _app.js like recommended but now i am fighting with how to best get my props and dispatch my actions. I had look at a few examples and practices and now have a counter component based on one of these examples.

class Counter extends Component {
  increment = () => {
    const {dispatch} = this.props
    dispatch(incrementCount())
  }

  decrement = () => {
    const {dispatch} = this.props
    dispatch(decrementCount())
  }

  reset = () => {
    const {dispatch} = this.props
    dispatch(resetCount())
  }

  render () {
    const { count } = this.props
    return (
      <div>
        <h1>Count: <span>{count}</span></h1>
        <button onClick={this.increment}>+1</button>
        <button onClick={this.decrement}>-1</button>
        <button onClick={this.reset}>Reset</button>
      </div>
    )
  }
}

function mapStateToProps (state) {
  const {count} = state.counter;
  return {count};
}

export default connect(mapStateToProps)(Counter)

Most examples i have seen so far do something similar to this or only dispatch actions in getInitialProps. Is there a reason to do it this way and not use mapDispatchToProps?

Cause this work perfectly fine as well:

export default connect(null, {authenticate})(Signin);

Dispatching actions in getIntialProps seems to have some drawback (or i made some mistakes), cause they do not get executed again when the props change. In my user-profile component i get the current user based on a token from the redux store like this:

const Whoami = ({isAuthenticated, user}) => (
  <Layout title="Who Am I">
    {(isAuthenticated && user && <h3 className="title is-3">You are logged in as <strong className="is-size-2 has-text-primary">{user}</strong>.</h3>) ||
      <h3 className="title is-3 has-text-danger ">You are not authenticated.</h3>}
  </Layout>
);

Whoami.getInitialProps = async function (ctx) {
  initialize(ctx);

  const token = ctx.store.getState().auth.token;
  if(token) {
    const response = await axios.get(`${API}/user`, {headers: {
      authorization: token
    }});
    const user = response.data.user;
    return {
      user
    };
  }
    }

const mapStateToProps = (state) => (
  {isAuthenticated: !!state.auth.token}
);

export default connect(mapStateToProps)(Whoami);

This works perfectly fine for the initial page-load or when navigating there one the client, but when the token expires or i logout the page does not reflect that without reload or navigating there again without my mapStateToProps. But it seems super clunky to split the concern over 2 seperate functions. But i cant find a cleaner way to do it.

Thanks in advance

2

2 Answers

2
votes

About mapDispatchToProps:

It is better to use mapDispatchToProps at least because it is easier to test: you can just pass a mock function to your component. With using this.props.dispatch to dispatch some imported actions it can be much harder.

About getInitialProps:

This answer may be helpful:

GetInitialProps: is provided by Next.js and it is NOT always triggered, so be careful with that, it happen when you wrap 1 component inside another. If the parent Component has GetInitialProps, the child's GetInitialProps will never be triggered, see this thread for more info.

1
votes

I found some answers to my questions after playing around with next a bit more. For pages where the data does not change after intial load, i could get rid of mapStateToProps by rewriting my thunks a bit to return the dispatches and only use getInitialProps like this:

export function fetchShow(id) {
  return (dispatch) => {
      dispatch({ type: actionTypes.FETCH_SHOW_REQUESTED,id});
      // we need to return the fetch so we can await it
      return fetch(`http://api.tvmaze.com/shows/${id}`)
          .then((response) => {
              if (!response.ok) {
                  throw Error(response.statusText);
              }
              //dispatch(itemsIsLoading(false));
              return response;
          })
          .then((response) => response.json())
          .then((data) =>      dispatch({type: actionTypes.FETCH_SHOW_SUCEEDED,id, show: data, time: Date.now() }))
          .catch(() => dispatch({ type: actionTypes.FETCH_SHOW_ERROR,id }));
  };
}

Post.getInitialProps = async function ({store, isServer, pathname, query}) {
  const { id } = query;
  const {show} = await store.dispatch(fetchShow(id));
  return {show};
}

For pages where the data should update upon store changes i am not sure yet. My current idea is to try and write a helper function that will be called from both getInitialProps and mapStateToProps to reduce code duplication but i am not sure yet.