Last active
June 8, 2023 16:45
-
-
Save jamesarosen/d380b6a73da0d695d1f7df01b95d02f7 to your computer and use it in GitHub Desktop.
Revisions
-
jamesarosen revised this gist
Nov 23, 2019 . 1 changed file with 4 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -84,18 +84,17 @@ For applications that _do_ use Redux, we can partially apply the function in a r // reducers/helpers.js import imageUrlForRoot from 'lib/image-url' const defaultState = { imageUrl: imageUrlForRoot(null) } export function helpers(state = defaultState, action) { switch (action.type) { case 'RECEIVE_CONSTANTS': return { ...state, imageUrl: imageUrlForRoot(action.payload.imagesRoot), } default: return state } } ``` -
jamesarosen revised this gist
Nov 23, 2019 . 1 changed file with 5 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -89,10 +89,13 @@ export function helpers(state = {}, action) { case 'RECEIVE_CONSTANTS': return { ...state, imageUrl: imageUrlForRoot(action.payload.imagesRoot), } default: return { ...state, imageUrl: state.imageUrl || imageUrlForRoot(null), } } } ``` -
jamesarosen revised this gist
Nov 23, 2019 . 1 changed file with 1 addition and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -115,5 +115,6 @@ export default function Image({ imageId, alt, size }) { - **Con**: Requires the JS function plus an action-creator and a reducer - **Con**: Can't look at the `import` statements at the top of the file to find function dependencies - **Con**: Not a known pattern. - **Con**: Temporal coupling in the Redux version. If some root component doesn't dispatch an action that causes `RECEIVE_CONSTANTS`, all uses of `imageUrl` will return `null`. Have you done this? Do you like the idea of putting partially-applied functions into the Redux store? Do you have a better alternative? -
jamesarosen revised this gist
Nov 23, 2019 . 1 changed file with 1 addition and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -113,6 +113,7 @@ export default function Image({ imageId, alt, size }) { - **Pro**: Very easy to test - **Pro**: Usable with or without Redux - **Con**: Requires the JS function plus an action-creator and a reducer - **Con**: Can't look at the `import` statements at the top of the file to find function dependencies - **Con**: Not a known pattern. Have you done this? Do you like the idea of putting partially-applied functions into the Redux store? Do you have a better alternative? -
jamesarosen revised this gist
Nov 23, 2019 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ I have a function that generates image URLs. This function combines some relatively static global configuration with some dynamic data that changes on every invocation. I say "relatively static" because the configuration is loaded asynchronously during the application boot, but remains fixed after that. ### Option One ```js export default async function imageUrl(imageId, { size = 'normal' }) { @@ -18,7 +18,7 @@ export default async function imageUrl(imageId, { size = 'normal' }) { - **Con**: If `GET /api/constants` isn't cached in the browser, it's terrible for rendering performance. - **Con**: Testing image URL generation requires stubbing an HTTP API. ### Option Two An alternative would be to take the root as an argument: @@ -36,7 +36,7 @@ export default function imageUrl(imageId, { size = 'normal', imagesRoot }) { - **Con**: requires passing the `imagesRoot` around all over the application. - **Con**: May end up with a performance problem if multiple components do the `fetch('/api/constants')` call and don't cache the result. ### Option Three We could have our JavaScript function reach out to some global state: @@ -60,7 +60,7 @@ export default function imageUrl(imageId, { size = 'normal' }) { - **Con**: Tied to Redux. In fact, it's tied to an instance of the Redux store being available at `my/redux/store` - **Con**: Temporal coupling. If you call the function before dispatching the `fetchConstants` action that populates `state.constants.imagesRoot`, you get `null` back. ### Option Four We can keep the function _pure_ by using Currying: -
jamesarosen revised this gist
Nov 23, 2019 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -81,10 +81,10 @@ imgTag.src = imageUrlForRoot('https://example.com/images')('cheese.png') For applications that _do_ use Redux, we can partially apply the function in a reducer: ```js // reducers/helpers.js import imageUrlForRoot from 'lib/image-url' export function helpers(state = {}, action) { switch (action.type) { case 'RECEIVE_CONSTANTS': return { @@ -102,7 +102,7 @@ And consume it in a component: import { useSelector } from 'react-redux' export default function Image({ imageId, alt, size }) { const imageUrl = useSelector('helpers.imageUrl') const src = imageUrl(imageId, { size }) return <img alt={alt} src={src} /> } -
jamesarosen revised this gist
Nov 23, 2019 . 1 changed file with 7 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -108,4 +108,11 @@ export default function Image({ imageId, alt, size }) { } ``` - **Pro**: Fast - **Pro**: No `async` - **Pro**: Very easy to test - **Pro**: Usable with or without Redux - **Con**: Requires the JS function plus an action-creator and a reducer - **Con**: Not a known pattern. Have you done this? Do you like the idea of putting partially-applied functions into the Redux store? Do you have a better alternative? -
jamesarosen created this gist
Nov 23, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,111 @@ I have a function that generates image URLs. This function combines some relatively static global configuration with some dynamic data that changes on every invocation. I say "relatively static" because the configuration is loaded asynchronously during the application boot, but remains fixed after that. h3. Option One ```js export default async function imageUrl(imageId, { size = 'normal' }) { if (imageId == null) return null const constantsResponse = await fetch('/api/constants') const imagesRoot = constantsResponse.json().imagesRoot return `${imagesRoot}/${imageId}?size=${size}` } ``` - **Pro**: This has great cohesion -- everything required for generating image URLs is all expressed in one place. - **Con**: The `async` nature makes this hard to use in components. - **Con**: If `GET /api/constants` isn't cached in the browser, it's terrible for rendering performance. - **Con**: Testing image URL generation requires stubbing an HTTP API. h3. Option Two An alternative would be to take the root as an argument: ```js export default function imageUrl(imageId, { size = 'normal', imagesRoot }) { if (imageId == null || imagesRoot == null) return null return `${imagesRoot}/${imageId}?size=${size}` } ``` - **Pro**: Fast - **Pro**: No `async` - **Pro**: Easy to test - **Con**: requires passing the `imagesRoot` around all over the application. - **Con**: May end up with a performance problem if multiple components do the `fetch('/api/constants')` call and don't cache the result. h3. Option Three We could have our JavaScript function reach out to some global state: ```js import store from 'my/redux/store' export default function imageUrl(imageId, { size = 'normal' }) { if (imageId == null) return null const state = store.getState() const imagesRoot = state && state.constants && state.constants.imagesRoot if (imagesRoot == null) return null return `${imagesRoot}/${imageId}?size=${size}` } ``` - **Pro**: Fast - **Pro**: No `async` - **Pro**: Pretty easy to test by injecting state into the `store` - **Con**: Tied to Redux. In fact, it's tied to an instance of the Redux store being available at `my/redux/store` - **Con**: Temporal coupling. If you call the function before dispatching the `fetchConstants` action that populates `state.constants.imagesRoot`, you get `null` back. h3. Option Four We can keep the function _pure_ by using Currying: ```js export default function imageUrlForRoot(imagesRoot) { return function imageUrl(imageId, { size = 'normal' }) { if (imagesRoot == null || imageId == null) return null return `${imagesRoot}/${imageId}?size=${size}` } } ``` Applications that don't use Redux can still use it: ```js imgTag.src = imageUrlForRoot('https://example.com/images')('cheese.png') ``` For applications that _do_ use Redux, we can partially apply the function in a reducer: ```js // reducers/constants.js import imageUrlForRoot from 'lib/image-url' export function constants(state = {}, action) { switch (action.type) { case 'RECEIVE_CONSTANTS': return { ...state, imageUrl: imageUrlForRoot(action.payload.imagesRoot) } default: return state } } ``` And consume it in a component: ```jsx // Image.jsx import { useSelector } from 'react-redux' export default function Image({ imageId, alt, size }) { const imageUrl = useSelector('constants.imageUrl') const src = imageUrl(imageId, { size }) return <img alt={alt} src={src} /> } ``` Have you done this? Do you like the idea of putting partially-applied functions into the Redux store? Do you have a better alternative?