Redux- Async Actions on UI - reactjs

Maybe I am overthinking this, but so far most of the stuff I've read on redux-thunk handles async calling from API, etc.
Ideally I would like to have the same behavior but for UI's transition.
For instance, let's say I have a game that for simplicity, it requires two players, and each player has a turn to guess a name.
If the player's guess is matched, then I want to display the dialog for 5 seconds, and then reset the game.
Otherwise, display a dialog that indicates it's the next player's turn for 5 seconds.
I have the following code:
class Game extends Component {
constructor(props) {
super(props);
}
componentWillReceiveProps(nextProps) {
const { isMatchedNumbers } = nextProps
if (isMatchedNumbers) {
this.props.showDialog('You win!')
this.props.resetGame()
} else {
this.props.showDialog('next turn')
this.props.nextTurnPlayer()
}
}
render() {
...
}
}
const mapStateToProps = (state, props) => ({
isMatchedNumbers: state.isMatchedNumbers
})
const mapDispatchToProps = dispatch => ({
nextTurnPlayer: () => {
dispatch({ type: NEXT_TURN_PLAYER })
},
showDialog: message => {
dispatch({ type: MESSAGE, message })
},
resetGame: () => {
dispatch({ type: RESET_GAME })
},
})
export default connect(mapStateToProps, mapDispatchToProps)(Game)
How would I be able to achieve this?
I thought about adding setTimeOut inside mapDispatchToProps, but I feel that it is not the right way.

There is no reason you can't use redux-thunk for this, in fact, on the official documentation for it, they even use a setTimeout as a way to emulate that async nature.
function showDialogAsync() {
return dispatch => {
setTimeout(() => {
dispatch(showDialog());
}, 5000);
};
}
You can utilise this simple pattern where ever you want, be it for resetting the game or displaying dialogues.
Repo with Documentation

Redux-saga
Great for more complex async behaviour than redux-thunk

Related

Calling an action within another one in Redux using TypeScript

Suppose that I have these 2 actions (as an example) for "creating category" and "loading all categories". I need to load all categories every time I create a new category successfully, so, I need to call "loadAllCategories" action within "createCategory". I usually do that like this while using TypeScript with Redux:
// Loading all categories
export const loadAllCategories = () => async (dispatch: Dispatch) => {
try {
// omitted for brevity
dispatch<ILoadAntdTreeSelectCompatibleCategoriesAction>( {
type: TaxonomyActionTypes.LOAD_ANTD_TREESELECT_COMPATIBLE_CATEGORIES,
payload: {
catTreeSelectLoading: false,
catTreeSelectRegistry
}
})
} catch (error) {
// omitted for brevity
}
}
// Creating a category
export const createCategory = (taxonomy: ITaxonomy) => async (dispatch: Dispatch) => {
try {
await agent.Taxonomies.create(taxonomy);
dispatch<any>(loadAllCategories()); <--- Dispatching above action within this one
dispatch<ICreateCategoryAction>({
type: TaxonomyActionTypes.CREATE_CATEGORY,
payload: {
loadingInitial: false
},
})
} catch (error) {
// omitted for brevity
}
}
I wanted to know, using dispatch with "any" type is the only way to call another action within the current one or there is a better way of doing that?
Could I use a more specific type instead of "any"?
Needless to say without using dispatch(action), just by calling the action's name it doesn't change the state so we have to use dispatch.
What is the best practice for doing that?
There is a simpler way to do this when you create a category lets say you use an API for that, make that API return the value you added, in response, then add that category to category list in Redux. use the following function in the reducer.
const addToList = (oldList:any, doc:any) => {
let newList:any = oldList;
newList.push(doc);
return newList;
}
and in the reducer function call it like
case TaxonomyActionTypes.CREATE_CATEGORY:
return { ...state, categories: addToList(state.categories, action.payload) }
Edit
The Answer to your question is
dispatch<Array>
Example
interface Category {
name: String,
}
let x:Array<Category>

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.

Is this Flux architecture?

