Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to reset recursive time driver #272

Open
mrEvgenX opened this issue Sep 18, 2020 · 3 comments
Open

How to reset recursive time driver #272

mrEvgenX opened this issue Sep 18, 2020 · 3 comments

Comments

@mrEvgenX
Copy link

I need to increase counter by timer, then go to another view goSeeingCounted to see result for further processing.
Seems recursed timer is unstoppable. Any help?

Example of code to be fixed (playground):

const { div, p, button } = Moon.view.m;

const goSeeingCounted = ({ data }) => {
  return {
	  data,
	  time: undefined,
	  view: <viewSeeCounted counter=data.counter/>
	};
};

const viewCounter = ({ counter }) => (
	<div>
		<p>{counter}</p>
		<button @click=goSeeingCounted>Create</button>
	</div>
);

const viewSeeCounted = ({ counter }) => (
	<div>
		<p>Result: {counter}</p>
	</div>
);

const tick = ({data}) => {
  let newData = {
	counter: 0
  }
  if (data !== undefined) {
	newData.counter = data.counter + 1;
  }  
  return {
	data: newData,
	time: { 1000: tick },
    view: <viewCounter counter=newData.counter/>
  };
}

Moon.use({
	data: Moon.data.driver,
  	time: Moon.time.driver,
	view: Moon.view.driver("#root")
});

Moon.run(tick);
@kbrsh
Copy link
Owner

kbrsh commented Sep 19, 2020

This is a good exercise. Thanks for posting it, I think some revisions I have of Moon's API can help the ergonomics of things like this. The main problem is that tick outputs to a driver which sets a timeout, so setting time: undefined won't cancel any previous timeouts already set. The logic will have to go into tick or another function. Here's a quick way I was able to get it working:

const { div, p, button }
	= Moon.view.m;

const goSeeingCounted = ({ data }) => ({
	data: {...data, isTicking: false},
	view: <viewSeeCounted counter=data.counter/>
});

const viewCounter = ({ counter }) => (
	<div>
		<p>{counter}</p>
		<button @click=goSeeingCounted>Create</button>
	</div>
);

const viewSeeCounted = ({ counter }) => (
	<div>
		<p>Result: {counter}</p>
	</div>
);

const tick = ({data}) => {
	const newData = {...data, counter: data.counter + 1};
	return {
		data: newData,
		time: {1000: main},
		view: <viewCounter counter=newData.counter/>
	}
};

const main = (input) => {
  	const data = input.data === undefined ?
		{counter: 0, isTicking: true} :
		input.data;
  	const newInput = {...input, data};
	return data.isTicking ? tick(newInput) : goSeeingCounted(newInput);
};

Moon.use({
	data: Moon.data.driver,
  	time: Moon.time.driver,
	view: Moon.view.driver("#root")
});

Moon.run(main);

Playground

@mrEvgenX
Copy link
Author

Thanks for your solution. It look quite well and works nice. But another problem begins if I increase delay up to, say, 3 seconds and add the next step. Please, take a look at an example in playground. Try to click "Create" and "Get thanks" whithin 3 seconds.

I think I'm able to fix it now. But I'm sure, a solution for this is gonna look not so nice and pretty. What do you think?

And I'm sure it should be mentioned somewhere on moonjs.org, may be in guide time or examples. May be I can help with it, are moonjs.org's sources available for PRs?

@kbrsh
Copy link
Owner

kbrsh commented Sep 29, 2020

@mrEvgenX Ah I see, you're right, this is lacking too much elegance for my liking as well. First off, I'd like to thank you for both creating and extending this issue — it has pushed me to rethink of how Moon's functional approach can be used more practically. This is a perfect candidate for a new app structure and API I've been conceptualizing for the past few weeks. I tweeted a code sample that used this approach a few days ago (except with a React driver), so I'm excited to see how it can be applied here!

Basically, you structure an application with two transformations: input to state and state to output. Check this solution out:

const { div, p, button }
	= Moon.view.m;

const go = page => input => main({
	...input.data,
	page
});

const increment = input => 
	input.data.page === "index" ?
		main({
			...input.data,
			counter: input.data.counter + 1
		}) :
		main(input.data);

const pageIndex = state => ({
	data: state,
	time: {3000: increment},
	view: (
		<div>
			<p>{state.counter}</p>
			<button @click=(go("counted"))>Create</button>
		</div>
	)
});

const pageCounted = state => ({
	data: state,
	view: (
		<div>
			<p>Result: {state.counter}</p>
			<button @click=(go("thanks"))>Get Thanks</button>
		</div>
	)
});

const pageThanks = state => ({
	data: state,
	view: (
		<div>
			<p>Thank you</p>
		</div>
	)
});

const main = state => {
	switch (state.page) {
		case "index": return pageIndex(state);
		case "counted": return pageCounted(state);
		case "thanks": return pageThanks(state);
	}
};

Moon.use({
	data: Moon.data.driver,
  	time: Moon.time.driver,
	view: Moon.view.driver("#root")
});

Moon.run(input => main({
	page: "index",
	counter: 0
}));

Playground

In this case, any event handlers transform from input to state and then hand it off to main, which transforms state back into output. With this, event handlers and the initial run are responsible for the dirty work of converting inputs to a state that reflects an application much more accurately. Then, the main function can transform it back to driver outputs.

As I work on a few changes to the API and codebase structure, I'll be adding this methodology to the docs. If you'd like, you can try adding documentation; the code is on the gh-pages branch.

Let me know what you think!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants