Subscribing to a redux action in a react component - reactjs

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

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.

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

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)

next.js mapStateToProps, mapDispatchToProps and getInitialProps

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.

React Redux - update child in state array

So I have an object in Redux store called currentAccount. It has a child array called groups, which has a child array called tasks.
Now, I could update one task real easily in Angular by more-or-less going:
function updateTask(task){
http.post('xyz/updateTask', function(updatedTask){
task = updatedTask;
});
}
...and that'd work just fine.
In React, I can dispatch the data to actions, post it to the API with Axios, but then... how do I find and update the old task?
I could:
Get the server to return the entire currentAccount object again, then turn it into a JSON string and back (forcing Redux to re-render the whole tree),
...or do a forEach within a forEach to find the task by its ID, then replace it (although I'm not sure Redux would pick up on the change)
But both of these strike me as totally insane. Is there a simpler way, or is that just sort of how React works?
Apologies if this is a dumb question, I'm not really sure how else to word it, haha.
Once you have your response from your http call, dispatch an action that updates the store. You would need access to dispatch in your component, so you would use mapDispatchToProps to provide the functionality.
Here is a great starter tutorial for Redux by the creator, Dan Abramov.
This code example should help you.
// I'm using this stateless functional component to
// render the presentational view
const Task = ({ label, info }) => (
<div className="task">
<h3{ label }</h3>
<p{ info }</p>
</div>
);
// this is the class that receives props from connect
// and where we render our tasks from
class Tasks extends PureComponent {
// once your component is mounted, get your
// http data and then disptach update action
componentDidMount() {
// using axios here but you can use any http library
axios.post( "/some_url", task )
.then( ( response ) => {
// get update provided by mapDispatchToProps
// and use it to update tasks with response.data
const { update } = this.props;
update( response.data );
})
.catch( ( error ) ) => {
// handle errors
}
}
render() {
// destructure tasks from props
const { tasks } = this.props;
// render tasks from tasks and pass along props
// if there are no tasks in the store, return a loading indicator
return (
<div className="tasks">
{
tasks ? tasks.map(( task ) => {
return <Task
label={ task.label }
info={ task.info }
/>
}) :
<div className="loading">Loading...</div>
}
</div>
);
}
}
// this will provide the Tasks component with props.task
const mapStateToProps = ( state ) => {
return {
tasks: state.tasks
}
}
// this will provide the Tasks component with props.update
const mapDispatchToProps = ( dispatch ) => {
return {
update: ( tasks ) => {
dispatch({
type: "UPDATE_TASKS",
tasks
});
}
}
}
// this connects Task to the store giving it props according to your
// mapStateToProps and mapDispatchToProps functions
export default Task = connect( mapStateToProps, mapDispatchToProps )( Task );
You will need a reducer that handles the "UPDATE_TASK" action. The reducer updates the store, and considering the component is connected, it will receive new props with the updated store value and the tasks will updated in the DOM.
EDIT: To address the reducers, here is an additional example.
import { combineReducers } from "redux";
const tasks = ( state = [], action ) => {
switch( action.type ) {
case "UPDATE_TASKS":
// this will make state.tasks = action.tasks which
// you dispatched from your .then method of the http call
return action.tasks;
default:
return state;
}
};
const other = ( state = {}, action ) => {
...
}
const combinedReducers = combineReducers({
tasks,
other
});
const store = createStore(
combinedReducers/*,
persisted state,
enhancers*/
);
/*
The above setup will produce an initial state tree as follows:
{
tasks: [ ],
other: { }
}
After your http call, when you dispatch the action with the tasks in
the response, your state would look like
{
tasks: [ ...tasks you updated ],
other: { }
}
*/
If anyone else gets stuck on this I think I've sorted it out.
I just pass along all the indexes of the task's parents, then use 'dotPoints' to set the state like this:
dotProp.set(state, `currentProject.groups.${action.groupIndex}.tasks.${action.taskIndex}`, action.task);
Seems like a fairly neat solution so far.

Correct way to pre-load component data in react+redux

I do not know the correct way to pre-load data from API for a component to use.
I have written a stateless component which should render the data:
import React, { PropTypes } from 'react';
const DepartmentsList = ({ departments }) => {
const listItems = departments.map((department) => (
<li>{department.title}</li>
));
return (
<ul>
{listItems}
</ul>
);
};
DepartmentsList.propTypes = {
departments: PropTypes.array.isRequired
};
export default DepartmentsList;
And I have an action which will retreive data from the API:
import { getDepartments } from '../api/timetable';
export const REQUEST_DEPARTMENTS = 'REQUEST_DEPARTMENTS';
export const RECEIVE_DEPARTMENTS = 'RECEIVE_DEPARTMENTS';
const requestDepartments = () => ({ type: REQUEST_DEPARTMENTS });
const receiveDepartments = (departments) => ({ type: RECEIVE_DEPARTMENTS, departments });
export function fetchDepartments() {
return dispatch => {
dispatch(requestDepartments);
getDepartments()
.then(departments => dispatch(
receiveDepartments(departments)
))
.catch(console.log);
};
}
Now I think I have a few options to preload departments that are required for the list. I could use redux-thunk and mapDispatchToProps to inject fetchDepartments to the stateless component and implement componentWillMount or similar lifecycle method, to load data - but then I don't need to pass the list via props, as the component would always load data for himself, and I don't want that, because whenever a new component is created the data is fetched from api instead of store...
Another advice I've seen is to use getComponent function from react-router, and retreive all data before returning the component, however, I am not sure if it's the correct redux way, as I don't see how to use redux-thunk there, and logic kind of seems littered all accross the files, when it's the data required for only one component.
This leaves me with the only seemingly ok option to load data in container component's lifecycle methods, but I want to know what is considered the best practice for what I want to do.
The most 'redux-like' way of handling the pre-loading of data would be to fire off the asynchronous action in the lifecycle method (probably componentWillMount) of a Higher Order Component that wraps your app. However, you will not use the results of the API call directly in that component - it needs to be handled with a reducer that puts it into your app store. This will require you to use some sort of a thunk middleware to handle the asynchronous action. Then you will use mapStateToProps to simply pass it down to the component that renders the data.
Higher Order Component:
const mapStateToProps = (state) => {
return {
departments: state.departments
};
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
getDepartments: actionCreators.fetchDepartments
});
}
class App extends Component {
componentWillMount() {
this.props.getDepartments();
}
render() {
return <DepartmentsList departments={this.props.departments} />
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
reducers:
export function departments(state = [], action) {
switch(action.type) {
case 'RECEIVE_DEPARTMENTS':
return action.departments;
}
}

Resources