Closed JeanLucPons closed 3 years ago
An other idea (I think I will switch to this one) This prevents to call addListener and makes the search in the listener list each time you access the value of a DataSource. This solution implies that the DataSource model is constant and cannot change dynamically (which is my usecase). A dynamic model change can be handled by passing the DataSource array to the dependency list of useEffect (take care that the array reference is stable if models do not change)
/**
* Hook to register listener to fields when component is mounted and
* unregister them when component is unmounted
*/
export function useData(atts:Array<DataSource>) : void {
// Create the dispatcher, React garanty that this function is
// stable during the component lifecylce
const [, _dispatch] = useState({});
useEffect(() => {
//Component DidMount
atts.forEach(a=>{
a.addListener(_dispatch);
})
return () => {
//Component WillUnmont
atts.forEach(a=>{
a.removeListener(_dispatch);
})
};
}, []);
};
Example of usage: Here StateScalar.getValueField() returns one Field of the StateScalar object i want to track
export const MyComponent: FC<{ stateModel: StateScalar} > = ({ stateModel}) => {
useData([stateModel.getValueField()]);
let value = stateModel.getValue(); // Do not need to pass the hook here, getValue() is a shortcut to stateModel.getValueField().get()
That's an interesting suggestion, but I don't think it's a nice fit for this library. In the case of just direct dependencies in react, it may be a good idea, but it doesn't support some of the more advanced features of my system.
You're currently directly adding and removing dependencies on a data source in the second suggestion, but when you have a general data retriever, this would cause much more syntactic overhead and complexity for things like virtual data sources. This is a feature I commonly use, and what I think makes model-react very powerful and flexible. In the first suggestion you don't remove the listener on every update, so a virtual data source like this, wouldn't be stable:
function getSomething(hook?: IDataHook): Something | undefined {
return isSmthSet(hook) ? getSmth(hook) : undefined;
}
The path that's taken to obtain data here may change per call, so each call will need to register fresh hooks to ensure it's subscribed to all the right dependencies.
I think overall the suggestion is nice in terms of simplicity, but not nearly as flexible as the current system.
OK So for my usecase, adding and removing dependencies will likely be too much. I have to refresh fast data and refreshers are obviously asynchronous. In my final implementation I think I will also have to add "mutex" mechanism (may be using async-mutex) for the listener lists. So likely not possible for me to add and remove each at each render. Thx ;)
Edit:
My final implementation will probably looks something like this. I used the state to store and returns it. The listeners call the dispatcher with this as argurments. It allows a component that listen on several datasources to know which datasource has tigerred the rendering.
/**
* Hook to register listener to DataSources when the component is mounted and
* unregister them when the component is unmounted.
* Returns the DataSource which has triggered the rendering or undefined
* when the component is mounted.
*
* Eg: A react component that want to listen on the format and value of a NumberScalar
* change should use:
*
* export const MyComponent: FC<{ model: NumberScalar }> = ({ model }) => {
* let source = useData([model.getValue(),model.getFormat()]);
* let value = model.getValue().get()
* let format = model.getFormat().get()
* if( source === undefined ) {
* // The component has just been mounted
* } else if ( source == model.getFormat() ) {
* // Format has changed
* } else
* ...
*/
export type DataSourceState = { source: DataSource | undefined }
export function useData(atts: Array<DataSource>): DataSource | undefined {
// Create the dispatcher, React garanty that this function is
// stable during the component lifecycle
const [source, _dispatch] = useState<DataSourceState>({ source: undefined });
useEffect(() => {
//Component DidMount
atts.forEach(a => {
a.addListener(_dispatch);
})
return () => {
//Component WillUnmont
atts.forEach(a => {
a.removeListener(_dispatch);
})
};
}, []);
console.log("useData() returned:" + util.inspect(source.source));
return source.source;
};
Hello,
I made a proposal for a new useDataHook() implementation in order to avoid to remove and readd listeners at each rendering and remove them only on willUnmout. The trick is to use directly the function returned by useState() in the listener list. React guaranty a stable handle of this function during component lifecycle. I removed the asynchronous mechanism and "loadable field" management there for clarity.