Skip to main content

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.