Reload redux on save - reactjs

I am working on a React.JS project, based on the react-boilerplate templates. Unfortunately, react-boilerplate only has an example of loading remote data into redux. There is no save example.
I was able to write the save actions, reducer and saga, no problem. It is all pretty standard stuff. However, one issue holding me back, which I was unable to resolve - reloading the store after saving.
I did the below:
const mapDispatchToProps = dispatch => {
return {
loadEvent: eventId => dispatch(loadEvent(eventId)),
saveEvent: values => {
const event = dispatch(saveEvent(values))
return dispatch(loadEvent(event.id || values.id))
}
}
}
I want the above code to work as a promise - reload the event by id after save finished to completion.
It is not working like I need it to. I get load invoked, yet there is no new data in the store.

You should create some xxxx_REQUEST and xxxx_SUCCESS|FAILURE action types to each request (not important it is saving or not).
I don't know you are redux-saga or redux-thunk but after your request fetch finished, you should dispatch xxxx_SUCCESS|FAILURE then in your reducer, get data and store it on you store.
Then you could use a selector to get data from redux store in your component.

I resolved this issue by sticking everything inside my saga as below:
try {
// Call our request helper (see 'utils/request')
const createdEvent = yield call(request, requestURL, {
method: !isNaN(id) && id !== undefined && id !== null ? 'PUT' : 'POST',
body: JSON.stringify(event)
})
yield put(eventLoaded(createdEvent, id))
yield put(loadEvent(createdEvent['id']))
} catch (err) {
yield put(eventLoadingError(err))
}
Now, the thing works as I need it to.

Related

Why is my state not properly rendered in reactjs?

In my project I use ReactJS in combination with redux and firebase.
Creating a thunk to make async calls to firebase and store the data in redux.
When I'm fetching my files from firebase storage.
Using this method:
try {
let list = [];
await storage
.ref()
.child(path)
.listAll()
.then((res) => {
res.items.forEach((item) => {
storage
.ref()
.child(item.fullPath)
.getDownloadURL()
.then((urlRes) => {
list.push({
name: item.name,
url: urlRes,
});
});
});
});
dispatch(getFileActionSuccess(list));
This method works as intended.
It returns an array of files with their url to view/download them.
The problem is when I try to access this object in my state, it returns an empty array.
Even though when checking using Redux Devtools, I can clearly see that after the list was dispatched. And I could see the correct data.
Devtools image
Note: this is not the real code but a representation
function page() {
getFiles();
<filesList/>
}
function filesList() {
const files = useSelector((state) => state.files, _.isEqual);
console.log(files);
return (..insert render..);
}
But when logging the files. It shows an empty array at first. But when expanding it, it shows the correct data. But it doesn't render it. As I don't understand why it isn't showing like it is supposed to I no longer know what to do and how to fix this.
Simply fetch the data on component mount and component update, and update your state accordingly.
If you’re using React Hooks, you can use React.useState() and give it a dependency. In this case the dependency would be the part of your state which will update upon completion of your HTTP request.

React multiple http calls when navigating the application using the URL

I have a component which has filtering, searching and pagination capabilities. What I'm trying is to create a queryString and attach to the URL such that I can later copy and paste it in another browser so that I can reuse the filters.
To extract the query params from the URL I'm using the useLocation hook
const useQuery = () => new URLSearchParams(useLocation().search);
const pageNo = useQuery().get('page') ?? 1;
I'm using the useEffect hook to track for changes of the page query parameter value, and dispatch an action which will update the pageNo in the state object of my reducer.
React.useEffect(() => {
dispatch({
type: actionDescriptor.CHANGE_PAGE,
payload: pageNo
});
}, [pageNo]);
I have another useEffect hook which handles the fetch of the data, and gets triggered when the pageNo changes. I'm using the useNavigate to create and navigate to the new location if the http call was successful
const nav = useNavigate();
React.useEffect(() => {
(async function get() {
const response = // make http call and get response
if (response.status === 200) {
dispatch({
type: actionDescriptor.FETCH_SUCCESS,
payload: {
data: response.data['value'],
}
});
nav (`/data?page=${state.pageNo}`);
}
/// handle error
}
})();
}, [state.pageNo, state.pageSize, state.filter]);
When I'm navigating using the UI, selecting a different page for example, all works well, there is a single http call, the browser url is updated as expected (e.g. localhost/mydata?page=2). If however I'm copying the url and paste it in another window, it makes two http calls, and renders the dom twice. What might be the cause for this?
my guess is due to the parameters you are listening on state.pageNo, state.pageSize, state.filter. I'm assuming all of these are null/empty at the beginning of your app. Once you copied and pasted the url, two of these parameters will change which will cause the useEffect to be called twice.
put in a console.log in the useEffect to confirm that. Once that's confirmed, I would re-examine the list of parameters to see if you need to listen to all of them.
I would take a look at the pageNo. It looks like it might be changing from default value to 2 since you have 2 useEffects probably firing for the same render.

