While #43 made some progress on solidifying hooks types to make their usage safer, there is still a case that @cristianoc brought up today that is not covered. For example, if one replaces the renderCounter function by:
let renderCounter = () =>
if (Random.bool()) {
useReducer(reducer, 0, ((count, dispatch)) =>
<view>
<button title="Decrement" onPress={() => dispatch(Decrement)} />
<text> {"Counter: " ++ str(count)} </text>
<button title="Increment" onPress={() => dispatch(Increment)} />
</view>
);
} else {
ignore(useReducer(reducer, 0, ((_count, _dispatch)) =>
<view></view>
));
useReducer(reducer, 0, ((count, dispatch)) =>
<view>
<button title="Decrement" onPress={() => dispatch(Decrement)} />
<text> {"Counter: " ++ str(count)} </text>
<button title="Increment" onPress={() => dispatch(Increment)} />
</view>
);
};
(note the additional useState call on the second branch wrapped by ignore())
This code will compile, because in both branches we return hook((unit, reducer((int, action) => int))). But at runtime, the behavior becomes unexpected, because of the differences "state stacks" that are created between branches. In some cases (like if the ignored useState using a string-typed state) leading to runtime exceptions because of the different types of state[0] for that component.
@cristianoc also came up with this (amazing) idea that now that we know the shape of the state of each component in the form of nested tuples, we could change the underlying implementation to support this kind of behavior by moving away from the state stack model, into a new model where each hook has a reserved "slot" with the shape of the state needed.
If I understood correctly, the change would be that the hooks would return a "getter" and a "setter" for that individual piece of state, which means there would be no linear restriction about hooks anymore, as one would always get the "right paths" to read and write from them.
I believe that the implementation of these ideas could also lead to the removal of the Obj.magic that is currently used in State.re, but I have to noodle a bit more around this 🍜.
@bryphe Would you be open to more API changes to enforce a better safety? (even at the cost of diverging a bit from ReactJS hooks semantics...)
While #43 made some progress on solidifying hooks types to make their usage safer, there is still a case that @cristianoc brought up today that is not covered. For example, if one replaces the
renderCounterfunction by:(note the additional
useStatecall on the second branch wrapped byignore())This code will compile, because in both branches we return
hook((unit, reducer((int, action) => int))). But at runtime, the behavior becomes unexpected, because of the differences "state stacks" that are created between branches. In some cases (like if the ignoreduseStateusing a string-typed state) leading to runtime exceptions because of the different types ofstate[0]for that component.@cristianoc also came up with this (amazing) idea that now that we know the shape of the state of each component in the form of nested tuples, we could change the underlying implementation to support this kind of behavior by moving away from the state stack model, into a new model where each hook has a reserved "slot" with the shape of the state needed.
If I understood correctly, the change would be that the hooks would return a "getter" and a "setter" for that individual piece of state, which means there would be no linear restriction about hooks anymore, as one would always get the "right paths" to read and write from them.
I believe that the implementation of these ideas could also lead to the removal of the
Obj.magicthat is currently used inState.re, but I have to noodle a bit more around this 🍜.@bryphe Would you be open to more API changes to enforce a better safety? (even at the cost of diverging a bit from ReactJS hooks semantics...)