Recoiljs: fresh state management
Yes, it will never end, and this is so new, that their site has empty pages. But from a fast check, Recoil looks like light white state management that provides the benefits of a shared state without the ungainly of redux. Interestingly, it came out exactly at the moment I was looking for something like that. So I have taken it for a ride.
Recoil API’s and explanation will come after the hangman #notFancyAppChallenge. Just note that as the Recoil website is still unfilled, and has plenty of empty pages, this #notFancyAppChallenge will also not explain every API and util.
Recoil hangman
Recoil Data objects. I marked the Recoil keywords and usage.
const gameBoard = atom({ key: 'gameBoard', default: { word: getRandomWord(), selectedLetters: ['d','f','g'] } })
const availableLettersList = selector({ key: 'AvailableLettersList', get: ({get}) => { const board = get(gameBoard); const selected = board.selectedLetters; const selectedWord = selected.join(); const letters = 'abcdefghijklmnopqrstuvwxyz'.split(''); return letters.filter(l => selectedWord.indexOf(l) == -1); } })
Some helper JS functions
const turns = 7; const getRandomWord = () => { const words = ['Recoil demo','not so fancy app challenge', 'will it work','will i use it']; return words[Math.round(Math.random()*words.length)]; }
The React components:
const App = () => { return ( <RecoilRoot> <Word /> <SelectedLetters /> <AvailableLetters /> <Reset /> </RecoilRoot> ) } const Word = () => { const board = useRecoilValue(gameBoard); const selected = board.selectedLetters.join(''); const theWord = board.word.split('').map(l =>{ return l == ' ' || selected.indexOf(l) > -1? l:'_'; }) return <>The word: {theWord}</> } const SelectedLetters = () => { const board = useRecoilValue(gameBoard); return ( <p> Selected: {board.selectedLetters.join(', ')}, {new Array(turns - board.selectedLetters.length).fill('_').join(', ')} </p> ) } const AvailableLetters = () => { const letters = useRecoilValue(availableLettersList); const [board,setBoard] = useRecoilState(gameBoard); const clickLetter = (l)=> { if(turns > board.selectedLetters.length){ setBoard({ word: board.word, selectedLetters: [l,...board.selectedLetters] }); } } return ( <p> Select: {letters.map(l => <b key={l} onClick={() => {clickLetter(l)}}>{l}</b>)}, </p> ) } const Reset = () => { const [game, resetGame] = useRecoilState(gameBoard); const reset = () => { resetGame({ word: getRandomWord(), selectedLetters: [] }) } return <button onClick={reset}>reset</button> }
Installation:
npm install recoil yarn add recoil
RecoilRoot
To use Recoil, you need to wrap the components with a Recoil element. Somewhere in the docs they mention that you can define the constructor for the Aroms used according to the root.
function App = () => { return ( <RecoilRoot> <SomeStuffInside /> </RecoilRoot> ); }
Atom
We need a name for these pieces of state we’re gonna call them Adams. So, an atom is a changeable subscribable unit of state.
David McCabe: Recoil at React Europe 2020
Atom is the basic building block of Recoil, and presents the data that changes and need to be observed along with the app.
While defining atom you need to provide a unique key, and can define the default value.
const userName = atom({ key: 'userName', default: 'Jone Smith', });
You can use objects and lists. But the advantage of using separate atoms for each element over a single atom with a map, is that small atoms maintain their own individual subscriptions. So, updating the value for one element will only cause React components that have subscribed to just that atom to update.
const hangmanBoard = atom({ key: 'hangmanBoard', default: { word: '', usedLetters: [] }, });
Selector: the Recoil computed value
Selector is basically a pure function plus information about what state it depends on so that when that state changes we can recompute the function and recompute and rerender any components that depend on it
David McCabe: Recoil at React Europe 2020
const userHiddenName = selector({ key: 'userHiddenName', get: ({get}) => { const userName = get(userName); return Array(userName.length).fill('_').join('') }, //set: ({set}, newValue) => { set(userName,'someValue');} })
Async selectors
const fetchSomeData = selector({ key: 'fetchSomeData', get: async ({get}) => { const response = await fetch(... return response; } });
isRecoilValue
Used to chack if a value is a Recoil object
const recoilAtom = atom(... const recoilSelector = selector(... isRecoilValue(recoilAtom); // true isRecoilValue(recoilSelector); // true
Hooks:
useRecoilValue
Used to observe an atom or selector:
const SomeStuffInside = () =>{ const userName = useRecoilValue(userName); return ( <b onClick={hideName}>You name is {setName}</b> ) }
useRecoilState
As useState in React, a getter and setter for the atom:
const SomeStuffInside = () =>{ const [userName,setName] = useRecoilState(userName); const hideName = () => { setName('Im a ghost'); } return ( <b onClick={hideName}>You name is {userName}</b> ) }
And as in the other observables, to update a list or an object, you need to recreate it:
const Letter = (props) => { const letter = props.letter; const [hangmanBoard,resetBoard] = useRecoilState(hangmanBoard); const addLetter = () => { resetBoard({ word: hangmanBoard.word, usedLetters: [letter,...hangmanBoard.usedLetters] }); } return ( <span onClick={addLetter}>{letter}</span> ) }
useSetRecoilState
“Returns a setter function for updating the value of writeable Recoil state.”
useResetRecoilState
Returns a function that will reset the value of the given state to its default value.
useRecoilValueLoadable
“Used for reading the value of asynchronous selectors. This hook will implicitly subscribe the component to the given state”
useRecoilStateLoadable
useRecoilCallback
useTransactionalObservation
While the hooks above are mentioned on the Repoid site, sometimes with an empty page. This one I had seen it mentioned only in the video, it didn’t appear on the Recoil site :-/. Same as the withStateChange
Recoil utils
elementPositionStateFamily
Returns a function that returns a Recoil atom. Same as Atom but the default value can also be parameterized.
const newGameBoard = atomFamily({ key: someUniquKey, default: param => someFunctionToSetTheDefaultValue(param) }); function newWord({wordText}) { const gameBoard = newGameBoard(wordText,wordText); return ...
selectorFamily
Similar to the selector, but allows you to pass parameters to the get and set callbacks of a selector.
constSelector
errorSelector
noWait
waitForAll
waitForAny
waitForNone
Conclusion
As written above, It came out exactly while I needed something like this. Although the FED community is very tired of the new libraries and frameworks that popup every day, Recoil seems smooth and simple, still is very strong and powerful. Let give it a day or two to settle down and fill the official website. Then it really might just be the missing piece we needed.