diff --git a/doc/hooks.md b/doc/hooks.md
index 87801de0..24e5c08c 100644
--- a/doc/hooks.md
+++ b/doc/hooks.md
@@ -12,6 +12,8 @@
- [`onWillUnmount`](#onwillunmount)
- [`onWillPatch`](#onwillpatch)
- [`onPatched`](#onpatched)
+ - [`onWillStart`](#onwillstart)
+ - [`onWillUpdateProps`](#onwillupdateprops)
- [`useContext`](#usecontext)
- [`useRef`](#useref)
- [`useSubEnv`](#usesubenv)
@@ -203,6 +205,59 @@ before the component patched.
abstractions. `onPatched` registers a callback, which will be called just
after the component patched.
+### `onWillStart`
+
+`onWillStart` is an asynchronous hook. This means that the function registered
+in the hook will be run just before the component is first rendered and can return a
+promise, to express the fact that it is an asynchronous operation.
+
+Note that if there are more than one `onWillStart` registered callback, then they
+will all be run in parallel.
+
+It can be used to load some initial data. For example, the following hook will
+automatically load some data from the server, and return an object that will
+be ready whenever the component is rendered:
+
+```js
+function useLoader() {
+ const component = Component.current;
+ const record = useState({});
+ onWillStart(async () => {
+ const recordId = component.props.id;
+ Object.assign(record, await fetchSomeRecord(recordId));
+ });
+ return record;
+}
+```
+
+Note that this example does not update the record value whenever props are
+updated. For that situation, we need to use the `onWillUpdateProps` hook.
+
+### `onWillUpdateProps`
+
+Just like `onWillStart`, `onWillUpdateProps` is an asynchronous hook. It is
+designed to be run whenever the component props are updated. This could be
+useful to perform some asynchronous task such as fetching updated data.
+
+```js
+function useLoader() {
+ const component = Component.current;
+ const record = useState({});
+
+ async function updateRecord(id) {
+ Object.assign(record, await fetchSomeRecord(id));
+ }
+
+ onWillStart(() => updateRecord(component.props.id));
+ onWillUpdateProps(nextProps => updateRecord(nextProps.id));
+
+ return record;
+}
+```
+
+Note that if there are more than one `onWillUpdateProps` registered callback,
+then they will all be run in parallel.
+
### `useContext`
See [`useContext`](context.md#usecontext) for reference documentation.
diff --git a/doc/readme.md b/doc/readme.md
index e79c461d..ea10525b 100644
--- a/doc/readme.md
+++ b/doc/readme.md
@@ -16,10 +16,12 @@ owl
EventBus
Observer
hooks
+ onWillStart
onMounted
- onWillUnmount
+ onWillUpdateProps
onWillPatch
onPatched
+ onWillUnmount
useContext
useState
useRef
diff --git a/doc/store.md b/doc/store.md
index 7f50709b..7dcc96da 100644
--- a/doc/store.md
+++ b/doc/store.md
@@ -9,6 +9,7 @@
- [Actions](#actions)
- [Getters](#getters)
- [Connecting a Component](#connecting-a-component)
+ - [`useStore`](#usestore)
- [Semantics](#semantics)
- [Good Practices](#good-practices)
@@ -111,6 +112,14 @@ const actions = {
};
```
+The first argument to an action method is an object with four keys:
+- `state`: the current state of the store content,
+- `dispatch`: a function that can be used to dispatch other actions,
+- `getters`: an object containing all getters defined in the store
+- `env`: the current environment. This is useful sometimes, in particular if
+ an action needs to apply some side effects (such as performing an rpc), and
+ the `rpc` method is located in the environment.
+
Actions are called with the `dispatch` method on the store, and can receive an
arbitrary number of arguments.
@@ -196,76 +205,66 @@ is a string or a number.
### Connecting a Component
-At some point, we need a way to access the state in the store from a component.
-By default, an Owl `Component` is not connected to any store. To do that, we
-need to create a component inheriting from `OwlComponent`:
+At some point, we need a way to interact with the store from a component. This
+can be done with the help of the three store hooks:
+
+- `useStore` to subscribe a component to some part of the store state,
+- `useDispatch` to get a reference to a dispatch function
+- `useGetters` to get a reference to the getters defined in the store.
+
+
+Assume we have this store:
```javascript
const actions = {
increment({ state }, val) {
- state.counter += val;
+ state.counter.value += val;
}
};
const state = {
- counter: 0
+ counter: {value: 0}
};
const store = new owl.Store({ state, actions });
+```
-class Counter extends owl.ConnectedComponent {
- static mapStoreToProps(state) {
- return {
- value: state.counter
- };
- }
- increment() {
- this.env.store.dispatch("increment");
- }
+A counter component can then select this value and dispatch an action like this:
+
+```js
+class Counter extends Component {
+ counter = useStore(state => state.counter);
+ dispatch = useDispatch();
}
const counter = new Counter({ store, qweb });
```
-```xml
-
-```
-
-The `ConnectedComponent` class can be configured with the following fields:
-
-- `mapStoreToProps`: a function that extracts the `props` of the Component
- from the `state` of the `Store` and returns them as a dict.
-- `getStore`: a function that takes the `env` in arguments and returns an
- instance of `Store` to connect to (if not given, connects to `env.store`)
-- `hashFunction`: the function to use to detect changes in the state (if not
- given, generates a function that uses revision numbers, incremented at
- each state change)
-- `deep` (boolean): [only useful if no hashFunction is given] if `false`, only watch
- for top level state changes (`true` by default)
-
-Note that the class `ConnectedComponent` has a `dispatch` method. This means
-that the previous example could be simplified like this:
-
-```javascript
-class Counter extends owl.ConnectedComponent {
- static mapStoreToProps(state) {
- return {
- value: state.counter
- };
- }
-}
-```
-
```xml
```
+### `useStore``
+
+The `useStore` hook is used to select some part of the store state. It accepts
+two arguments:
+
+- a selector function, which takes the store state as first argument (and the
+ component props as second argument) and returns
+ an object or an array (which will be then observed)
+- optionally, an object with a `store` key (if we want to override the default
+ store) and an equality function (if we want to specialize the comparison)
+
+
+If the `useStore` callback selects a sub part of the store state, the component
+will only be rerendered whenever this part of the state changes. Otherwise, it
+will perform a strict equality check and updates the component every time this
+check fails.
+
### Semantics
-The `Store` and the `ConnectedComponent` try to be smart and to optimize as much
+The `Store` class and the `useStore` hook try to be smart and to optimize as much
as possible the rendering and update process. What is important to know is:
- components are always updated in the order of their creation (so, parent
@@ -283,8 +282,9 @@ as possible the rendering and update process. What is important to know is:
- avoid asynchronous components as much as possible. Asynchronous components
lead to situations where parts of the UI is not updated immediately.
- do not be afraid to connect many components, parent or children if needed. For
- example, a `MessageList` component could get a list of ids in its `mapStoreToProps` and a `Message` component could get the data of its own
+ example, a `MessageList` component could get a list of ids in its `useStore`
+ call and a `Message` component could get the data of its own
message
-- since the `mapStoreToProps` function is called for each connected component,
+- since the `useStore` function is called for each connected component,
for each state update, it is important to make sure that these functions are
as fast as possible.
diff --git a/src/context.ts b/src/context.ts
index 59333a3e..a7f6c4ff 100644
--- a/src/context.ts
+++ b/src/context.ts
@@ -40,6 +40,11 @@ export class Context extends EventBus {
* next. However, the only things that matters is that children are updated
* after their parents. So, this could be optimized by being smarter, and
* updating all widgets concurrently, except for parents/children.
+ *
+ * A potential cheap way to improve this situation is to keep track of the
+ * depth of a component in the component tree. A root component has a depth of
+ * 1, then its children of 2 and so on... Then, we can update all components
+ * with the same depth in parallel.
*/
async __notifyComponents() {
const id = ++this.id;