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

Feature: useState(..) #22

Open
getify opened this issue Apr 9, 2022 · 2 comments
Open

Feature: useState(..) #22

getify opened this issue Apr 9, 2022 · 2 comments
Assignees
Labels
enhancement New feature or request

Comments

@getify
Copy link
Owner

getify commented Apr 9, 2022

Taking inspiration from Hooks (i.e., React), a state preserving mechanism like useState(..) is proposed.

You could use it in do-routines like this:

IO.do(component).run({});

function *component(viewContext) {
	var [ id, updateID ] = yield useState(() => Math.floor(Math.random() * 1000));
	
	console.log(`random ID: ${id}`);
	
	id = yield updateID(42);
	
	console.log(`fixed ID: ${id}`);
}

There are some unresolved issues with such a mechanism:

  • The nextSlotIdx needs some way to be reset between invocations of the component(..)
  • State is by default shared among all invocations in the same view-context... perhaps need a more explicit mechanism for forcing a sub-view-context for encapsulating the state
  • Since this uses numeric slot indexing (as React/etc does), there's no clear way to share/access state by some unique name. State sharing is an important requirement, so need to figure out to design that into this mechanism. Perhaps an optional ID that overrides using numeric slot indexing.

Here's a candidate first implementation:

function useState(initialVal) {
	return IO((viewContext = {}) => {
		var { state = {}, } = viewContext;
		viewContext.state = state;
		var { nextSlotIdx = 0, slots = [], } = viewContext.state;
		state.nextSlotIdx = nextSlotIdx;
		state.slots = slots;
		var curSlotIdx = state.nextSlotIdx++;

		if (!(curSlotIdx in slots)) {
			if (typeof initialVal == "function") {
				initialVal = initialVal();
			}
			slots[curSlotIdx] = initialVal;
		}
		return [ slots[curSlotIdx], function update(nextVal){
			return IO(({ state: { slots, }, }) => {
				if (typeof nextVal == "function") {
					nextVal = nextVal(slots[curSlotIdx]);
				}
				return (slots[curSlotIdx] = nextVal);
			});
		}];
	});
}
@getify getify added the enhancement New feature or request label Apr 9, 2022
@getify getify self-assigned this Apr 9, 2022
@getify
Copy link
Owner Author

getify commented Apr 11, 2022

Here's an alternate approach that I think addresses some of the above limitations:

doWithState(component).run({});

function *component({ useState, }) {
	var [ id, updateID ] = yield useState(() => Math.floor(Math.random() * 1000));
	
	console.log(`random ID: ${id}`);
	
	id = yield updateID(42);
	
	console.log(`fixed ID: ${id}`);
}

And the implementation of doWithState(..):

function doWithState(gen,...args) {
	var state = { nextSlotIdx: 0, slots: [], };
	
	return IO(env => {
		state.nextSlotIdx = 0;
		var context = { ...env, useState, };
		return IO.do(gen,...args).run(context);
	});

	// ********************************************

	function useState(initialVal) {
		return IO(env => {
			var slots = state.slots;
			var curSlotIdx = state.nextSlotIdx++;

			if (!(curSlotIdx in slots)) {
				if (typeof initialVal == "function") {
					initialVal = initialVal();
				}
				slots[curSlotIdx] = initialVal;
			}
			return [
				slots[curSlotIdx],
				
				function update(nextVal){
					return IO(() => {
						if (typeof nextVal == "function") {
							nextVal = nextVal(slots[curSlotIdx]);
						}
						return (slots[curSlotIdx] = nextVal);
					});
				},
			];
		});
	}
}

@getify
Copy link
Owner Author

getify commented Apr 11, 2022

A further refinement to the doWithState(..) approach, that seems to solve all the remaining issues as listed earlier... provides a useState(..) for accessing private state via numerically indexed slots, and useSharedState for accessing shared state via named slots, both of which are still fully self-contained in the provided IO reader-env object:

doWithState(component).run({});

function *component({ useState, useSharedState, }) {
	var [ id, updateID ] = yield useState(() => Math.floor(Math.random() * 1000));
	var [ greeting, updateGreeting ] = yield useSharedState("greeting","Hello World!");
	
	console.log(`random ID: ${id}`);
	
	id = yield updateID(42);
	
	console.log(`fixed ID: ${id}`);

	console.log(greeting);
}

And this implementation:

const privateStateKeys = new WeakMap();
const SHARED_STATE = Symbol("shared-state");

function doWithState(gen,...args) {
	return withState(IO.do(gen,...args),gen);
}

function doXWithState(gen,deps,...args) {
	return withState(IOx.do(gen,deps,args),gen);
}

function withState(io,gen) {
	if (!privateStateKeys.has(gen)) {
		privateStateKeys.set(gen,Symbol(`private-state:${gen.name || gen.toString()}`));
	}
	const PRIVATE_STATE = privateStateKeys.get(gen);
	
	return IO(env => {
		var {
			[PRIVATE_STATE]: privateState = { nextSlotIdx: 0, slots: [], },
			[SHARED_STATE]: sharedState = {},
		} = env;
		env[PRIVATE_STATE] = privateState;
		env[SHARED_STATE] = sharedState;
		
		privateState.nextSlotIdx = 0;
		return io.run({
			...env,
			useState,
			useSharedState,
		});
	});

	// ********************************************

	function useState(initialVal) {
		return IO(({ [PRIVATE_STATE]: privateState, }) => {
			return accessState(
				privateState.slots,
				privateState.nextSlotIdx++,
				initialVal,
				env => env[PRIVATE_STATE].slots
			);
		});
	}
}

function useSharedState(stateName,initialVal) {
	return IO(({ [SHARED_STATE]: sharedState, }) => {
		return accessState(
			sharedState,
			stateName,
			initialVal,
			env => env[SHARED_STATE]
		);
	});
}

function accessState(stateStore,prop,initialVal,getStateStore) {
	if (!(prop in stateStore)) {
		if (typeof initialVal == "function") {
			initialVal = initialVal();
		}
		stateStore[prop] = initialVal;
	}
	return [
		stateStore[prop],
		
		function update(nextVal){
			return IO(env => {
				var stateStore = getStateStore(env);
				if (typeof nextVal == "function") {
					nextVal = nextVal(stateStore[prop]);
				}
				return (stateStore[prop] = nextVal);
			});
		}
	];
}

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

No branches or pull requests

1 participant