cyclejs / storage

A Cycle.js Driver for using localStorage and sessionStorage.
MIT License
51 stars 14 forks source link

Default value #12

Closed laszlokorte closed 8 years ago

laszlokorte commented 8 years ago

The behavior I proposed in #9 turned out be not as useful as I suspected. Using defaultIfEmpty does not work as I expected because the stream returned by getItem never terminates and is never "empty".

Propably I would be best to be able to pass an optional default value as second argument to getItem which get's emitted if no value is stored.

But as I am not very experienced in RX myself I think someone else should take a look at it before we change anything. Maybe there is another better solution.

kahlil commented 8 years ago

Sure this could be done but I am not sure why you would want this behavior. Can you elaborate with a use case?

laszlokorte commented 8 years ago

Hm.. maybe I am approaching it in a wrong way.

const counterValue = Storage.local
  .getItem('myCount') // request the stored value from the storage
  .map(parseInt) // convert it to an integer
  .take(1); // take only one value from the stream to prevent an feedback cycle with the later stored values

const myCounter = counter({
  DOM: DOM,
  initial$: counterValue, // pass the loaded value as initial value into the counter component
});

const storageRequests$ = myCounter
  .values$ // the stream of the current counter value
  .map((val) => ({ // request to store the current value of the counter
    key: 'myCount',
    value: val,
  }));

Now since no value is emitted anymore if no value is stored the counterValue stream never get's started. This leads to the counter never being rendered (as it depends on an initial value).

To start the sequence an initial value has to be injected into the counterValue stream.

const counterValue = Storage.local
  .getItem('myCount')
  .map(parseInt)
  .startWith(42) // injecting it before the take(1) call does not work because it would never take the stored value
  .take(1);
const counterValue = Storage.local
  .getItem('myCount')
  .map(parseInt)
  .take(1)  
  .startWith(42) // If injecting it after the take(1) does not work. I am not exactly sure why. I think the take(1) does not prevent the feedback cycle anymore
const counterValue = Storage.local
  .getItem('myCount')
  .map(parseInt)
  .take(1)  
  .defaultIfEmpty(42) // does not work because if no completion or error signal is sent the stream is not recognized as empty

The following is working:

const storageRequests$ = myCounter
  .values$
  .startWith(20) // injecting the default value into the storage requests
  .map((val) => ({
    key: 'myCount',
    value: val,
  }));

But this is not necessary what I want. I do not want any value to be stored until the user interacts with the counter.

What I would like to to is: Load a value from the store. If no value is stored used a default value. If the value get's changed by a user interaction save the new value to the store. No value should be stored until the user interacts with the component.

kahlil commented 8 years ago

Hmm this is interesting. As I understand this:

const counterValue = Storage.local
  .getItem('myCount')
  .map(parseInt)
  .take(1)  
  .startWith(42)

Should totally work, it has the desired output if you just use Rx. Would be interesting to find out why this does not work for you. Maybe @staltz has an idea?

staltz commented 8 years ago

.take(1).startWith(42) should work and should be the preferred way. Just debug why isn't it working.

laszlokorte commented 8 years ago

I got it working now. .take(1).startWith(42) is working indeed. The problem was a initial$.last() call inside my counter component which was used to get only the latest initial value:

const model = (initial$, actions) => {
  const initialValue$ = initial$.last(); // does not work since the the stream never terminates if no 
  const modifications$ = makeModification(actions);
  const value$ = initialValue$.merge(
    modifications$
  ).scan((prev, modFn) => modFn(prev));

  return value$.map(
    (value) => { return {value}; }
  );
};
kahlil commented 8 years ago

Great. Then I can close this issue, yes?