Can a redux action both dispatch and return something in React

I have set up my redux properly: store, actions, reducers, I've mapped state to props etc and I am using redux thunk.
This is what my current actions look like:
I normally use my actions like this inside my components and it works just fine:
this.props.addCars(newCar)
Sometimes I want to add a new car and in the next line do something with the new data using this.props.cars
This.props.cars does not show the car I just recently added though and using a Promise, or await like this does not work
await this.props.addCars(newCar)
So I was wondering if there is a way to make my actions dispatch and return the data response.
You need to wait for the response from axios and then dispatch the results plus return. Something like this.
export async function addCars(car){
var result = await axios.post('http://localhost:8000/api/cars', car)
.then((resp)=>
{
return resp.data
}
dispatch({type: ADD_CAR, payload: result});
return result;
}

What is the best approach of writing redux actions that need data from other actions

I have made some research about possible ways to do it, but I can't find one that uses the same architecture like the one in the app I'm working on. For instance, React docs say that we should have a method which makes the HTTP request and then calls actions in different points (when request starts, when response is received, etc). But we have another approach. We use an action which makes the HTTP call and then dispatches the result. To be more precise, my use case is this:
// action to get resource A
getResourceA () {
return dispatch => {
const result = await axios.get('someLink');
dispatch({
type: GET_RES_A,
payload: result
});
};
}
// another action which needs data from resource A
getSomethingElseByIdFromA (aId) {
return async dispatch => {
const result = await axiosClient.get(`someLink/${aId}`);
dispatch({
type: GET_SOMETHING_BY_ID_FROM_A,
payload: result
});
};
}
As stated, the second action needs data from the first one.
Now, I know of two ways of doing this:
return the result from the first action
getResourceA () {
return async dispatch => {
const result = await axios.get('someLink');
dispatch({
type: GET_RES_A,
payload: result
});
return result;
};
}
// and then, when using it, inside a container
async foo () {
const {
// these two props are mapped to the getResourceA and
// getSomethingElseByIdFromA actions
dispatchGetResourceA,
dispatchGetSomethingElseByIdFromA
} = this.props;
const aRes = await dispatchGetResourceA();
// now aRes contains the resource from the server, but it has not
// passed through the redux store yet. It's raw data
dispatchGetSomethingElseByIdFromA(aRes.id);
}
However, the project I'm working on right now wants the data to go through the store first - in case it must be modified - and only after that, it can be used. This brought me to the 2nd way of doing things:
make an "aggregate" service and use the getState method to access the state after the action is completed.
aggregateAction () {
return await (dispatch, getState) => {
await dispatch(getResourceA());
const { aRes } = getState();
dispatch(getSomethingElseByIdFromA(aRes.id));
};
}
And afterward simply call this action in the container.
I am wondering if the second way is all right. I feel it's not nice to have things in the redux store just for the sake of accessing them throughout actions. If that's the case, what would be a better approach for this problem?
I think having/using an Epic from redux-observable would be the best fit for your use case. It would let the actions go throughout your reducers first (unlike the mentioned above approach) before handling them in the SAME logic. Also using a stream of actions will let you manipulate the data throughout its flow and you will not have to store things unnecessary. Reactive programming and the observable pattern itself has some great advantages when it comes to async operations, a better option then redux-thunk, sagas etc imo.
I would take a look at using custom midleware (https://redux.js.org/advanced/middleware). Using middleware can make this kind of thing easier to achieve.
Something like :
import {GET_RESOURCE_A, GET_RESOURCE_B, GET_RESOURCE_A_SUCCESS, GET_RESOURCE_A_ERROR } from '../actions/actionTypes'
const actionTypes = [GET_RESOURCE_A, GET_RESOURCE_B, GET_RESOURCE_A_SUCCESS, GET_RESOURCE_A_ERROR ]
export default ({dispatch, getState}) => {
return next => action => {
if (!action.type || !actionTypes.includes(action.type)) {
return next(action)
}
if(action.type === GET_RESOURCE_A){
try{
// here you can getState() to look at current state object
// dispatch multiple actions like GET_RESOURCE_B and/ or
// GET_RESOURCE_A_SUCCESS
// make other api calls etc....
// you don't have to keep stuff in global state you don't
//want to you could have a varaiable here to do it
}
catch (e){
} dispatch({type:GET_RESOURCE_A_ERROR , payload: 'error'})
}
}
}

chaining multiple async dispatch in Redux

I am trying to chain multiple actions together in the following fashion:
A. post user data to database
B. use posted data to query Elasticsearch for results
(I do A and B in parallel)
B1. with results from ES, query original database for results from two tables
B2. navigate to new page and update UI
I am using thunks right now to reason about my code, but I also found this async pattern to be extremely verbose:
export function fetchRecipes(request) {
return function(dispatch) {
dispatch(requestRecipes(request))
return fetch(url)
.then(response => response.json())
.then(json => dispatch(receiveRecipes(request, json))
)
}
}
this, along with "requestRecipes" and "receiveRecipes" as other action creators seems like quite a bit just to make one async call. (a request, a receive, and a fetch function)
summary: when you're chaining 2-3 async actions whose outputs depend on each other (I need to promisify when possible), is there a more efficient means of doing so without writing 3 functions for each async call?
I figure there had to be a way. I'm pattern matching off of the Redux docs and soon became very overwhelmed with the functions I was creating
thanks a lot for the feedback!
You can use redux-saga instead of redux-thunk to achieve this more easily. redux-saga lets you describe your work using generators and is easier to reason about.
The first step is to describe how you pass your data to redux without worrying about services or async stuff.
Actions
// actions.js
function createRequestTypes(base) {
return {
REQUEST: base + "_REQUEST",
SUCCESS: base + "_SUCCESS",
FAILURE: base + "_FAILURE",
}
}
// Create lifecycle types on `RECIPES`
export const RECIPES = createRequestTypes("RECIPES")
// Create related actions
export const recipes = {
// Notify the intent to fetch recipes
request: request => ({type: RECIPES.REQUEST, request})
// Send the response
success: response => ({type: RECIPES.SUCCESS, response})
// Send the error
error: error => ({type: RECIPES.FAILURE, error})
}
Reducer
// reducer.js
import * as actions from "./actions"
// This reducer handles all recipes
export default (state = [], action) => {
switch (action.type) {
case actions.RECIPES.SUCCESS:
// Replace current state
return [...action.response]
case actions.RECIPES.FAILURE:
// Clear state on error
return []
default:
return state
}
}
Services
We also need the recipes API. When using redux-saga the simplest way to declare a service is to creating a (pure) function which reads the request as argument and returns a Promise.
// api.js
const url = "https://YOUR_ENPOINT";
export function fetchRecipes(request) {
return fetch(url).then(response => response.json())
}
Now we need to wire actions and services. This is where redux-saga come in play.
// saga.js
import {call, fork, put, take} from "redux-saga/effects"
import * as actions from "./actions"
import * as api from "./api"
function* watchFetchRecipes() {
while (true) {
// Wait for `RECIPES.REQUEST` actions and extract the `request` payload
const {request} = yield take(actions.RECIPES.REQUEST)
try {
// Fetch the recipes
const recipes = yield call(api.fetchRecipes(request))
// Send a new action to notify the UI
yield put(actions.fetchRecipes.success(recipes))
} catch (e) {
// Notify the UI that something went wrong
yield put(actions.fetchRecipes.error(e))
}
}
}
function* rootSaga() {
yield [
fork(watchFetchRecipes)
]
}
And that's it! Whenever a component will send a RECIPES.REQUEST action, the saga will hook up and handle the async workflow.
dispatch(recipes.request(req))
What's awesome with redux-saga is that you can easily chain async effects and dispatch actions during the workflow.
Based on your description, the only time you actually update your UI is right at the end of all these asynchronous operations (B1).
If you don't use the results from the preceding async calls to change your application state / update your UI, what is the benefit of having these fine-grained actions?
Of course there are things like "loading / request started" and "finished loading / request stopped", but it seems to me, that in your case, you could just do the chained async calls outside of redux (in some kind of API-layer) and only use one action.
This action dispatches a "REQUEST_STARTED", then calls the API-layer, which does the DB-calls and elasticsearch request etc., and then dispatches either "REQUEST_SUCCESS" or "REQUEST_FAILURE", based on the result of the promise, which will give you the data you need to update your UI.
This way, the state in redux only concerns itself with ONE side-effect, instead of the implementation details of your chained calls. Also, your action gets a lot simpler, because it just handles the results of one async call.

Resources