Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.68.0
- Add `objectLens`
- Add `arrayLens`

# 1.67.0
- Improvements to `unwind`: use `_.castArray` to avoid unwinding strings and other array-likes
- Add `unwindArray`
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,10 @@ Returns a function that will set a lens to `false`
`value -> arrayLens -> includeLens`
An include lens represents membership of a value in a set. It takes a value and lens and returns a new lens - kind of like a "writeable computed" from MobX or Knockout. The view and set functions allow you to read and write a boolean value for whether or not a value is in an array. If you change to true or false, it will set the underlying array lens with a new array either without the value or with it pushed at the end.

#### optionLens
`value -> arrayLens -> optionLens`

@stellarhoof stellarhoof Mar 4, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This signature doesn't match the usage in the test: F.optionLens('a', 'selected', object)

Also shouldn't this be under domLens?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not a dom lens, it's a higher-order lens - it belongs in the same category as includeLens, since it's the same kind of thing.

And yeah, you're right. I just copied the signature from includeLens, but I guess it's wrong for both 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the difference, to clarify, is that the higher-order lenses includeLens and optionLens take a lens and return a lens, while the domLens methods take a lens and return an object mapping DOM properties to functions that manipulate the lens)

Like `includeLens`, `optionLens` takes a value and lens and returns a new lens. The view and set functions allow you to read and write a boolean value for whether the value passed to the `optionLens` is currently selected (meaning whether it matches the value stored in the underlying lens). Setting the `optionLens` to true "selects" its value (sets the value of the underlying lens to it); setting it to false sets the underlying value to `null`.

#### domLens.value
`lens -> {value, onChange}` Takes a lens and returns a value/onChange pair that views/sets the lens appropriately. `onChange` sets with `e.target.value` (or `e` if that path isn't present).
Example:
Expand Down Expand Up @@ -676,6 +680,9 @@ Takes a value and returns a function lens for that value. Mostly used for testin
### objectLens
Takes a value and returns a object lens for that value. Mostly used for testing and mocking purposes.

### arrayLens
Takes a value and returns an array lens for that value. Mostly used for testing and mocking purposes.

### stateLens
`([value, setValue]) -> lens` Given the popularity of React, we decided to include this little helper that converts a `useState` hook call to a lens. Ex: `let lens = stateLens(useState(false))`. You generally won't use this directly since you can pass the `[value, setter]` pair directly to lens functions

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "futil-js",
"version": "1.67.0",
"version": "1.68.0",
"description": "F(unctional) util(ities). Resistance is futile.",
"main": "lib/futil-js.js",
"scripts": {
Expand Down
12 changes: 12 additions & 0 deletions src/lens.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export let objectLens = val => ({
val = x
},
})
export let arrayLens = val => {
let result = [val]
result.push(x => {
result[0] = x
})
return result
}

// Lens Conversion
export let fnToObj = fn => ({
Expand Down Expand Up @@ -52,6 +59,11 @@ export let includeLens = (value, ...lens) => ({
set: x => set(_.uniq(toggleElementBy(!x, value, view(...lens))), ...lens),
})

export let optionLens = (value, ...lens) => ({
get: () => _.equals(value, view(...lens)),
set: x => set(x ? value : null, ...lens),
})

// Lens Manipulation
//let construct = (...lens) => (lens[1] ? lensProp(...lens) : lens[0])
let lensPair = (get, set) => ({ get, set })
Expand Down
30 changes: 22 additions & 8 deletions test/lens.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ describe('Lens Functions', () => {
l.set(5)
expect(l.get()).to.equal(5)
})
it('arrayLens', () => {
let l = F.arrayLens(1)
expect(l[0]).to.equal(1)
l[1](5)
expect(l[0]).to.equal(5)
})
})
describe('Conversion', () => {
it('fnToObj', () => {
Expand Down Expand Up @@ -78,6 +84,21 @@ describe('Lens Functions', () => {
expect(F.view(includesB)).to.be.true
expect(object.arr).to.deep.equal(['a', 'c', 'd', 'b'])
})
it('optionLens', () => {
let object = { selected: 'a' }
let optionA = F.optionLens('a', 'selected', object)
let optionB = F.optionLens('b', 'selected', object)
expect(F.view(optionA)).to.be.true
expect(F.view(optionB)).to.be.false
F.on(optionB)()
expect(F.view(optionA)).to.be.false
expect(F.view(optionB)).to.be.true
expect(object.selected).to.equal('b')
F.off(optionB)()
expect(F.view(optionA)).to.be.false
expect(F.view(optionB)).to.be.false
expect(object.selected).to.deep.equal(null)
})
})
describe('Manipulation', () => {
it('view', () => {
Expand Down Expand Up @@ -196,14 +217,7 @@ describe('Lens Functions', () => {
})
describe('additional implicit lens formats', () => {
it('arrayLens', () => {
let arrayLens = val => {
let result = [val]
result.push(x => {
result[0] = x
})
return result
}
let lens = arrayLens(false)
let lens = F.arrayLens(false)
F.on(lens)()
expect(lens[0]).to.be.true
F.off(lens)()
Expand Down