How to warn react component when redux saga put a success action? - reactjs

I am using the redux action pattern (REQUEST, SUCCESS, FAILURE) along with redux saga. I made a watcher and worker saga just like that:
import axios from 'axios';
import { put, call, takeEvery } from 'redux-saga/effects';
import * as actionTypes from 'constants/actionTypes';
import * as actions from 'actions/candidates';
const { REQUEST } = actionTypes;
// each entity defines 3 creators { request, success, failure }
const { fetchCandidatesActionCreators, addCandidateActionCreators } = actions;
const getList = () => axios.get('/api/v1/candidate/');
// Watcher saga that spawns new tasks
function* watchRequestCandidates() {
yield takeEvery(actionTypes.CANDIDATES[REQUEST], fetchCandidatesAsync);
}
// Worker saga that performs the task
function* fetchCandidatesAsync() {
try {
const { data } = yield call(getList);
yield put(fetchCandidatesActionCreators.success(data.data));
} catch (error) {
yield put(fetchCandidatesActionCreators.failure(error));
}
}
const postCandidate = params => axios.post('/api/v1/candidate/', params).then(response => response.data).catch(error => { throw error.response || error.request || error; });
// Watcher saga that spawns new tasks
function* watchAddCandidate() {
yield takeEvery(actionTypes.ADD_CANDIDATE[REQUEST], AddCandidateAsync);
}
// Worker saga that performs the task
function* AddCandidateAsync({ payload }) {
try {
const result = yield call(postCandidate, payload);
yield put(addCandidateActionCreators.success(result.data));
} catch (error) {
yield put(addCandidateActionCreators.failure(error));
}
}
export default {
watchRequestCandidates,
fetchCandidatesAsync,
watchAddCandidate,
AddCandidateAsync,
};
My reducer has two flags: isLoading and success. Both flags change based on the request, success and failure actions.
The problem is that I want my component to render different things when the success action is put on the redux state. I want to warn the component every time a _success action happens!
The flags that I have work well on the first time, but then I want them to reset when the component mounts or a user clicks a button because my component is a form, and I want the user to post many forms to the server.
What is the best practice for that?
The only thing I could think of was to create a _RESET action that would be called when the user clicks the button to fill up other form and when the component mounts, but I don't know if this is a good practice.

You need to assign a higher order component, also called a Container, that connects the store with your component. When usgin a selector, your component will automatically update if that part of the state changes and passes that part of the state as a prop to your component. (as defined in dspatchstateToProps)
Down below i have a Exmaple component that select status from the redux state, and passes it as prop for Exmaple.
in example i can render different div elements with text based on the status shown in my store.
Good luck!
import { connect } from 'react-redux'
const ExampleComponent = ({ status }) => {
return (
<div>
{status === 'SUCCESS' ? (<div>yaay</div>) : (<div>oh no...</div>)}
</div>
)
}
const mapStateToProps = state => {
return {
status: state.status
}
}
const mapDispatchToProps = dispatch => {
return {}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ExampleComponent)

Related

How to get the new state with Redux-Saga?

I'm trying to get the new state to which the getCart() generator function returns me in reducer, but the state is coming "late".
The state I need comes only after the second click of the button.
NOTE: The error on the console I am forcing is an action.
import { call, put, select, all, takeLatest } from 'redux-saga/effects';
import { TYPES } from './reducer';
import { getCart, getCartSuccess, getCartFail } from './actions';
import API from 'services/JsonServerAPI';
export function* getCartList() {
try {
const response = yield call(API.get, '/2cart');
yield put(getCartSuccess(response.data));
} catch (error) {
yield put(
getCartFail(error.response ? error.response.statusText : error.message)
);
}
}
export function* addToCart({ id }) {
yield put(getCart());
yield select(({ CartReducer }) => {
console.log(CartReducer);
});
console.log(id);
}
// prettier-ignore
export default all([
takeLatest(TYPES.GET, getCartList),
takeLatest(TYPES.ADD, addToCart)
]);
Since getCartList performs async actions you will need some way to wait for those to complete in the addToCart before logging.
One option is to call the getCartList directly from the addToCart saga without dispatching a redux action - this may not be preferable if you have other middleware that relies on TYPES.GET being dispatched.
export function* addToCart({ id }) {
// call the `getCartList` saga directly and wait for it to finish before continuing
yield call(getCartList);
yield select(({ CartReducer }) => {
console.log(CartReducer);
});
console.log(id);
}
The other option is take on the list of actions that will be dispatched once the getCartList saga completes:
export function* addToCart({ id }) {
yield put(getCart());
// wait until one of the success or failure action is dispatched, sub with the proper types
yield take([TYPES.GET_SUCCESS, TYPES.GET_FAILURE]);
yield select(({ CartReducer }) => {
console.log(CartReducer);
});
console.log(id);
}
This has some potential tradeoffs as well - you will need to make sure the action list in take stays up to date with all possible ending types that getCartList can put and you need to make sure you keep using takeLatest (vs say takeEvery) to trigger addToCart so you don't end up with multiple concurrent sagas that could fulfill the take clause.

