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.
Related
I have an icon that when clicked it triggers a function that calls an API and then dispatch an action to remove a blog post from the state. But the problem is that my UI does not re-render. However, if I refresh my browser the post that I deleted is no longer there and my store matches my state.
Here is my function that calls an API and then dispatch an action:
export function deletePost(postID) {
return (dispatch) => {
fetch(`${url}/posts/${postID}`, { method: 'DELETE', headers})
.then((response) => response.json())
.then((postID) => dispatch(removePost(postID)))
.catch((error)=>{console.log('dispatch error',error)});
};
Here is my action:
export function removePost ( postID ) {
return {
type: REMOVE_POST,
postID,
}
}
And here is my reducer:
function posts (state = {}, action) {
switch (action.type) {
case REMOVE_POST:
return [
...state.filter((post)=>post.id!==action.postID)
];
default:
return state
}
}
Now when I simply dispatch an action without calling an API
export function deletePost(postID) {
return (dispatch) => {
dispatch(removePost(postID));
}
My state is correctly updated but of course my redux store is not. When I do the calling of API before dispatching an action as shown earlier, there is also no error coming from the console log.
What could be the problem here? I am very new to ReactJS and can't find a solution yet to this problem after many tries.
Also, as a note, I am using redux-thunk in this project.
I have a few questions, but first: I think the problem is here:
[
...state.filter((post)=>post.id!==action.postID)
]
What is the state's shape? Is it state = [post1, post2, ...]? I can see the initial state is {}, so I find it weird to be calling state.filter and not state.posts.filter or whatever here.
The other might be problem, is with post.id !== action.postID, maybe the received ID is an number type, and maybe the local id is a string? Maybe the other way around?
I just need a little help with regards to fetching of data through an api
currently I have this code in my container
fetchAll: () => {
Promise.resolve(token.getToken()).then( (response) => {
let obj = {
token_type: response.data.token_type,
access_token: response.data.access_token
}
apiFetch("http://dev.apiserver.com/api/all",{
"method": "GET",
"headers": {
"Authorization": `${obj.token_type} ${obj.access_token}`,
"userId": 1
}
})
.then( (res) => {
return res.json();
}).then( (json) => {
dispatch({
type: 'FETCH_ALL',
payload: json
})
}).catch(function(err) {
console.log(err)
})
})
}
I'm calling the above function from componentWillMount(), I can successfully log the result in my console
here is my reducer code
const allReducer = (state: Immut = initialState, action: { type: string, payload: any }) => {
switch (action.type) {
case FETCH_ALL:
let newState = state
let payload = action.payload
newState = Immutable.fromJS(payload)
console.log(newState)
return newState
}
}
the problem is that it doesn't update the state or re-render the components
Since your reducer is getting called, the issue is likely not in the AJAX call or the update part of the Redux cycle, but in how your component connects to the state. Changes in the state should reactively trigger a re-render of the component, but for this to happen, the props of the component need to change. First, a few steps on how you might debug this issue, before some suggestions of possible causes, and suggested best practices.
Redux Dev Tools
Install Redux Dev Tools on Chrome. DevTools allow you to view a history of actions, and the changes each action induced to the Redux state.
First, use Redux DevTools to ensure that the shape of your Redux state is what you expect, even before triggering any action / AJAX call.
Then, select your AJAX fetch success (FETCH_ALL) action in Redux DevTools and, looking at the diff, see that the state changes as you expect.
Manual debugging
Try putting a debug value in the initialState of your reducer, and ensure it renders on the component.
See that the props of the component are updated when state changes. Put a console.log for this.props and nextProps in the life-cycle method componentWillReceiveProps(nextProps).
Issues to look for
Your component may be looking for the data in a wrong place of the state. The structure of your store follows the composition of your reducers. For example, the following puts the output of myDataReducer under app.db:
const rootReducer = combineReducers({
app: combineReducers({ db: myDataReducer }),
});
You seem to be using Immutable.js. Vanilla Redux treats the state as a POJO. You should either A) use a JavaScript object as the root of your state, or B) use a special combineReducers from redux-immutable.
Best practices
Trigger data fetching from componentDidMount instead of componentWillMount.
Trigger all side effects with an Redux action, instead of chaining promises directly in component code. Effect those changes using Redux middleware, like redux-saga. In your case, you could use redux-promise, create an action like
const action = ({type: FETCH_DATA, payload: axios.get(ENDPOINT, params)});
The redux-promise middleware handles the resolving, so that your reducer will only see the resolved value in the payload.
You're dispatching type 'FETCH_ALL' and catching case FETCH_ALL. One is a variable and one is a string.
I have a React Native application with Redux actions and reducers. I'm using the redux-thunk dispatch for waiting the asyncron calls. There is an action in my application:
export const getObjects = (id, page) => {
return (dispatch) => {
axios.get(`URL`)
.then(response => {
dispatch({ type: OBJECTS, payload: response });
}).catch(error => {
throw new Error(`Error: objects -> ${error}`);
});
};
};
That's working properly, but sometimes the user click on the back button before the action finished the request, and I must cancel it. How can I do it in a separated action? I read this, but I didn't find any option in axios for abort. I read about the axios cancellation, but it's create a cancel method on the function scope and I can't return, because the the JS don't support multiple returns.
What is the best way to cancel axios request in an other Redux action?
I would recommend using something like RxJS + Redux Observables which provides you with cancellable observables.
This solution requires a little bit of learning, but I believe it's a much more elegant way to handle asynchronous action dispatching versus redux-thunk which is only a partial solution to the problem.
I suggest watching Jay Phelps introduction video which may help you understand better the solution I'm about to propose.
A redux-observable epic enables you to dispatch actions to your store while using RxJS Observable functionalities. As you can see below the .takeUntil() operator lets you piggyback onto the ajax observable and stop it if elsewhere in your application the action MY_STOPPING_ACTION is dispatched which could be for instance a route change action that was dispatched by react-router-redux for example:
import { Observable } from 'rxjs';
const GET_OBJECTS = 'GET_OBJECTS';
const GET_OBJECTS_SUCCESS = 'GET_OBJECTS_SUCCESS';
const GET_OBJECTS_ERROR = 'GET_OBJECTS_ERROR';
const MY_STOPPING_ACTION = 'MY_STOPPING_ACTION';
function getObjects(id) {
return {
type: GET_OBJECTS,
id,
};
}
function getObjectsSuccess(data) {
return {
type: GET_OBJECTS_SUCCESS,
data,
};
}
function getObjectsError(error) {
return {
type: GET_OBJECTS_ERROR,
data,
};
}
const getObjectsEpic = (action$, store) = action$
.ofType(GET_OBJECTS)
.switchMap(action => Observable.ajax({
url: `http://example.com?id=${action.id}`,
})
.map(response => getObjectsSuccess(response))
.catch(error => getObjectsError(error))
.takeUntil(MY_STOPPING_ACTION)
);
The situation
I have an onboarding scenario where the user goes through a step-by-step onboarding. I want to manage the client side state of the user's progress with Redux. The synchronization between the server and the client is already implemented in Relay, but I still need a Redux store for client-side state management. As such, problems arise with synchronizing the Relay-/Redux-Store.
What I'm doing right now is to wrap my React component with Redux and then with Relay:
// OnboardProgressView.js
// ...
// wrap React component with Redux
const mapStateToProps = (state) => {
return {
onboardProgress: state.onboardProgress,
}
}
const ReduxContainer = connect(
mapStateToProps,
)(OnboardProgressView)
// this is only for convenience of access of the Relay data
const MappedOnboardProgressView = mapProps({
params: (props) => props.params,
user: (props) => props.viewer.user,
})(ReduxContainer)
// wrap Redux component with Relay
export default Relay.createContainer(MappedGettingStartedView, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
user {
userId
onboardProgressStep
}
# more stuff ...
}
`,
},
})
My progress
I have found ways to accomplish different operations as follows:
Initialization of the Redux store with server data
I am initializing the Redux state right after creating the store with an asynchronous raw Relay query. To make that possible I am also using the redux-thunk middleware. Redux initiates a request to Relay which queries the server. Visual representation (an arrow denotes data flow, the order of elements reflects the 'call order'): Redux <= Relay <= Server
// app.js
const store = createStore(reducer, applyMiddleware(thunk))
store.dispatch(fetchOnboardProgress())
// onboardProgress.js
export function fetchOnboardProgress () {
return function (dispatch) {
var query = Relay.createQuery(Relay.QL`
query {
viewer {
user {
id
onboardProgress
}
}
}`, {})
return new Promise(function (resolve, reject) {
Relay.Store.primeCache({query}, ({done, error}) => {
if (done) {
const data = Relay.Store.readQuery(query)[0]
dispatch(update(data.user.onboardProgress, data.user.id))
resolve()
} else if (error) {
reject(Error('Error when fetching onboardProgress'))
}
})
})
}
}
Updating data on server when dispatching a Redux action
Redux => Relay => Server
To have consistent state changes, when the user progresses through the onboarding process, I fire a Redux action that will also asynchronously do a Relay mutation. I am also using redux-thunk for this purpose.
function nextStep () {
return function (dispatch, getState) {
const currentStep = getState().onboardProgress.step
const currentStepIndex = OnboardProgress.steps.indexOf(currentStep)
const nextStep = OnboardProgress.steps[currentStepIndex + 1]
const userId = getState().onboardProgress._userId
return _updateReduxAndRelay(dispatch, nextStep, userId)
}
}
function _updateReduxAndRelay (dispatch, step, userId) {
return new Promise((resolve, reject) => {
Relay.Store.commitUpdate(new UpdateUserMutation({
userId: userId,
onboardProgressStep: step,
}), {
onSuccess: () => {
dispatch(update(step, userId))
resolve()
},
onFailure: reject,
})
})
}
export function update (step, userId) {
const payload = {onboardProgress: new OnboardProgress({step, userId})}
return {type: UPDATE, payload}
}
Open Problems
I still haven't find an approach to the following situation:
Updating the Redux Store when the Relay Store updates
Changes to data on the server might have external sources, that are not triggered by a user action in our app. With Relay we can solve this with forceFetching or polling. A Relay query looks like this: Relay <= Server. I'd like to additionally have this data flow: Relay => Redux when external data changes.
Another possible reason for the need to update the Redux store with new data is when we want to synchronize data that is deeply nested in the Relay store, or part of a complex query.
For example, think of the count of comments to a blog post. When a user is posting a new comment, another component showing the comment count should update as well.
If we manage this information in Redux, we need a way to trigger a Redux action when a Relay query comes with new information. I am not aware of such a callback, or another solution to this situation.
My Questions
In this context, I have those questions:
What can I improve in my existing approaches? Is there something I did that is highly dangerous/leads to inconsistencies? (see My Progress)
How can I manage to sync the Redux store when for some reason the Relay store is being updated. I am looking for a React component life cycle method or a Relay callback where I can then send a Redux action to the Redux store. (see Open Problems)
RelayNetworkLayer is what you should use to sync the redux store with the relay one as it allows you to subscribe to everything that happens there. I'll update this post later if anything else comes to mind.
From the discussion here it seems that the state of Redux reducers should be persisted in a database.
How does something like user authentication works in this instance?
Wouldn't a new state object be created to replace the previous state in the database for every user (and their application state) created and edited?
Would using all of this data on the front end and constantly updating the state in the database be performant?
Edit: I've created an example Redux auth project that also happens to exemplify universal Redux, and realtime updating with Redux, Socket.io and RethinkDB.
From the discussion here it seems that the state of Redux reducers should be persisted in a database.
To persist the state or not, it's likely not a concern of Redux at all. It's more up to application logic.
If something happens in an application, like data upload to server, obviously you need to save state (or a slice of the state to a server).
Since network calls are asynchronous, but Redux is synchronous - you need to introduce additional middleware, as redux-thunk or redux-promise.
As sign-up example, you likely need that actions,
export function creatingAccount() {
return { type: 'CREATING_ACCOUNT' };
}
export function accountCreated(account) {
return { type: 'ACCOUNT_CREATED', payload: account };
}
export function accountCreatingFailed(error) {
return { type: 'ACCOUNT_CREATING_FAILED', payload: error };
}
export function createAccount(data, redirectParam) {
return (dispatch) => {
dispatch(creatingAccount());
const url = config.apiUrl + '/auth/signup';
fetch(url).post({ body: data })
.then(account => {
dispatch(accountCreated(account));
})
.catch(err => {
dispatch(accountCreatingFailed(err));
});
};
}
Some portion of state, e.g. user object after authorization, might be stored to localStore and re-hydrated on next application run.
Those are valid concerns. Using localStorage to persist state on the frontend might be a better strategy. You can implement this using middleware, for example:
import {createStore, compose, applyMiddleware} from 'redux';
const localStorageMiddleware = ({getState}) => {
return (next) => (action) => {
const result = next(action);
localStorage.setItem('applicationState', JSON.stringify(
getState()
));
return result;
};
};
const store = compose(
applyMiddleware(
localStorageMiddleware
)
)(createStore)(
reducer,
JSON.parse(localStorage.getItem('applicationState'))
)
If you're concerned about the enemy accessing the user's laptop and stealing credentials from it you could persist state to the backend when the user leaves the page (Navigator.sendBeacon() might be helpful here) & store it in the session.