Emixam23
Emixam23

Reputation: 3964

React Js - Combine Redux and Services layers

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:

enter image description here

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 !

Upvotes: 0

Views: 3860

Answers (2)

lecstor
lecstor

Reputation: 5707

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

Upvotes: 2

Alexandre Nicolas
Alexandre Nicolas

Reputation: 1949

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)

Upvotes: 2

Related Questions