I am using React with Redux and Sagas to create a card game simulator. I need to create a function which updates the redux state, waits for the new state and runs again. I'm not sure if / how something like that is possible in React Redux land. Below is a simplified version of what I'm trying to achieve:
function endTurn() {
// do something
}
function playerTurn(playerHand) {
const decision = getPlayerDecision(playerHand);
updatePlayerHand(decision); // this dispatches an action to update redux state
// If player chose to 'Stand', the game ends.
// Otherwise, it's player's turn again.
if(decision === 'Stand') {
endTurn();
} else {
// here I need the updated hand, how do I get it?
playerTurn(updatedHand);
}
}
An obvious solution would be to place this logic in 'componentWillReceiveProps' but it doesn't seem right and I'm sure it would end up being very buggy. Intuitively, it feels like a job for Redux Saga but I haven't been able to find anything relevant in the docs. Any suggestions?
SOLUTION: Krasimir's answer with the use of yield select
pointed me in the right direction. Below is a simplified version of the code I ended up with:
import { put, takeEvery, select } from 'redux-saga/effects';
function* playPlayerTurn() {
const playerHand = yield select(getPlayerHand);
const decision = getPlayerDecision(playerHand);
// some action that executes the decision
// the action results in a change to playerHand
yield put({
type: `PLAY_PLAYER_DECISION`,
decision,
});
// If player chose to 'Stand', the turn ends.
// Otherwise, it's player's turn again.
if(decision === 'Stand') {
console.log('PLAYER OVER');
} else {
console.log('PLAYER AGAIN');
yield put({
type: `PLAY_PLAYER_TURN`,
});
}
}
export function* playerTurnWatcher() {
yield takeEvery(`PLAY_PLAYER_TURN`, playPlayerTurn);
}
Essentially, I'm able to call the saga recursively
You need to do something - as written this will call sleep (returning a promise) and then immediately throw it away! If you yield a call() then redux-saga will wait for the promise to resolve, e.g.: yield call(sleep, 2) . The best solution is in the answer above - using the delay utility function.
First we find the index of the item in the State array by using findIndex. In the picture above, x+1 is the index of the item needs updating. Once the updated object is created, the quickest way to update is using the slice method and spread operator (…).
take(pattern) Creates an Effect description that instructs the middleware to wait for a specified action on the Store. The Generator is suspended until an action that matches pattern is dispatched. The result of yield take(pattern) is an action object being dispatched.
The fact that the Redux state changes predictably opens up a lot of debugging possibilities. For example, using time travel makes it possible to travel back and forth between different states instead of having to reload the whole app in order to get back to the same place.
Let's assume that you dispatch an action PLAYER_HAND
where the payload is the decision
.
import { select, takeLatest } from 'redux-saga/effects';
const waitForPlayerTurn = function * () {
takeLatest(PLAYER_HAND, function * (decision) {
// at this point the store contains the right `decision`
if(decision === 'Stand') {
endTurn();
} else {
playerTurn(yield select(getUpdateHand)); // <-- getUpdateHand is a selector
}
});
}
And of course you have to run the waitForPlayerTurn
saga.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With