next.js mapStateToProps, mapDispatchToProps and getInitialProps - reactjs

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

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.

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.

Related

Wait for redux action to finish dispatching when using redux saga

I have a redux saga setup which works fine. One of my dispatches is to create a new order, then once that has been created I want to do things with the updated state.
// this.props.userOrders = []
dispatch(actions.createOrder(object))
doSomethingWith(this.props.userOrders)
Since the createOrder action triggers a redux saga which calls an API, there is a delay, so this.props.userOrders is not updated before my function doSomethingWith is called. I could set a timeout, but that doesn't seem like a sustainable idea.
I have read the similar questions on Stack Overflow, and have tried implementing the methods where relevant, but I can't seem to get it working. I'm hoping with my code below that someone can just add a couple of lines which will do it.
Here are the relevant other files:
actions.js
export const createUserOrder = (data) => ({
type: 'CREATE_USER_ORDER',
data
})
Sagas.js
function * createUserOrder () {
yield takeEvery('CREATE_USER_ORDER', callCreateUserOrder)
}
export function * callCreateUserOrder (newUserOrderAction) {
try {
const data = newUserOrderAction.data
const newUserOrder = yield call(api.createUserOrder, data)
yield put({type: 'CREATE_USER_ORDER_SUCCEEDED', newUserOrder: newUserOrder})
} catch (error) {
yield put({type: 'CREATE_USER_ORDER_FAILED', error})
}
}
Api.js
export const createUserOrder = (data) => new Promise((resolve, reject) => {
api.post('/userOrders/', data, {headers: {'Content-Type': 'application/json'}})
.then((response) => {
if (!response.ok) {
reject(response)
} else {
resolve(data)
}
})
})
orders reducer:
case 'CREATE_USER_ORDER_SUCCEEDED':
if (action.newUserOrder) {
let newArray = state.slice()
newArray.push(action.newUserOrder)
return newArray
} else {
return state
}
This feels like an XY Problem. You shouldn't be "waiting" inside a component's lifecycle function / event handler at any point, but rather make use of the current state of the store.
If I understand correctly, this is your current flow:
You dispatch an action CREATE_USER_ORDER in your React component. This action is consumed by your callCreateUserOrder saga. When your create order saga is complete, it dispatches another "completed" action, which you already have as CREATE_USER_ORDER_SUCCEEDED.
What you should now add is the proper reducer/selector to handle your CREATE_USER_ORDER_SUCCEEDED:
This CREATE_USER_ORDER_SUCCEEDED action should be handled by your reducer to create a new state where some "orders" property in your state is populated. This can be connected directly to your component via a selector, at which point your component will be re-rendered and this.props.userOrders is populated.
Example:
component
class OrderList extends React.PureComponent {
static propTypes = {
userOrders: PropTypes.array.isRequired,
createOrder: PropTypes.func.isRequired,
}
addOrder() {
this.props.createOrder({...})
}
render() {
return (
<Wrapper>
<Button onClick={this.addOrder}>Add Order</Button>
<List>{this.props.userOrders.map(order => <Item>{order.name}</Item>)}</List>
</Wrapper>
)
}
}
const mapStateToProps = state => ({
userOrders: state.get('userOrders'),
})
const mapDispatchToProps = {
createOrder: () => ({ type: 'CREATE_ORDER', payload: {} }),
}
export default connect(mapStateToProps, mapDispatchToProps)(OrderList)
reducer
case 'CREATE_USER_ORDER_SUCCEEDED':
return state.update('userOrders',
orders => orders.concat([payload.newUserOrder])
)
If you really do need side-effects, then add those side-effects to your saga, or create a new saga that takes the SUCCESS action.

Subscribing to a redux action in a react component

