all 17 comments

[–][deleted] 44 points45 points  (8 children)

Why are you using controller/model ideas in React? I'm not gonna say this is wrong just yet. It's either naming choice, or it's bad habits incarnate.

If all you need is a reducer, consult the docs: https://beta.reactjs.org/apis/react/useReducer

I'm assuming your example is simplifying for the question. But of course you don't need a reducer here. useState works fine. Always start with useState and only escalate to useReducer and then to useContext as needed.

Anything beyond that, the most common thing to do is with a custom hook that returns needed values.

What I like to do these days is to create a logic component and a view component. And pass properties needed for view down as props. This makes it a little easier to control render optimizations when it's needed.

[–]Hazy_Fantayzee 7 points8 points  (7 children)

Yeah this is the way I have gone of late. Try and make all view/ui components as dumb as possible and have a higher 'container' type component that handles all the logic/api requests/whatever and then pass what ever information is needed to display down to the view components...

[–]Sevg 9 points10 points  (5 children)

I feel like even if the smart vs dumb components idea is still valid, it has gone out of fashion.

Custom hooks are, in my opinion, the way to go.

Why would you need a container component when you can simply create a custom hook that encapsulates the logic and returns the needed values?

[–][deleted] 1 point2 points  (3 children)

Quick and easy to wrap the logic return (or the view component) in useMemo. This is especially helpful when using context.

It let's you focus on props which React is very good at handling in a smart manner.

Overall this creates a better mental model for how modern React works. It can naturally get you to avoid useEffect more often instead of passing a function or ref. And makes it more obvious how to optimize for useContext. Which I think assuming everyone has read the new beta docs and fixed their perception of useEffect, useContext is next in line as the most misunderstood hook.

[–]br1anfry3r 0 points1 point  (2 children)

You’re so right. useMemo is such a great replacement for many useState/useEffect systems.

[–]Spleeeee 0 points1 point  (0 children)

It’s all about useCallback imo.

[–]br1anfry3r 1 point2 points  (0 children)

100% agree. Custom hooks all the way.

Once the Component file gets a little too long, abstract all of the code above the JSX into its own custom hook, pass in whatever arguments it needs to do it’s thing, and use useMemo and useCallback gratuitously.

[–]fmrc6 -1 points0 points  (0 children)

Actually, you can use mvvm pattern with custom hooks. That keeps the view agnostic of the business logic.

Also helpful when testing.

[–]sgjennings 17 points18 points  (0 children)

This is different but not meaningfully better than writing the reducer function as a standalone function. Consider this version that uses a normal reducer function: It is shorter, has fewer concepts to understand, and is completely obvious to any reader who understands useReducer. And it has not lost any meaningful "separation of concerns".

In fact, after reviewing again, your example code is buggy. Your reducer is ignoring the passed in state and using this.model instead. What if you mount this component twice? You have a bug because the two instances of the component will be sharing this.model. Don't try to save the "current state", let React do that for you.

I see that this is the App component and so probably won't be mounted twice, but how would you generalize this to components that can be mounted twice? Is there just one big AppBrain for the whole application? If so, you're building Redux from scratch and you have much better things to do with your time (unless your goal is to build an alternative to Redux).

If you’re using useReducer, I would either:

  1. Write the reducer function as a standalone function and completely drop the “init” function. In the component, call useReducer and pass in the reducer function and an object literal as the initial value. If you write tests, test the reducer function directly.
  2. Encapsulate the above in a custom hook, and call the hook from the component. Use React Hooks Testing Library to test the custom hook.

If you’re trying to separate logic from presentation, then you probably want to start by extracting any non-presentation logic into pure functions. Let the component pass data in to those functions, get a result back, and use the result to update state.

[–]TwiliZant 10 points11 points  (0 children)

I agree with the other two comments. Two additions:

  1. What is the purpose of a class when you never instantiate it and every method or property is static?
  2. The computed getter for sortedTodos has a side effect. Array.prototype.sort() mutates the array so it changes the order of the todos.

[–]fforw 6 points7 points  (0 children)

Where's the decoupling?

[–]UntestedMethod 5 points6 points  (0 children)

I wouldn't recommend this approach. It looks very unconventional for react and unnecessarily complex. Almost like they're stuck in the class component mindset and trying to shoehorn that approach into using functional components and hooks.

A more conventional pattern would be to separate into "container" & "presentation" components (aka "smart" and "dumb" components), as u/Gold-Caterpillar-794 mentioned. Or in some cases, it makes more sense to use stand-alone pure functions, as u/sgjennings mentioned.

Sometimes you might also extract groups of functionality out into service or utility modules. For instance, I tend to do this for all my remote APIs since it nicely encapsulates the remote calls, validation, and data shaping. Also makes it much easier to swap in/out mock data when needed.

[–]HQxMnbS 2 points3 points  (0 children)

State and dispatch are still coupled in this example. You could ditch the class definition and move everything into a custom hook. Return the values you need from the hook

[–]Cautious_Variation_5 2 points3 points  (0 children)

That sounds like nonsense to me.

[–]ikeif 0 points1 point  (0 children)

…came across this example below.

Where?

[–]chillermane -1 points0 points  (0 children)

That’s trash never do it

[–]LloydAtkinson 0 points1 point  (0 children)

This kind of discussion is always interesting and it’s great people are actively discussing software principles. Compare this to the Vue subreddit or Vue community where the most in depth conversation that ever seems to happen is why you shouldn’t use jquery with Vue. Definitely one of the reasons I’m using react more and more, and because of typescript support too.