DEV Community

Nishchal Gautam
Nishchal Gautam

Posted on

Let's talk about how React is only a view layer

Hello everyone,
This is a mix between discussion and a post, if you don't agree with me, I hope you write a comment and hopefully change my mind as long the point changes my views. I have recently done https://github.com/fossapps/Feature.Manager project, and I need a UI, I don't need UI to be fancy or production ready, I want it to be simple, and when I do decide to change I should still be able to make it production ready.

Fetching from API

To fetch a data from API, I don't expect people to write fetch calls, they shouldn't be going to documentation and trying to find out how to make the call, what is the url, what parameter is going to be used etc. That's just a implementation detail which no one cares for, I want developers to invest time in actually building a product rather than stare at a API documentation, in the interest of saving developer's time, I always prepare/generate a API client.

I generally use swagger to document my API, and I generate clients for most of the language, however for typescript, I use @crazyfactory/tinka, this supports middleware, mocking, testing, caching etc, I haven't found anything which makes api calls better than this.

When I do need to make an API call, here's what I do:

const api = Sdk.getInstance(baseUrl);
const response = api.profile.getById(userId);
if (isError(response)) {
  // do something
  return;
}
// use the actual response

And because everything is in typescript, I get full typescript features everywhere.

Now consumer of Sdk, never has to look at API documentation, just use your IDE, which shows what you need.

Normally for a large project where I want to ensure it's a very solid project, I create a separate package and use semantic-release to create releases automatically with proper semantic versioning, if you don't know what this is, have a look at semantic-release project.