I have an async thunk that fetches some information from a web service, it can dispatch three types of actions
FETCH_REQUESTED
FETCH_SUCCEEDED
FETCH_FAILED
Finally, if it's succeeded; it returns the actual response, or an error object.
I have a component that should detect whether the operation has failed or not, preferably by subscribing to the FETCH_FAILED action and displaying an error message based on the type of the error (404/401 and other status codes)
export const fetchData = () => {
return async (dispatch, getState) => {
const appState = getState();
const { uid } = appState.appReducer;
await dispatch(fetchRequested());
try {
const response = await LookupApiFactory().fetch({ uid });
dispatch(fetchSucceeded(response));
return response;
} catch (error) {
dispatch(fetchFailed());
return error;
}
}
}
I'm quite new to redux and react, so I'm a bit unsure if I'm heading in the right direction, any help would be appreciated.
To implement a proper redux call back and storage mechanism you should have a store to keep all your data,
const store = createStore(todos, ['Use Redux'])
then, you dispatch data to store,
store.dispatch({
type: 'FETCH_FAILED',
text: reposnse.status //Here you should give the failed response from api
});
Then you can get the value from the store in any of your components using a subscribe function. It will be called any time an action is dispatched, and some part of the state tree may potentially have changed.
store.subscribe(()=>{
store.getState().some.deep.property
})
This is a simple implementation of Redux. As your app grows more complex, you'll want to split your reducing function into separate functions, each managing independent parts of the state using combineReducers. You can get more information from redux.js site
The most common approach is to use connect function from react-redux library. This is a HoC which subscribes to state changes. Take a look at this library, additionally it allows you to bind your action creators to dispatch, what gives you an ability to dispatch your actions from component.
You can use it like this:
import React from 'react';
import { connect } from 'react-redux';
const MyComponent = ({ data, error }) => (
<div>
{error && (
<span>Error occured: {error}</span>
)}
{!error && (
<pre>{JSON.stringify(data, null, 2)}</pre>
)}
</div>
);
const mapStateToProps = (state) => ({
data: state.appReducer.data,
error: state.appReducer.error
});
export default connect(mapStateToProps)(MyComponent);
You can use conditional rendering inside your jsx as I've shown above, or use guard clause, like this:
const MyComponent = ({ data, error }) => {
if (error) {
return (
<span>Error occured: {error}</span>
);
}
return (
<pre>
{JSON.stringify(data, null, 2)}
</pre>
);
}
Assuming reducers,
for FETCH_FAILED action,you can put some meaningful flag indicating
there are some failure.Based on that flag you can show error messages or do other action.
const testReducers =(state,actione)=>{
case 'FETCH_FAILED' : {
return {
...state,{ error_in_response : true }
}
};
default : return state;
}
In your container,you can get that flag and passed it to your component.
Assuming combineReducers used to combine reducers;
const mapStateToProps=(state)=>{
return {
error_in_response : state.testReducers.error_in_response
}
}
connect(mapStateToProps)(yourComponent)
In your component, this can be accessed using this.props.error_in_response

How to pass argument to React Redux middleware inside mapDispatchToProps

The situation is I am creating a single board which will hold a collection of note cards (each note has an id, title and body), and each note card will have a button to delete it. Also the application will be syncing with firebase, so my main question is how to pass arguments to middlewares AND do it inside of mapDispatchToProps. The following is my code to point out where my success with middleware and where I am currently blocked.
To hydrate the app on startup, I dispatch a middleware function that gets the data from firebase, and then dispatches actions handled by reducers and finally gets updated by the container/presentation component.
Middleware function:
export function hydrateApp(dispatch) {
dispatch({type: 'PENDING'});
fireBaseDBRef.once('value').then(snapshot => {
let firebaseNotes = snapshot.val()
let notes = [];
// populate notes using firebaseNotes, nothing exciting
dispatch({ type: 'DONE', notes: notes });
// the 'DONE' action.type is handled by the reducer and passes data
// to the container component successfully
}).catch(e => {
dispatch({type: 'ERROR', error: e});
});
}
Container component:
const mapStateToProps = state => {
return {
notes: state.boardReducer.notes
};
};
const mapDispatchToProps = dispatch => {
return {
addNote: () => {
dispatch(boardMiddleware.createNote);
}
};
};
const BoardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(BoardPresentation);
So far so good, and this is what I added to the same middleware and container component files to handle delete scenarios.
Middleware function:
export function deleteNote(id) {
return (dispatch) => {
dispatch({type: 'PENDING'});
//firebase stuff happening here
dispatch((type: 'DONE'});
}
}
Container component:
const mapDispatchToProps = dispatch => {
return {
addNote: () => {
dispatch(boardMiddleware.createNote);
},
removeNote: (id) => {
dispatch(boardMiddleware.deleteNote(id));
}
};
};
The problem is that deleteNote gets called non-stop on startup, I don't even need to click the button.
I know the code presented may not make a whole bunch of sense, but the crux of my problem is that I need to some how pass an id to the middleware function when the user clicks on the button, and because I'm passing the function as a prop, it for some reasons decides to just call it a million times.
I could call boardMiddleware.deleteNote function inside the presentation component just like the examples in the official redux page do, but I'm wondering if there is a way of doing it the way I'm trying to do.
I also thought about binding the argument into the middleware function, but that also doesn't feel right, something like this
removeNote: (id) => {
dispatch(boardMiddleware.deleteNote.bind(id));
}
Thanks for any help in advance!