This is how I've been organizing my React / Redux projects because it's how they did it in the tutorial I followed. Is this what Flux architecture is and if not what would you call this?
First I call a function in my component that's defined in the action file
This function does an ajax request to get info from an API
Then it fires off an action creator
The reducer listens for action creators and once one is detected it executes a function that updates the state
Here's an example:
Component
class List extends React.Component {
componentDidMount() {
this.props.getPosts();
}
// etc...
}
const mapStateToProps = state => {
return {
posts: state.posts
};
};
const mapDispatchToProps = dispatch => {
return {
getPosts: () => dispatch(actions.getPosts())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(List);
Action
const postsLoaded = posts => {
return {
type: actionTypes.POSTS_LOADED,
posts: posts
};
};
export const getPosts = () => {
return dispatch => {
axios
.get('http://api.something.com/posts', {})
.then(response => {
dispatch(postsLoaded(response.posts));
})
.catch(e => {
console.error(e);
});
};
};
Reducer
const setPosts = (prevState, action) => {
return {
...prevState,
...action.posts
};
};
const reducer = (prevState = {}, action) => {
switch (action.type) {
case actionTypes.POSTS_LOADED:
return setPosts(prevState, action);
default:
return prevState;
}
};
export default reducer;
Flux is a design pattern. Redux is one of several libraries that implement Flux. The intent is NOT for you to "use Redux to implement Flux", but rather "use the Flux pattern by using Redux".
You can find a much better description in the docs below, but in simplest terms, the Flux architecture is based on a unidirectional data flow, which means that each piece receives data from one place, and outputs changes to another. The intent of this pattern is to eliminate "spaghetti code", where various parts of the application pass data in many different directions, which can eventually become very difficult to trace.
In other words, your components are the "View" in the diagram below.
Redux store gives state to your component
Your component renders something, and when a user performs an action, the component creates an action and gives it to the dispatcher.
The dispatcher finds the reducer that can handle your action, and gives the result to the store.
And the cycle repeats.
This image and an in-depth overview of Flux can be found here.

Use async/await in React component

Is it a good practice to use async/await directly in React component then store the result in the store ? For example:
class User extends Component {
render() {
return <div>{this.props.user.name}</div>
}
componentWillMount() {
this.getUser();
}
async getUser() {
try {
const user = await userAction.get();
this.props.storeUser(user);
} catch (err) {}
}
}
const state2props = (state) => ({
user: state.User.user
});
const dispatch2props = dispatch => ({
storeUser: (user) => dispatch(userReducer.store(user)),
});
export default connect(state2props, dispatch2props)(User);
It seems more flexible than the classic react/redux pattern.
Yes, you can use async/await in react components. It's not a bad practice
It's just architecture question.
There are lots of ways to implement async logic in applications. In small application you can implement async logic in react components. When your application grow up, you will get some issues like duplicated code (for example you want to fetch user in several react components), code composition and code splitting.
You can use redux-thunk https://github.com/gaearon/redux-thunk, redux-saga https://github.com/redux-saga/redux-saga, redux-logic https://github.com/jeffbski/redux-logic or any other solution.
Moreover you can create your own custom middleware such as:
const reactions = {};
export const addReactions = signals => {
reactions = { ...reactions, ...signals };
};
export default (signalMiddleware = ({ getState, dispatch }) => next => action => {
if (!action.signal) {
return next(action);
}
if (!reactions[action.signal]) {
throw new Error(`There is no handler for ${action.signal} signal`);
}
reactions[action.signal]({ getState, dispatch, payload: action.payload });
});
Such middleware allows you implement business logic into separate layer. For example:
import { addReactions } from './path/to/signalMiddleware';
// Describe your Actions for middleware:
const fetchUser = (id) => ({
signal: 'FETCH_USER',
payload: id
});
const anotherAction = () => ({
signal: 'SOME_ANOTHER_ACTION_WITH_USER',
});
// Describe your business logic using middleware:
addReactions({
FETCH_USER: async ({dispatch}, {id}) => {
const user = await fetcher.get(id);
dispatch({
type: 'RECEIVE_USER',
payload: user,
});
},
SOME_ANOTHER_ACTION_WITH_USER: () => {
// do some awesone job :)
}
})
So our react component could be:
class User extends Component {
render() {
return <div>{this.props.user.name}</div>
}
componentDidMount() {
this.props.dispatch(fetchUser(123));
}
}
export default connect(state2props, dispatch2props)(User);
Now you can divide your application architecture into 3 layer:
1) View — react-components
2) Business logic — your middleware
3) Data logic — your reducer
Between view and business layer we use specific actions with signal field and without type field.
Between business and data logic we use actions with type field.
This architecture allows you to get a strict separation of layers. This architecture is useful in big applications.
In small application it's ok to use redux-thunk or write async logic in react-components.

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!

Resources