Can someone explain to me the usefulness of "stateless" components? Doesn't thing just mean that you have moved the state outside of the components themselves, they are not really stateless.
For example, their dropdown menu takes in an open={false}. This just mean that you now have to keep track of if this menu is open or closed outside of the menu component itself. Or, you have to write a wrapper menu component that emits something more useful and maintains that state.
Doesn't some state belong in the component itself? Certainly there are cases where this makes sense, right?
The way I like to put it is that stateless components are great because they don't have a mind of their own. Stateless components have one responsibility, and that is to encapsulate all of the UI details of a component, but not the functionality of that component.
The dropdown component is a great example actually. It is a minor convenience for the dropdown component to have it's own "open/closed" state. However, when you need more explicit control over the dropdown in a certain portion of the interface (i.e. this dropdown should open not only when I click on it's trigger element, but also when I enter the konami code on some other portion of the site) then it becomes a less minor inconvenience that the open/closed state is actually not in your control.
In general, I do create stateful wrapper components for things like this. A single stateless component that handles common UI details can be wrapped by multiple stateful components that handle functionality. Here's a simplified dropdown/tooltip example.
HoverCard component: A stateless "floating div" component that can be styled and positioned around a trigger element. Handle things such as listening for events (Esc pressed, click or hover over the trigger, detect clicks outside of the HoverCard) and fitting the HoverCard onto the screen if there is some overflow.
Dropdown component: A stateful wrapper component around the HoverCard component with custom functionality. Manages it's own "open/closed" state which is toggled via the event callbacks (onEsc, onClickTriggerEl, onClickOutsideHoverCard) it supplies to HoverCard.
Tooltip component: Another stateful wrapper component around HoverCard which is very similar to Dropdown but with different HoverCard styling and slightly different "open" state management. For example, it uses the onMouseOverTriggerEl and onMouseLeaveTriggerEl HoverCard callbacks instead of the onClickTriggerEl HoverCard callbacks to toggle its open state.
The cool thing is that they both get all of the benefits of HoverCard.
You generally have three choices when it comes to state. You can either use something like an event bus, raise events for others to change their state, move the state to the top and manage all those changes at once, or have something that manages the state of children.
An example would be if you have accordions setup that causes any open accordions to close when a different one is open. If you go with an event bus, each component would setup listeners for something like 'accordion:opened' set their state to close if the opened accordion wasn't theirs. If you move state to the top you'd set all other accordions to closed based off the event and rerender. The last would be an accordion group component that manages all the state for the children, which ultimately is a more localized version of moving state all the way to the top.
Stateless functions just take input and give output without storing anything. This would be accordions with a group that manages the state of the children or by fully moving it all the way to the top, so external functions handle the state. When you use the event bus approach you localize all state within the component and it is no longer a simple given A, you get B.
All interactive components are going to require some minimal local state in order to maintain their function.
But the amount of that state that the actual app programmer need necessarily know and fiddle with is pretty small, and can easily be abstracted away if the framework you're operating under allows it.
I daily use Reagent, which is a very clever ClojureScript layer built on React, and generally the standard pattern there is to have a single canonical state atom that has the stuff you genuinely care about, and the rest can be left to stateless or abstracted local-state-only components that you need only pass the big atom to, or even just a cursor to the parts of it that matter to that individual component.
We have an entire in-house forms library for this that we use like this every day, and honestly once you get used to it it's fantastic and saves yourself a lot of "state hell" like you get with complicated OOP frameworks.
Reagent is basically the reason I'm still a web developer even though I'd really rather be working in native: because declarative FRP is a hell of a lot more fun to work with.
My (beginner level) understanding is that if you move the state up to a higher level then you can generalise the component.
For example a button's state can be controlled by a higher level container component and that state might include variables such as "buttonTitle" = "Hit delete to continue", and onDelete = deleteCustomerRecord. So the core button code can be used in a variety of contexts.
I guess I understand that for simple component. The checkbox here is a good example where the value is the state, so there is no use in keeping an extra copy of it. Same would be with a slider component or whatever. But when it comes to more complex components I always feel like encapsulating functionality makes the component more reusable and less boilerplate-y which in turn encourages use. It's sort of like convention over configuration.
But then again, maybe it just hasn't 'clicked' with me yet.
Edit: I should say though that having all state external would help in testing the component since you can now simulate every possible state without going into the internals of the component itself.
I'm not religious about keeping components stateless. I have some components where I just found it was getting too complex to keep bouncing the state up and down the hierarchy so I just merged it all into one big stateful component.
If I was a better programmer I probably would have known how to structure it properly to avoid this but I'm not.
Also recently I have gone to the trouble of learning Redux which effectively provides a mechanism for global state and probably that would remove much of the problem with moving state around. But this is the thing about programming - you build your code doing it one way, and 80% into your project find a better way, which you start using. Hmmmm.... now should I get the damn thing built and have the app use two (or more) ways of getting the same thing done, or go back and make the whole app consistently use the better way, or not use the better way and instead continue to use the old way but keep things consistent?
In some way, yes, the state belongs to the component itself, because in the end the component is an object and objects encapsulate that state as long as they are alive.
The beauty of "stateless" components is that you can see them as functions, and should aim to create them as pure functions[1] where the only thing that can change the output (HTML) of your component/function is its input.
Another way to see it is as syntactic sugar for pure functions where instead of:
MyMenu.render({name: 'Something', open: false});
MyMenu.render({name: 'Something', open: true});
You have
var menu = new MyMenu({name: 'Something', open: false});
> Doesn't some state belong in the component itself? Certainly there are cases where this makes sense, right?
It depends how pure you want to be. The app I'm paid to write is in clojurescript and follows the convention of keeping almost everything in the global state. The main things I'm missing is focus and cursor state in text fields. The main benefit to doing this session recording and playback. I capture every point of non-determinism in the app as an event so I get full fidelity playback from recorded event streams.
It's more work than just keeping things locally or using two-way bindings but the code to handle the sort of things you'd keep in internal state is simple so handling the events is just registering the cross-app generic handler for the event. I designed the system for capture and replay but the main benefit turned out to be that when something goes wrong I generally know the source of the problem immediately even if I didn't write the code.
To use your menu open/closed as an example, I click on the preferences menu and it doesn't open. I look at the log and don't see `[:preferences/toggle-dropdown-menu :open]`, the problem is that something's eating the mouse event and I have to walk the component hierarchy. If I do see the event, I dump the app state and look in `:preferences :dropdown-menu` and see that the value is `true` instead of `:open` so my handler is broken. If the value is correct, the problem is either in the component itself (click another dropdown and see if it works) or in the computed value chain I'm using to feed the data into the component. For everything but the mouse event, I know by convention which file and function contains the relevant code and they're almost all 5-10 lines of code. Here's what the handler would look like:
I've used a similar, albeit more opinionated library for boilerplate UI stuff (modals, dropdowns, etc.) and the issue I have with it is styling. It works fine for the well-defined use cases but when you need to style something not part of the proptypes or as a child/parent of the given prop, the treatment becomes worse than the cure. You have to resort to rooting around the DOM with dev tools, determining the proper divs (which are usually horribly long and messy), make the style, and hope the library isn't overwriting it at runtime.
Rebass does all styling inline. You can override per instance (pass in `style` prop), or "globally" via the React `context`. Example themes [1] can be seen in the demo [2] ("Config" dropdown in top right).
You can't directly access the classNames of child components i.e. if the component is composed of an <h1> inside of a <div> (with <div> being the top-level component), you cannot directly set the className of the h1 if a prop doesn't exist to do so
But you can set the classname of the component and then refer to it as ".componentclass h1" or similar, right? Or is that what you are saying gets too unwieldy?
Using ".component-name <insert child tag>" goes against semantic UI conventions and couples your custom styles to the tags instead of the classes in the component. It quickly becomes brittle when trying to update those styles if the tags change in an updated version, or if the rendered output changes structure, etc.
Maybe it's time to rethink your conventions...? The solution to your problem is in front of you, but you're avoiding implementing it because of strict adherence to arbitrary conventions.
The next level future of UI would be something as developer-productive as Visual Basic .NET from 2007: lay out my app visually in minutes, double click to add code, build, run, ship.
It's 2016 and I'm still using a soup of hacks to build web UIs. GWT was promising but far too clunky. React+Bootstrap is almost there but I still have to write code to make wheels roll (in a parser hack called JSX!) and I still have to think about the web layer instead of having it abstracted away. As soon as I start using anything else I have to drag in a soup of hacks, so in the end I always end up with a web app with a hundred dependencies. Sorry, must have (insert hipster framework here) installed too if I want to embed BeanieCap.JS.
I would pay thousands of dollars for something as productive as VS.NET from 2007 but for generating modern responsive UIs for the web. It's okay to simplify the problem by being opinionated, but only if your opinions don't suck and only if whatever abstractions you create are elegant and degrade gracefully when they (inevitably) leak a little. Now that MS is open sourcing .NET, adding web UIs to Xamarin and making them work like mobile and desktop would be one route to this. Another would be to reboot GWT using Go->ASM.JS as the code path and do the UI in Go, then build a visual UI designer for it. Use the dom as a renderer and shit-can CSS and all the rest of that stuff in favor of an opinionated uniform minimally-themable design (as long as it doesn't look like crap).
Every now and then I go searching for this. Nope, still doesn't exist. I have a wad of cash in hand but nobody will take it so back to hacking web UIs in a text editor manually. Sigh.
The problem is that we're trying to think in terms of "apps" on a platform that was invented and built in terms of "documents." The web wasn't designed to be an "interface", the browser was, and it's purpose was to display interlinked documents.
On top of this, the pace of the web's development has outstripped the capability of anyone to actually plan the extensions to this base model, so everything since is just hacks on top of hacks. But it works enough for people to keep going through investor storytime year on year, so ... we continue to move fast, and break everything.
I would argue that .NET did allow for developers to be very productive at shipping the worst UI code I have ever seen. Productivity != shipping horrible code faster than ever. In my opinion.
This reminds me a lot of semantic-ui[1] and the react-specific wrapper, react-semantify[2]. Semantic-UI is entirely CSS Class-based, and has lots of similar components. You can definitely use it without the react wrapper, though the wrapper makes it "feel" a bit more react-like. Some of the interactive functionality is based upon jquery, which is decidedly un-react, but while using it in my most recent project, the jquery bits haven't gotten in my way very much.
I'd love to see react-semantify expanded to replace the jquery-specific bits more thoroughly beyond simply replacing the initialization methods, but haven't had enough time to work on those changes myself.
- I've nothing to do with these projects except that I've used them recently and enjoyed them for the most part.
"ebass is a React UI component library that uses inline styles to avoid "
Which also means you can't override the CSS if you're trying to go for something like oh I don't know, basic cohesion; without using !important. Seems useless for anything short of prototyping.
Same opinion... What if the UI need is slightly different from what ebass offers? !important everywhere?
This solution to the "leaky global styles" seem worse than the problem it solves.
I think it is very interesting and also a good excercise to read its source and learn How the author implemented componente. Also seems interesting react-static by the same author.
For example, their dropdown menu takes in an open={false}. This just mean that you now have to keep track of if this menu is open or closed outside of the menu component itself. Or, you have to write a wrapper menu component that emits something more useful and maintains that state.
Doesn't some state belong in the component itself? Certainly there are cases where this makes sense, right?