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.
Related
After some researches, I found some questions on stackoverflow about what I am trying to achieve, however, I don't feel that these questions and their answers gives me the "answers" or the "directions" i am looking for..
Note: I am pretty new to react even if I already made 2 projects and implemented redux into one of them. However, I ain't new at all in C# or in Go, even less in C. Based on my experience, I am just used to some architectures and I would like to reproduce one of them.
Here is a pretyy good schema from a similar question of mine:
Situation:
So let say I have pages that contains Components. I want these pages/compoments to display some stuff. One of my functionnality is to discover a map and for that, when the client moves, he gets new parts from my API. However, I don't wanna ask the server to give me the new parts and the ones I discovered already.
My idea about it would be to use a service MapService.js. This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones (concat).
However, I have to be logged for this, so I would like an ApiService.js that would store my authentication data and automatically put them in each of my requests.
Based on what I said, we would have something as:
Page -> Component -> Service -> API
From this, the API response would be gotten by my service, handled, then returned to the component. Handled means (data added to the previous then all returned)
I saw on internet one question that was referring "MVCS" (Model View Controller Service) pattern and I think I am looking for something as but I am not sure about how to implement it in ReactJs.
Redux seems to be something that you put all around and everywhere in your solution. What I would like is to use it as a "repository" let say, to be able to manage it from a service and not from the component itself. However, a service should be a single instance shared across the app and I don't know if something such as dependency injection could be the solution in ReactJS
Feel free to ask any edit if you need more details :)
Thanks for your help !
Here is a minimal example of Redux middleware usage. Usually, redux devs are using libraries (that give you a middleware) to have access to more appropriate APIs.
Redux middleware are chained, so each middleware can call the next middleware. The first middleware of the chain is called every time dispatch function (you can have it from react-redux connect) is called. In a middleware, if there is no next middleware it is the reducers that will be called. The next middleware can be call asynchronously after receiving an action. (Redux docs will still be better than my explainations).
In my example there is a catService that provide function that call rest API. Your services can be anything (a Class instance or a singleton for example). Usually in React/Redux stack, devs don't use object oriented development.
If a component dispatch getCat(123), the catMiddleware will be called (synchronously). Then requestGetCat will be called with the id 123. When the promise returned by requestGetCat will be resolved a setCat action will be send through the reducers to update the redux state. Once the redux state is done, the component listening for cats items object will be update too (triggering a rerender).
That can look very complexe, but in fact, it is very scalable and convenient.
// catService.js
// return a promise that return a cat object
const requestGetCat = id =>
fetch(`www.catcat.com/api/cat/${id}`)
.then(response => response.json())
// catTypes.js
export const GET_CAT = 'GET_CAT'
export const SET_CAT = 'SET_CAT'
// catActions.js
export const getCat = id => ({
type: GET_CAT,
id
})
export const setCat = (cat, id) => ({
type: SET_CAT,
id,
cat
})
// catReducer.js
const initialState = {
items: {}
}
const catReducer = (state = initialState, action) => {
if (action.type === SET_CAT) {
return {
items: {
...state.items,
[action.id]: action.cat
}
}
}
}
// catMiddleware.js
const handleGetCat = (next, action) => {
requestGetCat(action.id)
.then(cat => next(setCat(cat, action.id)))
// after retrieving the cat send an action to the reducers (or next middleware if it exist)
}
const actionHandlers = {
[GET_CAT]: handleGetCat
}
// receive every actions passing by redux (if not blocked)
// store: { dispatch, getState }
// next: next middleware or reducers (that set redux state)
// action: a redux action (dispatched) with at least type property
const catMiddleware = store => next => action => {
const handler = actionHandlers[action.type]
if (handler) {
handler(next, action)
} else {
// passing the action to the next middleware (or reducer - when there is no next middleware)
next(action)
}
}
// you have to apply your middleware
// and your reducer (see redux doc)
This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones
This is something I've wanted to do in the past, but never implemented a solution for.
The issue is that you essentially want to "cross the streams"..
In Redux there are two separate streams, ie dispatch an action to update the store, and read data from the store. Each of these are executed separately from a component. Combined, they can be used in a cycle by calling an action to load data into the store which triggers an update of the component which then reads from the store.
Basically you can't have non-component code that reads from the store, and if the data is missing, fires an action to load the data, then returns the data.
Thinking about it now, I'm wondering if the way to do this without adding logic to your view component is to wrap it in a component (HOC) that provides the logic.
The HOC will check the state for the location specified in the props. If it doesn't find it, it will dispatch an action to fetch it and render a loading display. When the state is updated with the new location it will update and render the wrapped component.
You could optionally always render the wrapped component and have it cope with the missing location until it is updated with the location set..
untested brain-dump below
loader HOC:
import React, { useEffect } from "react";
import actions from "./actions";
function withLocationLoader(Component) {
const Wrapper = function ({ location, locations, loadLocation, ...props }) {
useEffect(() => {
if (!locations[location]) {
loadLocation(location);
}
}, [locations]);
if (locations[location]) {
return <Component locations={locations} {...props} />;
}
return <div>Loading...</div>;
}
const mapStateToProps = (state, ownProps) => {
return { locations: state.locations };
};
const mapActionsToProps = {
loadLocation: actions.loadLocation,
};
return connect(
mapStateToProps,
mapActionsToProps
)(Wrapper);
}
export { withLoader };
component:
function MyBareComponent({ locations }) {
return <div>{JSON.stringify(locations)}</div>;
}
const MyComponent = withLocationLoader(MyBareComponent);
export { MyComponent };
actions: (utilising redux-thunk middleware)
function setLocation(location, data) {
return { type: "SET_LOCATION", payload: { location, data } };
}
export function loadLocation(location) {
return dispatch =>
Promise.resolve({ geoData: "" }) // mock api request
.then(data => dispatch(setLocation(location, data)));
}
So I have a goal to keep my MongoDB database in sync with the current state of my application. For example, whenever my state changes (like the name of a project, for example), that change will be saved into the database after the action is dispatched and reducer is signaled and the state is changed.
As an example, I have a state object with the following structure:
const INITIAL_STATE = {
name: "",
triggered: false,
isUnique: false
};
So when a user changes the name of the project, that name change will be first be done by the state itself and after the state changes, there is a call to the DB to just change the name of the project.
To simulate a DB change, I used localStorage to get across the same purpose:
function handshake() {
return ({ dispatch, getState }) => next => action => {
// send action to next Middleware
next(action);
const db = JSON.parse(localStorage.getItem("temporaryDB"));
const presentState = getCurrentState(getState());
if(db) {
const areStatesEqual = isEqual(db, presentState);
if(!areStatesEqual) return localStorage.setItem("temporaryDB", JSON.stringify(presentState));
return;
}
localStorage.setItem("temporaryDB", JSON.stringify(presentState));
};
}
export default function configureStore(initialState = {}) {
return createStore(
rootReducer,
applyMiddleware(handshake())
)
}
getCurrentState is just a utility function that gets the current state. Regardless, my logic is to use a Redux middleware and look for changes between the database object and the store object. If the objects are different in any way, I would replace the DB object with the Redux store, keeping everything in sync.
It's a naive approach and I'm looking to see if there is a better way of reaching the goal I have of keeping the state and database in sync throughout the entire application's lifecylce.
I think you just need to subscribe to the store and listen to all the changes happening there.
e.g. two function to load/save state to keep it sync
export const loadState = () => {/*the DB logic*/}
export const saveState= () => {/*the DB logic*/}
then you can compose your redux with these function and initial the state with calling the loadState()
import { loadState, saveState } from "where theyare"
const syncWithDBstate= loadState();
const store = createStore(
rootReducer,
syncWithDBstate,
composeWithDevTools(applyMiddleware(thunk)) // here I am suing the chrome devtool extention
);
store.subscribe(() => {
saveState(store.getState());
});
Basic setup:
1) App is React and Redux,
2) App is served by a front facing NGINX serving static files like html, pictures and of course the app itself. It also forwards all relevant requests (web sockets and/or AJAX) to the back end (phoenix/elixir).
3) Users are required to authenticate. I'm using redux-oidc library, which is client side only and it works fine.
4) After user logs on is when I get hazy on what to do next.
Question(s):
1) I can't send the state along with the first request because I don't know who the user is and, thus, don't know which state to send. Meanwhile application is already booted (empty store created, login component displayed),
2) After user logs on I can't show anything (like user specific nav bar, timeline , mailbox) I have to saturate the store and let react do its job. What approach should I take?
3) Server rendering is out because a) I'm not using Node and rendering react components using chosen framework is messy and complicated at best and b) I won't be able to export the app to NGinx since it only serves static assets and there is no server logic run there. I could, theoretically, get rid of NGinx, have server based login on the API server and send down HTML along with JSON state which could be used to render the app on the client. However, NGinx does not only serve static assets but also load balances few instances and, thus, getting rid of it is not something I want to do.
Any advice would be appreciated.
Hydrating the state after the store was created, can be achieved by creating a main reducer that can bypass the top level reducers, and replace the whole state.
Reducers are functions that get the current state, combine it with the payload of an action, and return a new state. Usually the main reducer is a combination of all top reducers using combineReducers, and the state is the combination of state pieces returned by the top level reducers.
However, the main reducer can react to actions directly. If the main reducer receives a certain action (hydrate), instead of calling the combined reducers, it returns the action's payload (the saved state). Other actions are passed to the combined reducers.
const mainReducer = (state = {}, action) =>
action.type === 'hydrate' ?
action.payload // hydrate the state
:
reducers(state, action); // create new state by using combined reducers
Working example:
const { combineReducers, createStore } = Redux;
const people = (state = [], action) => action.type === 'people' ? [...state, action.payload] : state;
const items = (state = [], action) => action.type === 'items' ? [...state, action.payload] : state;
const reducers = combineReducers({
people,
items
});
const mainReducer = (state = {}, action) => action.type === 'hydrate' ? action.payload : reducers(state, action);
const store = createStore(mainReducer);
store.subscribe(() => console.log(store.getState()));
store.dispatch({ type: 'people', payload: 5 });
store.dispatch({ type: 'items', payload: 'green' });
store.dispatch({ type: 'hydrate', payload: {
people: [20, 30, 50, 100],
items: ['green', 'yellow', 'red']
}});
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
Even though the accepted answer might do the trick, I think that what you are chasing is an anti-pattern, after all.
If you are doing client rendering and handling authentication client side, the only thing you should send to your client, in my opinion, is a <Spinner/>, with zero preloaded state.
Once you are on the client, initialise your store, do your authentication, and decide if you are going to fetch data and render the authenticated version of the page, or if user is not authenticated yet, show them the login form. Everything from this point on, should be handled client side.
Here is what Redux has to say about it:
https://redux.js.org/usage/server-rendering
So, if during the initial render you got "Schrödinger" user (might be authenticated or not), the only thing you can safely assume to show is a spinner.
ANOTHER OPTION
If you really need to get new preloaded data from the server on multiple requests (that's kind of what happens in NextJS apps).
If you are going to pre-render every page (and get new state that on every page change), you can do what NextJS suggests you to do:
They basically re-initialise the store and merge the new data every time it receives new preloaded data from the server.
Here is the example:
https://github.com/vercel/next.js/tree/canary/examples/with-redux-thunk
And the main part of their code:
import { useMemo } from 'react'
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunkMiddleware from 'redux-thunk'
import reducers from './reducers'
let store
function initStore(initialState) {
return createStore(
reducers,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware))
)
}
export const initializeStore = (preloadedState) => {
let _store = store ?? initStore(preloadedState)
// After navigating to a page with an initial Redux state, merge that state
// with the current state in the store, and create a new store
if (preloadedState && store) {
_store = initStore({
...store.getState(),
...preloadedState,
})
// Reset the current store
store = undefined
}
// For SSG and SSR always create a new store
if (typeof window === 'undefined') return _store
// Create the store once in the client
if (!store) store = _store
return _store
}
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState])
return store
}
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.
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.