Handling Auth State using Redux

I have a chat-app that uses React, Redux and Firebase. I'm also using thunkmiddleware to do the async updates of the state with Firebase.
I successfully get everything I need, except that everything depends of a previously hard-coded variable.
The question is, how can I call inside my ActionCreators the getState() method in order to retrieve a piece of state value that I need in order to fill the rest of my states?
I currently have my auth: { uid = 'XXXZZZYYYY' }... I just need to call that like
getState().auth.uid
however that doesn't work at all.
I tried a lot of different questions, using mapDispatchToProps, etc. I can show my repo if needed.
Worth to mention that I tried following this other question without success.
Accessing Redux state in an action creator?
This is my relevant current code:
const store = createStore(
rootReducer,
defaultState,
applyMiddleware(thunkMiddleware));
And
function mapDispatchToProps(dispatch) {
watchFirebase(dispatch); // to dispatch async Firebase calls
return bindActionCreators(actionCreator, dispatch);
}
const App = connect(mapStateToProps, mapDispatchToProps)(AppWrapper);
Which I am exporting correctly as many other not pure functions work correctly.
For instance, this works correctly:
export function fillLoggedUser() {
return (dispatch, getState) => {
dispatch({
type: C.LOGGED_IN,
});
}
}
However as suggested below, this doesn't do a thing:
const logState = () => ( dispatch, getState ) => {
console.log(getState());
};
In general your thunked action creator should look something like the below (I have used a post id as an example parameter):
const getPost = ( postId ) => ( dispatch, getState ) => {
const state = getState();
const authToken = state.reducerName.authToken;
Api.getPost(postId, authToken)
.then(result => {
// where postRetrieved returns an action
dispatch(postRetrieved(result));
});
};
If this is similar to what you have then I would log your state out and see what is going on with a simple thunk.
const logState = () => ( dispatch, getState ) => {
console.log(getState());
};

How to dispatch Redux action from stateless component when route is loaded?

