Created
April 27, 2021 13:12
-
-
Save jacques-blom/23c3514d43b1f5223d291245bf5abc32 to your computer and use it in GitHub Desktop.
Atom Effects 3
This file contains 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 characters
import {Button} from '@chakra-ui/button' | |
import {Input} from '@chakra-ui/input' | |
import {Box, Divider, Heading, VStack} from '@chakra-ui/layout' | |
import React, {useState} from 'react' | |
import { | |
atom, | |
atomFamily, | |
DefaultValue, | |
useRecoilCallback, | |
useRecoilState, | |
useRecoilValue, | |
useResetRecoilState, | |
} from 'recoil' | |
import {shoppingListAPI} from './fakeAPI' | |
type ItemType = { | |
label: string | |
checked: boolean | |
} | |
const idsState = atom<number[]>({ | |
key: 'ids', | |
default: [], | |
effects_UNSTABLE: [ | |
() => { | |
// TODO: Fetch a list of item ids from the server | |
}, | |
], | |
}) | |
const itemState = atomFamily<ItemType, number>({ | |
key: 'item', | |
default: {label: '', checked: false}, | |
effects_UNSTABLE: (id) => [ | |
() => { | |
// TODO: | |
// 1. Fetch individual item data from the API and initialise the atoms | |
// 2. Update/create individual item data via the API | |
}, | |
], | |
}) | |
export const AtomEffects = () => { | |
const ids = useRecoilValue(idsState) | |
const resetList = useResetRecoilState(idsState) | |
const nextId = ids.length | |
const insertItem = useRecoilCallback(({set}) => (label: string) => { | |
set(idsState, [...ids, nextId]) | |
set(itemState(nextId), {label, checked: false}) | |
}) | |
return ( | |
<Container onClear={() => resetList()}> | |
{ids.map((id) => ( | |
<Item key={id} id={id} /> | |
))} | |
<NewItemInput | |
onInsert={(label) => { | |
insertItem(label) | |
}} | |
/> | |
</Container> | |
) | |
} | |
const Container: React.FC<{onClear: () => void}> = ({children, onClear}) => { | |
return ( | |
<Box display="flex" flexDir="column" alignItems="center" pt={10}> | |
<Box width="400px" backgroundColor="yellow.100" p={5} borderRadius="lg"> | |
<Heading size="lg" mb={4}> | |
Shopping List | |
</Heading> | |
<VStack spacing={3} divider={<Divider borderColor="rgba(86, 0, 0, 0.48)" />}> | |
{children} | |
</VStack> | |
</Box> | |
<Button variant="link" mt={3} onClick={onClear}> | |
Clear list | |
</Button> | |
</Box> | |
) | |
} | |
type ItemProps = { | |
id: number | |
} | |
const Item = ({id}: ItemProps) => { | |
const [item, setItem] = useRecoilState(itemState(id)) | |
return ( | |
<Box | |
rounded="md" | |
textDecoration={item.checked ? 'line-through' : ''} | |
opacity={item.checked ? 0.5 : 1} | |
_hover={{textDecoration: 'line-through'}} | |
cursor="pointer" | |
width="100%" | |
onClick={() => setItem({...item, checked: !item.checked})} | |
> | |
{item.label} | |
</Box> | |
) | |
} | |
const NewItemInput = ({onInsert}: {onInsert: (label: string) => void}) => { | |
const [value, setValue] = useState('') | |
return ( | |
<Input | |
value={value} | |
placeholder="New item" | |
padding={0} | |
height="auto" | |
border="none" | |
_focus={{border: 'none'}} | |
_placeholder={{color: 'rgba(86, 0, 0, 0.48)'}} | |
onChange={(e) => { | |
setValue(e.currentTarget.value) | |
}} | |
onKeyPress={({key}) => { | |
if (key === 'Enter') { | |
onInsert(value) | |
setValue('') | |
} | |
}} | |
/> | |
) | |
} |
This file contains 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 characters
const randomDelay = () => { | |
return new Promise<void>((resolve) => { | |
setTimeout(resolve, randomIntBetween(500, 3000)) | |
}) | |
} | |
export const getWeather = async (zipCode: string) => { | |
await randomDelay() | |
if (!getWeatherCache[zipCode]) { | |
getWeatherCache[zipCode] = randomIntBetween(5, 35) | |
} else { | |
getWeatherCache[zipCode] += randomIntBetween(-1, 2) | |
} | |
return getWeatherCache[zipCode] | |
} | |
const getWeatherCache: Record<string, number> = {} | |
function randomIntBetween(min: number, max: number) { | |
return Math.floor(Math.random() * (max - min + 1) + min) | |
} | |
type ItemType = { | |
label: string | |
checked: boolean | |
} | |
class ShoppingListAPI { | |
items: Record<number, ItemType> | |
constructor() { | |
const persisted = localStorage.getItem('ShoppingListAPI') | |
if (persisted == null) { | |
this.items = {} | |
} else { | |
this.items = JSON.parse(persisted) | |
} | |
} | |
async getItems() { | |
await this.randomDelay('getItems') | |
return this.items | |
} | |
async getItem(id: number): Promise<ItemType | undefined> { | |
await this.randomDelay('getItem', id) | |
return this.items[id] | |
} | |
async createOrUpdateItem(id: number, item: ItemType) { | |
await this.randomDelay('createOrUpdateItem', id) | |
this.items[id] = item | |
this.persist() | |
} | |
async deleteItem(id: number) { | |
await this.randomDelay('deleteItem', id) | |
delete this.items[id] | |
this.persist() | |
} | |
private async randomDelay(name: string, param?: number) { | |
let label = `Fake Request: ${name}.` | |
if (param != null) label += ` id: ${param}` | |
await randomDelay() | |
console.log(`End ${label}`) | |
} | |
private persist() { | |
localStorage.setItem('ShoppingListAPI', JSON.stringify(this.items)) | |
} | |
} | |
export const shoppingListAPI = new ShoppingListAPI() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment