React Js - Combine Redux and Services layers - reactjs

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)));
}

Related

How to chain redux actions using returned result of the previous action?

I'm building an app in React Native, and using Redux with redux-persist to act as on device database.
The crux of the issue is, how do I return the result of a redux action, to then dispatch another action with this data? Read on for more specifics.
The user can create custom habit types. When doing so, I dispatch an action to create a habit type in the store (e.g. "running"). This action generates a new unique UUID for the habit type. I then want to add this newly created habit type to a routine (e.g. "morning routine"), and so I need to receive back the UUID of the habit type and call another dispatch to add it to the routine.
I'm using immer to make manipulating the state in my reducers simpler, and have this code (simplified example):
import produce from "immer";
const userReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_CUSTOM_HABIT_TYPE: {
return produce(state, draftState => {
const newHabitType = {
id: generateUuid(),
name,
};
draftState.customHabitTypes.push(newHabitType);
return draftState;
});
}
}
};
I'm then dispatching it in my component, like so (simplified):
dispatch({
type: ADD_CUSTOM_HABIT_TYPE,
name: "running",
});
How can I then say, after creating this new habit type, to dispatch another action and add it to my routine?
I've looked at redux-thunk and redux-saga, and spent hours reading about these and trying to get redux-thunk to work, but to no avail. I'm sure this must be simple, but I'm coming up blank, and so maybe others are too, hence this post.
A very simple solution would be to generate the unique id before dispatching the action.
Example
const newHabitType = {
id: generateUuid(),
name,
};
dispatch({
type: ADD_CUSTOM_HABIT_TYPE,
habit: newHabitType,
});
dispatch({
type: ADD_CUSTOM_HABIT_TO_ROUTINE,
habit: newHabitType.id,
});
Pros
You no longer need to chain actions per se, you just need to dispatch them in order.
This preserves one of the most important Redux guidelines: your reducer should not have any side effects (in your case, generating a random id). reference
Cons
If you create the new habits in multiple places, you will have to generate the unique ids in every place where you dispatch the action. This might lead to repeated code. The solution to this would be to encapsulate the whole logic for creating the habits to a single component and then reuse this component everywhere.
Actions do not return data per se, the are simply objects which mutate the store based on the rules defined in the reducer. Two possible solutions:
Option A, create a composite action.
const compositeAction = args => {
return dispatch => {
return someAsyncCall(args).then(response => {
dispatch(addCustomHabitat(response))
dispatch(followUpAction())
}
}
}
const addCustomHabitat = response => {
return {
type: "ADD_CUSTOM_HABIT_TYPE",
data: response
}
}
const followUpAction = () => {
...another action...
}
Option B, connect the results of the first action to the dispatching component through react-redux and pass them to the second action.
import {connect} from 'react-redux';
const MyReactComponent = props => {
dispatch(addCustomHabitatTypeAction());
if(props.customHabitatType !== undefined)
dispatch(followUpAction(props.customHabitatType());
return (
...JSX here...
);
}
const mapStateToProps = state => {
return {
customHabitatType: state.userReducer.customHabitatType
}
}
connect(mapStateToProps)(MyReactComponent);
I hope this helps! Please excuse my abbreviated code and let me know if you have any questions.

How to prevent UI freeze when calling API with axios

I am trying to load data when my component loads using componentDidMount. However calling the Redux action, making the call with axios seems to freeze the UI. When I have a form with 12 inputs and one makes an API call I would assume I can type in the other inputs and not have them freeze up on me.
I've tried reading some other posts on the subject but they are all a little different and everything I have tried doesn't seem to resolve the issue.
I am working on linux using React 16.8 (when using RN I use 55.4)
I have tried making my componentDidMount async as well as the redux-thunk action. It didn't seem to help anything, so I must be doing something wrong.
I tried doing the following with no success. Just using short form for what I tried. Actual code listed below.
async componentDidMount() {
await getTasks().then();
}
And I tried this
export const getTasks = () => (async (dispatch, getState) => {
return await axios.get(`${URL}`, AJAX_CONFIG).then();
}
Current Code:
Component.js
componentDidMount() {
const { userIntegrationSettings, getTasks } = this.props;
// Sync our list of external API tasks
if (!isEmpty(userIntegrationSettings)) {
getTasks(userIntegrationSettings.token)
// After we fetch our data from the API create a mapping we can use
.then((tasks) => {
Object.entries(tasks).forEach(([key, value]) => {
Object.assign(taskIdMapping, { [value.taskIdHuman]: key });
});
});
}
}
Action.js
export const getTasks = () => ((dispatch, getState) => {
const state = getState();
const { token } = state.integrations;
const URL = `${BASE_URL}/issues?fields=id,idReadable,summary,description`;
const AJAX_CONFIG = getAjaxHeaders(token);
dispatch(setIsFetchingTasks(true));
return axios.get(`${URL}`, AJAX_CONFIG)
.then((response) => {
if (!isEmpty(response.data)) {
response.data.forEach((task) => {
dispatch(addTask(task));
});
return response.data;
} else {
dispatch(setIsFetchingTasks(false));
}
})
.catch((error) => {
dispatch(setIsFetchingTasks(false));
errorConsoleDump(error);
errorHandler(error);
});
});
reducer.js
export default (state = defaultState, action) => {
switch (action.type) {
case ADD_TASK:
case UPDATE_TASK:
return update(state, {
byTaskId: { $merge: action.task },
isFetching: { $set: false }
});
default:
return state;
}
};
So in my answer what are you going to learn?
General data loading with Redux
Setting up a component lifecycle method such as componentDidMount()
Calling an action creator from componentDidMount()
Action creators run code to make an API request
API responding with data
Action creator returns an action with the fetched data on the payload property
Okay, so we know there are two ways to initialize state in a Reactjs application, we can either invoke a constructor(props) function or we can invoke component lifecycle methods. In this case, we are doing component lifecycle methods in what we can assume is a class-based function.
So instead of this:
async componentDidMount() {
await getTasks().then();
}
try this:
componentDidMount() {
this.props.fetchTasks();
}
So the action creators (fetchTasks()) state value becomes the components this.props.fetchTasks(). So we do call action creators from componentDidMount(), but not typically the way you were doing it.
The asynchronous operation is taking place inside of your action creator, not your componentDidMount() lifecycle method. The purpose of your componentDidMount() lifecycle method is to kick that action creator into action upon booting up the application.
So typically, components are generally responsible for fetching data via calling the action creator, but it's the action creator that makes the API request, so there is where you are having an asynchronous JavaScript operation taking place and it's there where you are going to be implementing ES7 async/await syntax.
So in other words it's not the component lifecycle method initiating the data fetching process, that is up to the action creator. The component lifecycle method is just calling the action creator that is initiating the data fetching process a.k.a. the asynchronous request.
To be clear, you are able to call this.props.fetchTasks() from your componentDidMount() lifecycle method after you have imported the action creator to your component like and you have imported the connect function like so:
import React from "react";
import { connect } from "react-redux";
import { fetchTasks } from "../actions";
You never provided the name of the component you are doing all this in, but at the bottom of that file you would need to do export default connect(null, { fetchTasks })(ComponentName)
I left the first argument as null because you have to pass mapStateToProps, but since I don't know if you have any, you can just pass null for now.
Instead of this:
export const getTasks = () => (async (dispatch, getState) => {
return await axios.get(`${URL}`, AJAX_CONFIG).then();
}
try this:
export const fetchTasks = () => async dispatch => {
const response = await axios.get(`${URL}`, AJAX_CONFIG);
dispatch({ type: "FETCH_TASKS", payload: response.data });
};
There is no need to define getState in your action creator if you are not going to be making use of it. You were also missing the dispatch() method which you need when developing asynchronous action creators. The dispatch() method is going to dispatch that action and send it off to all the different reducers inside your app.
This is also where middleware such as Redux-Thunk comes into play since action creators are unable to process asynchronous requests out of the box.
You did not show how you wired up your redux-thunk, but it typically goes in your your root index.js file and it looks like this:
import React from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import App from "./components/App";
import reducers from "./reducers";
const store = createStore(reducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
Remember that connect function I said you needed to implement? That came into being as a result of implementing or you should have implemented the Provider tag. With the Provider tag, your components can all have access to the Redux store, but in order to hook up the data to your components you will need to import the connect function.
The connect function is what reaches back up to the Provider and tells it that it wants to get access to that data inside whatever component you have that lifecycle method in.
Redux-Thunk is most definitely what you needed to implement if you have corrected everything as I have suggested above.
Why is Redux-Thunk necessary?
It does not have anything intrinsically built into it, it's just an all-purpose middleware. One thing that it does is allow us to handle action creators which is what you need it to be doing for you.
Typically an action creator returns an action object, but with redux-thunk, the action creator can return an action object or a function.
If you return an action object it must still have a type property as you saw in my code example above and it can optionally have a payload property as well.
Redux-Thunk allows you to return either an action or function within your action creator.
But why is this important? Who cares if it returns an action object or a function? What does it matter?
That's getting back to the topic of Asynchronous JavaScript and how middlewares in Redux solves the fact that Redux is unable to process asynchronous JavaScript out of the box.
So a synchronous action creator instantly returns an action with data ready to go. However, when we are working with asynchronous action creators such as in this case, it takes some amount of time for it to get its data ready to go.
So any action creator that makes an network request qualifies as an asynchronous action creator.
Network requests with JavaScript are asynchronous in nature.
So Redux-Thunk, being a middleware which is a JavaScript function that is going to be called with every single action that you dispatch. The middleware can stop the action from proceeding to your reducers, modify the action and so on.
You setup dispatch(setIsFetchingTasks(true)) but when axios returns you never set it to false. Did you miss to add dispatch(setIsFetchingTasks(false)) before return response.data;?
This could be the reason if your UI waits for the fetchingTasks to finish

React Redux Saga boilerplate flow

I have started my react.js project using one of Starter kit. I have used one Boilerplate structure. I am trying to learn the actual flow of React
Redux Boilerplate. After gone through their structure I am able to call the API and got data on Component. Here is the flow what I have learned as per the structure:-
a. On componentDidMount I have called a method of Mudules to create an Action.
// On view
componentDidMount() {
this.props.getCampaignTopics(1);
}
//On Modules
export const getCampaignTopics = createAction(GET_CAMPAIGN_TOPICS, (id : string) => ({id}));
b. On saga when action has been fired, I am calling the API endpoint to get the data
// On Saga
export function* getCampaignTopics(actions) {
let id=0;
if(actions.payload.id)
{
id= actions.payload.id;
}
const responsetype = yield call(() => fetch(__CONFIG__.api_url+'topic/' + id,{method:'GET',data:{}}).then(response => response.json()))
yield put(addcampaignActions.setCampaignTopics(responsetype.data));
}
function* watchGetHome() {
yield takeLatest(addcampaignConstants.GET_CAMPAIGN_TOPICS, getCampaignTopics);
}
c. After Getting the data I am calling another method of Modules to set data
export const setCampaignTopics = createAction(SET_CAMPAIGN_TOPICS, (topics : any) => ({topics}));
export const reducers = {
[GET_CAMPAIGN_TOPICS]: (state, { payload }) =>
state.merge({
...payload,
}),
[SET_CAMPAIGN_TOPICS]: (state, { payload }) =>
state.merge({
...payload,
}),
}
d. In View I am using selector to separate the indexes
// Selector
const addcampaignDataSelector = state => state.addcampaign;
const topicSelector = createSelector(
addcampaignDataSelector,
payload => payload.get('topics')
);
export const addcampaignSelector = state => ({
topics: topicSelector(state)
});
e. In component I am getting the value on props
// render on Component
const { addcampaign } = this.props;
topics = addcampaign.topics.toJS().children;
So, that's the flow I have got from their tutorial and I am getting the data. But I want to make sure I am on right track or not? Please tell me this is the right process or not?
Next is I want to call a method from component and want receive the response on the component, so I can get the data on a local variable. Could anyone can give me some tutorial or example, So I can go through and learn easily.
If your looking for a production ready boilerplate,
react-boilerplate is one of the popular ones.
You seem to be on the right track. This diagram from react boilerplate guide might help in understanding the flow better.

Redux actions depending on/coupled to other actions

I am building a Redux application (my first) and am a little unclear about how much coupling is appropriate between actions.
My application has several forms whose values are serialized in the url.
For example, there is an input field for a particular search, and upon key-up a url parameter is updated. There are several other input fields that follow this pattern.
In my top-level index.js I have several blocks of code that look like this:
// Within the declaration of a high-level component
onForm1Change={(key, value) => {
// Listened to by "formValues" state cross-section reducer
store.dispatch({
type: actions.FORM1_CHANGE,
key: key,
value: value
});
// Listened to by "url" state cross-section reducer, leads to a url param update.
// Has it's own logic that is based upon the formValues state.
// Must run after FORM1_CHANGE finishes
store.dispatch({
type: actions.UPDATE_URL,
formValues: store.getState().formValues
});
}
}
Something about actions like UPDATE_URL doesn't feel right. This action doesn't stand on its own...it relies on other actions to be dispatched first.
Is this sort of coupling between actions a code smell? Are there any common techniques to de-couple/refactor these actions?
I think that's totally OK way of chaining synchronous actions. You can also use middleware like redux-thunk for this purpose to make it simpler to read (as you will store your actions dispatcher function as an action creater). Here is some article on this topic.
This is how i did it,
Defined a redux store middleware that will detect if any dispatched action has a queryString property, and update url with it.
import createHistory from "history/createBrowserHistory";
function queryStringMiddleware(history) {
return store => next => action => {
const { payload } = action;
if (payload.queryString) {
history.push({
search: queryString
});
}
next(action);
};
}
const history = createHistory();
const middlewares = [queryStringMiddleware(history)];
const store = configureStore({}, middlewares);
Then in your action:
const onForm1Change = (key, value) => {
store.dispatch({
type: actions.FORM1_CHANGE,
key: key,
value: value,
queryString: "?the=query"
});
};

How to synchronize Redux and Relay?

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.

Resources