Goal: when loading a react-router route, dispatch a Redux action requesting asynchronic Saga worker to fetch data for the underlying stateless component of that route.
Problem: stateless components are mere functions and don't have lifecycle methods, such as componentDidMount, so I can't(?) dispatch Redux action from inside the function.
My question is partly related to Converting stateful React component to stateless functional component: How to implement "componentDidMount" kind of functionality? , but my goal is to merely dispatch a single Redux action requesting data to be populated to the store asynchronously (I use Saga, but I think that's irrelevant to the problem, as my goal is to merely dispatch an ordinary Redux action), after which the stateless component would re-render due to the changed data prop.
I am thinking of two approaches: either use some feature of react-router, or Redux's connect method. Is there a so-called "React-way" to accomplish my goal?
EDIT: the only solution I have come up with so far, is dispatching the action inside mapDispatchToProps, this way:
const mapStateToProps = (state, ownProps) => ({
data: state.myReducer.data // data rendered by the stateless component
});
const mapDispatchToProps = (dispatch) => {
// catched by a Saga watcher, and further delivered to a Saga worker that asynchronically fetches data to the store
dispatch({ type: myActionTypes.DATA_GET_REQUEST });
return {};
};
export default connect(mapStateToProps, mapDispatchToProps)(MyStatelessComponent);
However, this seems somehow dirty and not the correct way.
I don't know why you absolutly want a stateless component, while a stateful component with componentDidMount would do the job in a simple way.
Dispatching actions in mapDispatchToProps is very dangerous and may lead to dispatching not only on mount but whenever ownProps or store props changes. Side effects are not expected to be done in this method that should remains pure.
One easy way to keep your component stateless is to wrap it into an HOC (Higher-Order Component) that you could easily create:
MyStatelessComponent = withLifecycleDispatch(dispatch => ({
componentDidMount: function() { dispatch({ type: myActionTypes.DATA_GET_REQUEST })};
}))(MyStatelessComponent)
Note that if you use Redux connect after this HOC, you can easily access dispatch from props directly as if you don't use mapDispatchToProps, dispatch is injected.
You can then do something very simple like:
let MyStatelessComponent = ...
MyStatelessComponent = withLifecycle({
componentDidMount: () => this.props.dispatch({ type: myActionTypes.DATA_GET_REQUEST });
})(MyStatelessComponent)
export default connect(state => ({
date: state.myReducer.data
}))(MyStatelessComponent);
HOC definition:
import { createClass } from 'react';
const withLifeCycle = (spec) => (BaseComponent) => {
return createClass({
...spec,
render() {
return BaseComponent();
}
})
}
Here is a simple implementation of what you could do:
const onMount = (onMountFn) => (Component) => React.createClass({
componentDidMount() {
onMountFn(this.props);
},
render() {
return <Component {...this.props} />
}
});
let Hello = (props) => (
<div>Hello {props.name}</div>
)
Hello = onMount((mountProps) => {
alert("mounting, and props are accessible: name=" + mountProps.name)
})(Hello)
If you use connect around Hello component, they you can inject dispatch as props and use it instead of an alert message.
JsFiddle
Now days you can use the useEffect hook as such:
import React, { useEffect } from 'react';
const MyStatelessComponent: React.FC = (props) => {
useEffect(() => {
props.dispatchSomeAction();
});
return ...
}
This is the equivalent for the componentDidMount/componentWillMount life cycle methods of functional/stateless components.
For further reading on hooks: https://reactjs.org/docs/hooks-intro.html
I think I found the cleanest solution without having to use stateful components:
const onEnterAction = (store, dispatchAction) => {
return (nextState, replace) => {
store.dispatch(dispatchAction());
};
};
const myDataFetchAction = () => ({ type: DATA_GET_REQUEST });
export const Routes = (store) => (
<Route path='/' component={MyStatelessComponent} onEnter={onEnterAction(store, myDataFetchAction)}/>
);
The solution passes the store to a higher order function that is passed to the onEnter lifecycycle method.
Found the solution from https://github.com/reactjs/react-router-redux/issues/319
If you want it to be completely stateless you can dispatch an event when the route is entered using onEnter event.
<Route to='/app' Component={App} onEnter={dispatchAction} />
Now you can write you function here provided you either import dispatch in this file or somehow pass it as parameter.
function dispatchAction(nexState,replace){
//dispatch
}
But this solution I feel is even more dirty.
The other solution which I could be really efficient is using containers and calling componentDidMount in that.
import React,{Component,PropTypes} from 'react'
import {connect} from 'react-redux'
const propTypes = {
//
}
function mapStateToProps(state){
//
}
class ComponentContainer extends Component {
componentDidMount(){
//dispatch action
}
render(){
return(
<Component {...this.props}/> //your dumb/stateless component . Pass data as props
)
}
}
export default connect(mapStateToProps)(ComponentContainer)
In general, I don't think this is possible without some kind of trigger action which is dispatched when the component is mounted/rendered for the first time. You've achieved this by making mapDispatchToProps impure. I 100% agree with Sebastien that this is a bad idea. You could also move the impurity to the render function, which is even worse. The component lifecycle methods are meant for this! His HOC solution makes sense, if you don't want to have to write out the component classes.
I don't have much to add, but in case you just wanted to see the actual saga code, here's some pseudocode, given such a trigger action (untested):
// takes the request, *just a single time*, fetch data, and sets it in state
function* loadDataSaga() {
yield take(myActionTypes.DATA_GET_REQUEST)
const data = yield call(fetchData)
yield put({type: myActionTypes.SET_DATA, data})
}
function* mainSaga() {
yield fork(loadDataSaga);
... do all your other stuff
}
function myReducer(state, action) {
if (action.type === myActionTypes.SET_DATA) {
const newState = _.cloneDeep(state)
newState.whatever.data = action.data
newState.whatever.loading = false
return newState
} else if ( ... ) {
... blah blah
}
return state
}
const MyStatelessComponent = (props) => {
if (props.loading) {
return <Spinner/>
}
return <some stuff here {...props.data} />
}
const mapStateToProps = (state) => state.whatever;
const mapDispatchToProps = (dispatch) => {
// catched by a Saga watcher, and further delivered to a Saga worker that asynchronically fetches data to the store
dispatch({ type: myActionTypes.DATA_GET_REQUEST });
return {};
};
plus the boilerplate:
const sagaMiddleware = createSagaMiddleware();
export default connect(mapStateToProps, mapDispatchToProps)(MyStatelessComponent);
const store = createStore(
myReducer,
{ whatever: {loading: true, data: null} },
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(mainSaga)

Resources