But in my case, I just created a directory called Sdk and wrote everything there. Here's a PR which introduced it: https://github.com/fossapps/Feature.Manager.Ui/pull/27
If you follow that PR you'll notice that I'm using the tinka package, and adding Sdk methods, and because everything's typed, consumer of this Sdk, (which is this project itself), will never have to worry about what method it's using, what URL it's hitting etc. Now obviously I could generate a client using autorest, but I couldn't find a generator which supported middlewares (I'll need middleware support).

Storing data (React is a View layer, don't fetch / store data in a view layer)

Now that fetching data is out of the way, let's talk about storing data.

I treat React as a view library, even though they're planning on introducing API calls with suspense soon.

One thing I really hate when I review someone's code, they simply go to componentDidMount (or useEffect), and make a api call, or they have hooks which makes the API call and put it on state.

IMO, this is a NO-NO, why would you access data from your view layer, have you ever queried a database from your view layer in any other MVC frameworks? Should you?

For that reason I've a different layer, I call it side effect layer, I use redux-saga for managing side effects this let's me keep my view and logic completely separate, in theory, if someone decided that I needed to ditch react for something else, in this situation I technically can, because again, React is just a view layer.

I keep data on redux and if say some day I happen to say I don't want to redux for data storage and want to move to mobx, I still can. They're not glued together.

Here's how I do this:
I've a page which needs data from API call, on it's componentDidMount (or useEffect, I'll come to this later), I check if I already have data available on store, if I do, then I don't do anything.

But if I don't have any data on store, then my componentDidMount will dispatch a action SOME_DOMAIN/FETCH_DATA (or something similar), it dispatches this, and my side effect layer notices this (redux saga has ability to listen to actions and call a method when it happens) on that call, I call API using the Sdk I mentioned before, and set the data on redux.

There's a side effect to this, how many times have you tried to do setState on a unmounted component? Say user goes to a page and immediately navigates away from that component, then you get a warning from react saying that's wrong, also you now can't re-use that data anymore, when user comes to the component, you make the api call again.

Because redux and redux-saga are different layers, that problem is not there anymore (and you can test your redux, redux-saga and your component separately, and writing tests are easier).

If you had done a fetch call inside a component, you'll end up with one messy component, and a very dreadful set of tests (worst if you decide to test after the code is done)

So I don't think you should keep data on your view layer, I also don't think you should make any calls from your view layer and I certainly don't think you should mix all of them on same layer.

The PRs I linked to, and the project I've linked to, neither of them is a gold standard, I know that, had I enough time, and had this project needed to be that way, I certainly would have spent a bit more time to separate Sdk into whole another npm package, same thing with components, I could have used a higher order component to give the color values and made the whole thing themeable, but that's not for today.

Let's talk about redux hooks

I've seen people jumping on this hooks' train left and right, let's talk about redux hooks first.

When you useSelector on your component, you basically glue your component to redux, view layer and data layer is very tightly coupled, and that's not what you want in a production ready app, I personally don't even want it on a throw away app, is calling connect function really that difficult?

And I've heard this argument before, "How about we create a Wrapper component which calls the useSelector and passes them down to components which needs them", to that I say you just implemented connect component, it's basically the same, except you'll want to do it for every component, if not, it's just another connect.

And with redux hooks, now you have to mock your redux store, which is a whole other story.

Remember, you really don't want to glue your view and data layer. I keep the concerns separate, and when you do use redux hooks your component violates a lot of SOLID principles.

It certainly violates single responsibility principle, because it's now communicating with your redux layer,

It also violates open for extension and closed for modification principle, Now that you've used redux hooks, you can't extend it in anyway, you're actually tied to redux layer, and that's it. Take these two component for example:

const Component: React.FC<IThemeProp> = (props) => {
  return (
    <div style={{padding: 10, background: props.theme.background, color: props.theme.color}}>{props.children}</div>
  );
};
export const Alert = withTheme(Component);
export const AlertWithHook: React.FC = (props) => {
  const theme = useTheme();
  return (
    <div style={{padding: 10, background: theme.background, color: theme.color}}>{props.children}</div>
  );
};

The above examples are just simple react hooks, but let's take them as an example,

The first component which doesn't use hooks, can be extended easily, you can just use the component without the theme, and personalize it.

The second component however, is already tied with a certain context, and there's nothing you can do to change the second component with hook.

Another thing, the first component is a pure component (without the higher order component), for same input, it always returns same output, but the second one with hooks, isn't a pure component, it actually depends on what hook returns, which is a side effect, I generally tend not to go that way.

Performance and functional vs classes

First off, I always prefer functional components for smaller components, if a component is getting big, probably time to break them down to smaller components.

Facebook also says, don't go and change your existing class components and change to functional, there's no need for that, (but people being people, have started rewriting libraries), I'm not doing that.

Another thing is people say with hooks, you get performance boost. I don't think that's the case, let me tell you why.

I think the hooks create another side effect which is all your components are functional, and functions are faster than classes, the performance boost isn't coming from hooks, but the fact that you have functional components.

What I prefer is to have smaller functional components, and when it comes to larger components which might have logic, or needs multiple handlers, I tend to have them as a separate method, after all, I can actually do OOO.

Discussions welcome

I'm sure I might have missed some points, and there are so many things I wanted to communicate, but don't have the time to write about and don't want to extend the post too long.

Please raise your concerns in a way we can talk about, and if you don't agree, let me know why you don't agree, if you do, can you elaborate why you feel that way? I feel like there are some things my instincts tell me are dirty, but I can't actually explain them.

Discussion (6)

Collapse
pengeszikra profile image
Peter Vivo

I already worked with class and redux free react application, and seems quite useful. If we find the right balance of our state, then useReducer is enough. Without any useContext. For complex side effect handling, I was use saga: npmjs.com/package/use-saga-reducer that is also fit for hook works. My another favourite js library is github.com/callbag/callbag great in side effect handling too.

Collapse
cyberhck profile image
Nishchal Gautam Author

what I meant is, as soon as you mix ui and data (if you use useSagaReducer), you're doing just that, but again, if that works for you then good for you :)

Collapse
pengeszikra profile image
Peter Vivo

In my mindset react as view layer means view component don't handle any data, just represent props, and call actions for interaction. One or many useReducer / useReduceSaga organise business logic, and works with state. So I have few controll component / program.

Thread Thread
cyberhck profile image
Nishchal Gautam Author • Edited on

well, IMO, as soon as you use any of the hooks from redux, like useSelector, (honestly, we've banned them in our linting rules, so I don't even know what hooks are available from redux), but as soon as you do that, your components gets glued together with data, sure, logic is in different file, doesn't mean it's still separate, you used it in a way that I can't re-use your component without using that hook.

But again, let me reiterate, if that works for you, it's good, for me, I don't like that even one bit, when you mix your view with data, that's a problem,

when you use component, the original component is still not really glued with redux, because your connect hoc does the glueing part, which means say you don't want to use redux anymore, you can.

But as soon as you useSelector from redux, then you're stuck with redux forever, (unless you change so many places)

Also btw, I don't use redux-saga the way it's shown in example, the reason is that there's handling of dependency correctly, say you want an API client, you don't want to have to create a client in every saga file, they have recommended to use what pattern works, use just that one, I use the OOP way, here's an example:
github.com/crazyfactory/ts-react-b... (registers all sagas)
github.com/crazyfactory/ts-react-b... (base class which all sagas implement, and it makes sure we get the dependency we need)
github.com/crazyfactory/ts-react-b... (example of a saga which fetches an API call)

On that boilerplate, we're not doing Sdk, so dummyApi is being called directly, BUT, when you do have api, you define that as dependency on your BaseSaga, and a instance of sdk will be passed (I'm finishing up a project where I'll be doing this, I'll give you that link once I have it. :)

Thread Thread
pengeszikra profile image
Peter Vivo

You can use saga capability with useSagaReducer without redux ( I don't use redux ). In that way you can use saga same simply way as use useReducer. And you right if you put hook in any component, you can't use without that. But I think if you write your components without any outer dependency, and all date and action come from props, then your components can be reused, don't mother if you use state, or saga driven side effects inside of them.

Collapse
0916dhkim profile image
Danny Kim

I agree that we need to separate two layers of side-effects (rendering & dynamic data), however, functional programming paradigm is very different from MVC and OOP. SOLID principles do not make too much sense in functional world, so it is difficult to criticize redux hooks based on SOLID.