Introducing Producers
producers are the central concept of Engine. Engine recommends that our components should only represent the view, and have as little logic as possible. Producers are where the logic lives in an Engine app.
Simplest place to see producers in action can be Todo list's footer. A producer
will count the number of pending todos, and show them in the view. Extract
Footer out of src/App.tsx into its own component. Create src/Footer.tsx
with following contents:
const Footer = () => (
<footer className="footer">
<span className="todo-count">
<strong>1</strong> items left
</span>
<ul className="filters">
<li>
<a href="#/" className="selected">
All
</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<button className="clear-completed">Clear completed</button>
</footer>
);
export default Footer;
Update src/App.tsx to use Footer:
+ import Footer from "./Footer";
...
- <footer className="footer">
- <span className="todo-count">
- <strong>1</strong> items left
- </span>
- <ul className="filters">
- <li>
- <a href="#/" className="selected">
- All
- </a>
- </li>
- <li>
- <a href="#/active">Active</a>
- </li>
- <li>
- <a href="#/completed">Completed</a>
- </li>
- </ul>
- <button className="clear-completed">Clear completed</button>{" "}
- </footer>
+ <Footer />
Footer will trust that pendingCount is going to be available in the state,
and that it'll always contain the correct number of pending todo items. Update src/Footer.tsx` based on this assumption:
- const Footer = () => (
+ const Footer: view = ({ pendingCount = observe.pendingCount }) => (
<footer className="footer">
<span className="todo-count">
- <strong>1</strong> items left
+ <strong>{pendingCount}</strong> items left
</span>
<ul className="filters">
The logic for counting pending items in the Footer itself, in fact, in a
traditional React app that's exactly what we would have done. But Engine
strongly recommends that business logic should be kept out of views, and put
it in producers. Add a producer to the Footer. In src/Footer.tsx, add
pendingCounter producer:
+ const pendingCounter: producer = ({
+ updatePendingCount = update.pendingCount,
+ todosById = observe.todosById
+ }) => {
+ const pendingCount = Object.values(
+ todosById as TodosById
+ ).reduce(
+ (accum: number, todo) =>
+ todo.status === TodoStatuses.done ? accum : accum + 1,
+ 0
+ );
+
+ updatePendingCount.set(pendingCount);
+ };
+
+ Footer.producers([pendingCounter]);
export default Footer;
producers are just normal functions which are labeled with
producer macro. They can access the state the same way as
views; they even have access to props that a view might get from its parent.
To add a producer to a component, .producers property of a view is given an
array of producers.
Similar to views, a producer is triggered whenever anything that it observes
changes. pendingCounter producer Observes todosById object, so whenever
anything in todosById changes, this producer is executed. Whenever status of any
todo item is updated, pendingCount gets updated accordingly.
In the next chapter, we'll take a look at how producers make it possible to a very create workflow for view producer communication.