recoil

State management library from Facebook

Will it kill redux?

14/05/2020 - React europe

Dave McCabe aka @mcc_abe

State management

State management strategies

  • Local state
  • Context
  • Redux
  • Mobex
  • ReactQuery
  • +XXX number of libs

HOw is recoil different?

ATOMS

ATOMS

Selectors

ATOMS

Selectors

Components

CODE

const App = () => {
  return (
    <div>
      <Counter />
    </div>
  )
}

const Counter = () => {
  const [value, setValue] = useState(0)

  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}
const App = () => {
  return (
    <div>
      <Counter />
      <ByTwo />
    </div>
  )
}

const Counter = () => {
  const [value, setValue] = useState(0)

  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}

const ByTwo = ({ value }) => <div>{value * 2}</div>
const App = () => {
  const [value, setValue] = useState(0)

  return (
    <div>
      <Counter value={value} setValue={setValue} />
      <ByTwo value={value} />
    </div>
  )
}

const Counter = ({ value, setValue }) => {
  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}

const ByTwo = ({ value }) => <div>{value * 2}</div>
const App = () => {
  const [value, setValue] = useState(0)

  return (
    <div>
      <Counter value={value} setValue={setValue} />
      <ByTwo value={value} />
      <VeryExpensiveSubTreeToReRender>???</VeryExpensiveSubTreeToReRender>
    </div>
  )
}

const Counter = ({ value, setValue }) => {
  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}

const ByTwo = ({ value }) => <div>{value * 2}</div>
const Ctx = React.createContext()

const App = () => {
  const [value, setValue] = useState(0)

  return (
    <Ctx.Provider value={{ value, setValue }}>
      <div>
        <VeryExpensiveSubTreeToReRender>
          <Counter />
          <ByTwo />
        </VeryExpensiveSubTreeToReRender>
      </div>
    </Ctx.Provider>
  )
}

const Counter = () => {
  const { value, setValue } = useContext(Ctx)

  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}

const ByTwo = () => {
  const { value } = useContext(Ctx)
  return <div>{value * 2}</div>
}
const Ctx = React.createContext()

const VeryExpensiveSubTreeToReRender = React.memo(ExpensiveSubTree);

const App = () => {
  const [value, setValue] = useState(0)

  return (
    <Ctx.Provider value={{ value, setValue }}>
      <div>
        <VeryExpensiveSubTreeToReRender>
          <Counter />
          <ByTwo />
        </VeryExpensiveSubTreeToReRender>
      </div>
    </Ctx.Provider>
  )
}

const Counter = () => {
  const { value, setValue } = useContext(Ctx)

  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}

const ByTwo = () => {
  const { value } = useContext(Ctx)
  return <div>{value * 2}</div>
}
// const reducer = () => ...
const store = createStore({ reducer })

const App = () => {
  return (
    <Provider store={store}>
      <div>
        <Counter />
        <ByTwo />
        <VeryExpensiveSubTreeToReRender>???</VeryExpensiveSubTreeToReRender>
      </div>
    </Provider>
  )
}

const Counter = () => {
  const value = useSelector((state) => state.value);
  const dispatch = useDispatch();

  return (
    <>
      <div>{value}</div>
      <button onClick={() => dispatch(setValue(value + 1))}>+</button>
    </>
  )
}

const ByTwo = () => {
  const value = useSelector((state) => state.value);
  return <div>{value * 2}</div>
}
const App = () => {
  return (
    <div>
      <Counter />
      <ByTwo />
    </div>
  )
}

const Counter = () => {
  const [value, setValue] = useState(0)

  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}

const ByTwo = ({ value }) => <div>{value * 2}</div>
import { RecoilRoot } from "recoil"

const App = () => {
  return (
    <RecoilRoot>
      <div>
        <Counter />
        <ByTwo />
      </div>
    </RecoilRoot>
  )
}

const Counter = () => {
  const [value, setValue] = useState(0)

  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}

const ByTwo = ({ value }) => <div>{value * 2}</div>
import { RecoilRoot, atom } from "recoil"

const counter = atom({
  key: "counter",
  default: 0
})

const App = () => {
  return (
    <RecoilRoot>
      <div>
        <Counter />
        <ByTwo />
      </div>
    </RecoilRoot>
  )
}

const Counter = () => {
  const [value, setValue] = useState(0)

  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}

const ByTwo = ({ value }) => <div>{value * 2}</div>
import { RecoilRoot, atom, useRecoilState } from "recoil"

const counter = atom({
  key: "counter",
  default: 0
})

const App = () => {
  return (
    <RecoilRoot>
      <div>
        <Counter />
        <ByTwo />
      </div>
    </RecoilRoot>
  )
}

const Counter = () => {
  const [value, setValue] = useRecoilState(counter)

  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}

const ByTwo = ({ value }) => <div>{value * 2}</div>
import { RecoilRoot, atom, useRecoilState, useRecoilValue } from "recoil"

const counter = atom({
  key: "counter",
  default: 0
})

const App = () => {
  return (
    <RecoilRoot>
      <div>
        <Counter />
        <ByTwo />
      </div>
    </RecoilRoot>
  )
}

const Counter = () => {
  const [value, setValue] = useRecoilState(counter)

  return (
    <>
      <div>{value}</div>
      <button onClick={() => setValue(prev => prev + 1)}>+</button>
    </>
  )
}

const ByTwo = () => {
  const value = useRecoilValue(counter);
  return <div>{value * 2}</div>
}
import { RecoilRoot, atom, useRecoilState, useRecoilValue, selector } from "recoil"

const counter = atom({
  key: "counter",
  default: 0
})

const byTwo = selector({
  key: "byTwo",
  get: ({ get }) => {
     const counterValue = get(counter);
     return counterValue * 2
  }
})

// App
// Counter

const ByTwo = () => {
  const value = useRecoilValue(counter);
  return <div>{value * 2}</div>
}
import { RecoilRoot, atom, useRecoilState, useRecoilValue, selector } from "recoil"

const counter = atom({
  key: "counter",
  default: 0
})

const byTwo = selector({
  key: "byTwo",
  get: ({ get }) => {
     const counterValue = get(counter);
     return counterValue * 2
  }
})

// App
// Counter

const ByTwo = () => {
  const value = useRecoilValue(byTwo);
  return <div>{value}</div>
}

Async

const currentUserNameQuery = selector({
  key: 'CurrentUserName',
  get: async ({get}) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <React.Suspense fallback={<div>Loading...</div>}>
        <CurrentUserInfo />
      </React.Suspense>
    </RecoilRoot>
  );
}

Is it production ready?

Will it kill redux?

Arguments against redux

  • Learning curve
  • Too much boilerplate
  • Not React libraries (concurrent mode?)
  • 3rd party libs for cache/async

Is it worth to check?

Is it worth to observe the recoil?

Pros

  • More Reactish (useState -> useRecoilState)
  • Easier learning curve
  • Performance
  • Concurrent mode support (soon)

cons

  • React only (hooks only)
  • Complicated Async API
  • Not production ready - yet...

Thank you

recoil

By Przemek Suchodolski