can i chain redux promsie action in react component? - reactjs

I have a redux action which get data from server my action is like this
export const getFakeData = () => (dispatch) => {
return dispatch({
type: 'GET_FAKE_DATA',
payload: {
promise: axios.get('/test'),
}
});
};
my reducer is like this
const reducer = (INITIAL_STATE, {
[GET_FAKE_DATA]: {
PENDING: () => ({
isLoading: true,
}),
FULFILLED: (state, action) => {
const { data } = action.payload.data;
return {
...state,
data,
error: false,
isLoading: false,
};
},
REJECTED: () => ({
isLoading: false,
error: true
}),
});
I want to show success alert after my action sent, is below code breaks the principle of redux about one way flow?
this.props.getFakeData().then(() => {
this.setState({
showAlert: true
});
});

According to your use-case, it's perfectly fine to keep showAlert flag in the component's local state, instead of the Redux store.
Here's what Redux official documentation stands for:
Using local component state is fine. As a developer, it is your job to
determine what kinds of state make up your application, and where each
piece of state should live. Find a balance that works for you, and go
with it.
Some common rules of thumb for determining what kind of data should be
put into Redux:
Do other parts of the application care about this data?
Do you need to be able to create further derived data based on this original data?
Is the same data being used to drive multiple components?
Is there value to you in being able to restore this state to a given point in time (ie, time travel debugging)?
Do you want to cache the data (ie, use what's in state if it's already there instead of re-requesting it)?

Related

How to save redux state per user transaction

I have a redux react web app booking court app and I want to manage/store redux state based on the user unique id that is coming from the server when the user initials the booking. Currently my initial state looks like this:
const initialState = {
payload: [],
loading: false,
}
I have a reducer like
const courtReducer = (state =initialState, action: any) => {
switch(action.type) {
case MAKE_BOOKING:
return {
...state,
loading,
payload: action.data
};
other action...
}
}
I am not sure if I am making sense.
If you are trying to figure out how to store state, maybe the "Sending Data with Thunks" section of the Redux documentation will help. It's part of a tutorial which should leave you with a pretty good idea of how to integrate async API calls with reducers.

React Redux - How to do a proper loading screen using React and Redux on url change

I am working on a web app that uses React + Redux. However, I am struggling with the loading screens. Basically, my initial state for the redux store is this.
const initialState = {
project: {},
projects: [],
customers: [],
vendors: [],
customer: {},
vendor: {},
loading: false
};
An example of one of my reducers is this
const fetchSalesProject = (state, action) => {
return updateObject(state, {
project: action.payload,
loading: false
});
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.FETCH_SALES_PROJECT: return fetchSalesProject(state, action);
default:
return state;
where updateObject is this
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
}
here are my actions (axiosInstance is just a custom addon to axios to include the necessary headers for authentication, but works exactly the same like a normal axios api call)
export const fetchSalesProject = (id) => (dispatch) => {
axiosInstance
.get(`/sales-project/detail/${id}/`)
.then((res) => {
dispatch({
type: FETCH_SALES_PROJECT,
payload: res.data,
});
})
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
export const showLoader = (area) => (dispatch) => {
dispatch({
type: SHOW_LOADER,
area: area ? area : true
});
};
where the param area is just in the case where loading is meant to be rendered only on a specific area on the page (not relevant for this question hence when showLoader is called, no params would be passed in and hence the area would be set to true for this question)
this is my component
componentDidMount() {
this.props.showLoader()
this.props.fetchSalesProject(this.props.match.params.id)
};
this is my mapStateToProps
const mapStateToProps = state => ({
project: state.api.project,
loading: state.api.loading
})
this is my conditional rendering
render() {
return (
!this.props.loading ?
{... whatever content within my page }
: { ...loading screen }
Basically, the issue I am facing is that, when i first load this component, there would be an error, as my this.props.project would be an empty {} (due to initialState). The reason why is that before the showLoader action is dispatched fully by componentDidMount, the component renders, and hence since this.props.loading is still set to the initialState of false, the conditional rendering passes through and the this.props.project would be rendered (however since it is empty, the various keys that I try to access within the rendering would be undefined hence throwing an error).
I tried various methods to try to overcome this, but none seem like an optimum solution. For example, I have tried to set the initialState of loading to an arbitary number, like eg. 0, and only render the page when this.props.loading === false, which is only after the data is fetched. However, the issue arises when I go onto a new page, like for example, another component called CustomerDetail. When that component is mounted, this.props.loading would be false and not 0, as it was previously manipulated by the previous component that I was on (SalesProjectDetail). Hence, that same error would come up as the conditional rendering passes through before any data was actually fetched.
Another method I tried was to use component states to handle the conditional rendering. For example, I only set a component state of done : true only after all the dispatches are completed. However, I felt like it was not good practice to mix component states and a state management system like Redux, and hence was hoping to see if there are any solutions to my problem that uses Redux only.
I am looking for a solution to my problem, which is to only render the contents only after the data has been fetched, and before it is fetched, a loading screen should be rendered. All help is appreciated, and I am new to React and especially Redux, so do guide me on the correct path if I am misguided on how this problem should be approached. Thanks all in advance!
there can be more ways to do it. But to go ahead with your scheme of local state...
I think there is no problem if you use local state to keep track of load complete or underway.
You can have a local state variable as you already said.
Before dispatch set that variable to loading=true then use async/await to fetch data and afterwards set loading back to false.
I think it will work for you.

How to share/split a Redux Store between multiple generic components?

I have a generic component called "VendorResults". I am passing a string prop down to each of these generic components such as "Microsoft", "Apple", etc.
<ScrollView>
<SearchResults/>
<VendorResults vendor={"microsoft"}/>
<VendorResults vendor={"oracle"}/>
</ScrollView>
Within this generic component, I am passing the vendor prop as a parameter to my Redux-Thunk actions as such:
componentDidMount() {
const {vendor} = this.props;
this.props.getVendorInformation(vendor);
}
An API call kicks off, and Thunk actions are dispatched. The data eventually makes its way to the Reducer and store. However, When I have more than one generic Vendor component, whichever async call finishes last, appears to take precedent over all the others. For example, if oracle finishes loading last, the microsoft component's state will change and show oracle data.
Actions
export function getVendorInformation(vendor) {
const url = `${VENDOR_URL}api/search/${vendor}`;
return dispatch => {
dispatch(getVendor());
fetch(url)
.then(blob => blob.json())
.then(data => {
dispatch(getVendorSuccess(data))
})
.catch(e => {
console.log(e);
dispatch(getVendorError(e.message))
});
};
Reducer
export default function(state=initialState, action){
switch (action.type){
case FETCHING_VENDOR: return {payload:[], fetching: true}
case FETCH_VENDOR_SUCCESS: return {payload: action.payload.data}
case VENDOR_ERROR: return {payload:[], error: true, ...state}
}
return state;
}
My Question:
I want to maintain this pattern of generic/reusable Vendor components - I do not want a new component for each vendor. The same goes for actions/reducers; unique vendor actions/reducers would get messy.
How can I share/split/partition a single Redux store into vendor specific chunks to maintain seperation of state but still benefit from one flow. Thank you!!
You need to pass vendor to reducer via action and re-do structure of your state. If list of vendors is pre-determined and not very long, it probably will be less messy to just create separate actions/reducers.
Otherwise, you need to have nested reducer:
const supportedActions = [FETCHING_VENDOR, FETCH_VENDOR_SUCCESS, VENDOR_ERROR];
const initialVendorState = {data:[], fetching: false, error: false};
const vendorReducer = (state = initialVendorState, action) => {
switch (action.type){
case FETCHING_VENDOR: return {data:[], fetching: true}
case FETCH_VENDOR_SUCCESS: return {data: action.payload.data}
case VENDOR_ERROR: return {...state, data:[], error: true}
}
return state;
}
const reducer = (state = {}, action) => {
if (supportedActions.includes(action.type)) {
const s = {};
s[action.payload.vendor] = vendorReducer(state[action.payload.vendor], action);
return {
...state,
...s
};
}
return state
}
export default reducer;
And your action creators should take vendor as parameter and pass it to reducer:
const fetchVendorSuccess = (vendor, data) => ({
type: FETCH_VENDOR_SUCCESS,
payload: {
vendor,
data
}
});
In your connect function you will need to use smth like data: (state[vendor] || {}).data to avoid errors if state does not have any info about that vendor
However, When I have more than one generic Vendor component, whichever async call finishes last, appears to take precedent over all the others. For example, if oracle finishes loading last, the microsoft component's state will change and show oracle data.
You are seeing Oracle data because after fetching the vendor data you are overwriting the entire vendor state with the latest array of vendor items.
case FETCH_VENDOR_SUCCESS: return {payload: action.payload.data}
To avoid this, you would need to merge the previous state with the new state.
Solution depends on what each vendor response looks like. As Gennady suggested, you can use an object and make each vendor a property on the object.
Using a flat array to store all the different vendor items presents challenges. How would you determine if a vendor has already been fetched?
To avoid overwriting the previous vendor, you would need to merge the new state with previous state. E.g.
case FETCH_VENDOR_SUCCESS: return [...state.data, ...payload.data]

How can I watch for changes from reducer in react component?

I have react app, and I am using redux as a store. Along with redux i am using redux-thunk. For example, i have action getUsers that fetch all users and storing them in user reducer. Also, if there is some error while fetching them i store that error. My question is how to in react component named UsersOverwiew watch for changes happend in reducer, for example error, and show that error to user? I did it with useEffect hook, but is there better way?
User reducer
case GET_USERS_BEGIN:
return {
...state,
users: {
loading: true,
error: {},
data: []
}
};
case GET_USERS_SUCCESS:
return {
...state,
users: {
loading: false,
error: {},
data: action.users
}
};
case GET_USERS_FAILURE:
return {
...state,
users: {
...state.users,
loading: false,
error: action.error,
}
};
UserOverview component
// fetching users
useEffect(() =>{
getUsers();
}, []);
// watch for changes in user reducer
useEffect(() =>{
if(users.error){
// if error happend do something
}
}, [users]);
This is only small part of code, i have already everything connected, component and reducer, but i wanted to simplify it as much as i can.
You are doing it the correct way if you are using functional components. The above answer which suggests componentWillMount() requires class based components.
However doing it with useEffect is completely fine.

Redux: Opinions/examples of how to do backend persistence?

I am wondering how folks using Redux are approaching their backend persistence. Particularly, are you storing the "actions" in a database or are you only storing the last known state of the application?
If you are storing the actions, are you simply requesting them from the server, then replaying all of them when a given page loads? Couldn't this lead to some performance issues with a large scale app where there are lots of actions?
If you are storing just the "current state", how are you actually persisting this state at any given time as actions happen on a client?
Does anyone have some code examples of how they are connecting the redux reducers to backend storage apis?
I know this is a very "it depends on your app" type question, but I'm just pondering some ideas here and trying to get a feel for how this sort of "stateless" architecture could work in a full-stack sense.
Thanks everyone.
Definitely persist the state of your reducers!
If you persisted a sequence of actions instead, you wouldn't ever be able to modify your actions in your frontend without fiddling around inside your prod database.
Example: persist one reducer's state to a server
We'll start with three extra action types:
// actions: 'SAVE', 'SAVE_SUCCESS', 'SAVE_ERROR'
I use redux-thunk to do async server calls: it means that one action creator function can dispatch extra actions and inspect the current state.
The save action creator dispatches one action immediately (so that you can show a spinner, or disable a 'save' button in your UI). It then dispatches SAVE_SUCCESS or a SAVE_ERROR actions once the POST request has finished.
var actionCreators = {
save: () => {
return (dispatch, getState) => {
var currentState = getState();
var interestingBits = extractInterestingBitsFromState(currentState);
dispatch({type: 'SAVE'});
window.fetch(someUrl, {
method: 'POST',
body: JSON.stringify(interestingBits)
})
.then(checkStatus) // from https://github.com/github/fetch#handling-http-error-statuses
.then((response) => response.json())
.then((json) => dispatch actionCreators.saveSuccess(json.someResponseValue))
.catch((error) =>
console.error(error)
dispatch actionCreators.saveError(error)
);
}
},
saveSuccess: (someResponseValue) => return {type: 'SAVE_SUCCESS', someResponseValue},
saveError: (error) => return {type: 'SAVE_ERROR', error},
// other real actions here
};
(N.B. $.ajax would totally work in place of the window.fetch stuff, I just prefer not to load the whole of jQuery for one function!)
The reducer just keeps track of any outstanding server request.
function reducer(state, action) {
switch (action.type) {
case 'SAVE':
return Object.assign {}, state, {savePending: true, saveSucceeded: null, saveError: null}
break;
case 'SAVE_SUCCESS':
return Object.assign {}, state, {savePending: false, saveSucceeded: true, saveError: false}
break;
case 'SAVE_ERROR':
return Object.assign {}, state, {savePending: false, saveSucceeded: false, saveError: true}
break;
// real actions handled here
}
}
You'll probably want to do something with the someResponseValue that came back from the server - maybe it's an id of a newly created entity etc etc.
I hope this helps, it's worked nicely so far for me!
Definitely persist the actions!
This is only a counterexample, adding to Dan Fitch's comment in the previous answer.
If you persisted your state, you wouldn't ever be able to modify your state without altering columns and tables in your database. The state shows you only how things are now, you can't rebuild a previous state, and you won't know which facts had happened.
Example: persist an action to a server
Your action already is a "type" and a "payload", and that's probably all you need in an Event-Driven/Event-Sourcing architecture.
You can call your back-end and send the actions inside your actionCreator (see Dan Fox's answer).
Another alternative is to use a middleware to filter what actions you need to persist, and send them to your backend, and, optionally, dispatch new events to your store.
const persistenceActionTypes = ['CREATE_ORDER', 'UPDATE_PROFILE'];
// notPersistenceActionTypes = ['ADD_ITEM_TO_CART', 'REMOVE_ITEM_FROM_CART', 'NAVIGATE']
const persistenceMiddleware = store => dispatch => action => {
const result = dispatch(action);
if (persistenceActionTypes.indexOf(action.type) > -1) {
// or maybe you could filter by the payload. Ex:
// if (action.timestamp) {
sendToBackend(store, action);
}
return result;
}
const sendToBackend = (store, action) => {
const interestingBits = extractInterestingBitsFromAction(action);
// déjà vu
window.fetch(someUrl, {
method: 'POST',
body: JSON.stringify(interestingBits)
})
.then(checkStatus)
.then(response => response.json())
.then(json => {
store.dispatch(actionCreators.saveSuccess(json.someResponseValue));
})
.catch(error => {
console.error(error)
store.dispatch(actionCreators.saveError(error))
});
}
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
createStore(
yourReducer,
aPreloadedState,
applyMiddleware(thunk, persistenceMiddleware)
)
(You can also use a middleware to send current state to the backed. Call store.getState().)
Your app already knows how to transform actions into state with reducers, so you can also fetch actions from your backend too.

Resources