How to wait for a saga to finish calling an api to execute a next function?

I have a question regarding the use of sagas.
I have a button that when clicked, triggers a function that calls an action:
Component.js
onClickChainIdentifier = (event) => {
//action called
this.props.getChains();
//next function to be called
this.teste();
}
}
Action.js
export function getChains(){
return {
type: GET_CHAINS,
}
}
When this action is dispatched, it fires a constant GET_CHAINS, which calls a saga:
Saga.js
export function* getAllChains() {
const requestURL = process.env.PATH_API.GET_CHAINS;
try {
const response = yield call(requestGet, requestURL);
yield put(getChainsSuccess(response));
} catch (err) {
yield put(getChainsError(err));
}
}
export default function* sagasApp() {
yield [
fork( takeLatest, GET_CHAINS, getAllChains ),
]
}
I would like that after the api return (of success or error), I could call the this.teste function that is inside the component.
How do I make this happen?
Thanks in advance for your help.
You could pass a callback to your getAllChains function:
onClickChainIdentifier = (event) => {
this.props.getChains(() => {
this.teste();
});
}
export function* getAllChains(callback) {
const requestURL = process.env.PATH_API.GET_CHAINS;
try {
const response = yield call(requestGet, requestURL);
yield put(getChainsSuccess(response));
if (callback) {
callback();
}
} catch (err) {
yield put(getChainsError(err));
}
}
You can use flags in order to control when and if your components should render. This is a common solution for rendering a fallback UI (e.g: a spinner or a text) in order to wait until an async process (saga, thunk, API service etc) is finished and the component has all it needs to render itself.
Check the solution I have posted here, you can visit this CodeSandBox which shows how you can use flags in order to solve it.
As jank pointed out, you can test component's state in the lifecycle methods and call a function when some condition is true. For example leveraging jank's example:
componentDidUpdate (prevProps) {
if (this.props.pending && !prevProps.pending) {
this.props.test()
}
}
Will call test every time the pending prop is changed from false to true. The test function can have side effects like fetching from server or using some browser API. Same functionality can be achieved using the newer useEffect of the Hooks API.

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.

Redux saga how to stop action from further propagating

I'm new to Redux Saga. I want to stop an action from further propagating. I am implementing row-level auto-saving mechanism. I use saga to detect row switch action, and then submit row changes and insert current row change action. codes like this:
// action-types.js
export const
SWITCH_ROW='SW_ROW',
CHANGE_CUR_ROW='CHG_CUR_ROW';
// actions.js
import {SWITCH_ROW,CHANGE_CUR_ROW} from './action-types'
export const switchRow=(oldRow,newRow)=>({type:SWITCH_ROW,oldRow,newRow})
export const changeRow=(row)=>({type:CHANGE_CUR_ROW,row})
// component.js
class MyComponent extends Component{
switchRow=(row)=>{
var oldRow=this.props.curRow;
this.props.dispatc(switchRow(oldRow,row));
}
render(){
...
{/* click on row */}
<div onClick={()=>this.switchRow(row)}>...</div>
...
}
}
// sagas.js
import {SWITCH_ROW} from './action-types'
import {changeRow} from './actions'
function* switchRow({oldRow,newRow}){
// Here I want to stop propagating SWITCH_ROW action further
// because this action is only designed to give saga a intervention
// point but not to be handled in reducer. I want a statement like
// below:
// stopPropogate();
if(oldRow && oldRow.modified===true){
yield call(svc.submit, oldRow);
}
yield put(changeRow(newRow))
}
export default function*(){
yield takeEvery(SWITCH_ROW,switchRow)
}
I know I can just ignore the SWITCH_ROW action in reducer. But, I think it's better if there is as least round trip as possible in program. Any suggests?
After more reading about Redux middle-ware, I think it's better to use a middle-ware to approach this goal.
At first, I renamed all type names of special actions for saga making them all starting with SAGA_. And then I use a Redux middle-ware to identify them and swallow them, and then those special actions can't reach reducer any more. Here is the codes:
// glutton.js
const glutton = () => next => action => {
if (!action.type.startsWith('SAGA_')) return next(action);
}
// store.js
...
const store = createStore(rootReducer, applyMiddleware(sagaMiddleWare, glutton, logger));
Lets say you are in a file which is not redux component, i mean you don't have access to dispatch , so i think throwing an error would be nice, and here is the code to catch the exception anywhere:
export default function autoRestart(generator) {
return function* autoRestarting(...args) {
while (true) {
try {
yield call(generator, ...args);
} catch (e) {
console.log(e);
yield put({ type: ReduxStates.Error });
}
}
}
};
all your sagas, need to implement this base function.

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

Resources