In this example, I used the object deconstruction of the props to insert the internal state management pieces as the defaults in lines 8–12. That is, if the consumer doesn’t pass in the control mechanisms ( inc and dec here) or the controlled value ( count ), then the component’s own methods and state are used.

This works wonderfully for any Counter component that is either entirely uncontrolled (no props passed in, all defaults used) or entirely controlled (every expected prop passed in, no defaults used). There are strange behaviors when the component is only partially controlled, though.

Partial control can happen several ways:

Permanent Partial Control: Some but not all of the controllers and controlled values are passed in as props, and this does not change over the course of the component’s lifetime.

Switching Fully Controlled to Entirely Uncontrolled: All of the controllers and controlled props are sometimes (but simultaneously) passed in as props, and sometimes (also simultaneously) nullified. This can happen in either direction (controlled to uncontrolled, and vice versa) and can happen on multiple occasions during the component’s lifetime.

Switching Partial Control: Some or all of the controllers and controlled values switch between undefined (or null ) and actual values during the lifetime of the component.

Step 3 — syncing internal and external representation of controlled values

When the component receives some but not all of the controllers and controlled values as props, whether during initialization or later in the component’s lifetime, there really isn’t anything that can be done. The controllers and the values they control must communicate. External controllers can’t affect internal state, and internal controllers aren’t allowed to overwrite external values because we stipulated that internal management is the default to be overridden by props. The most we can do is send a warning to the developer that they are engaging in behavior which will have unintended consequences.