GithubHelp home page GithubHelp logo

facebookexperimental / recoil Goto Github PK

View Code? Open in Web Editor NEW
19.4K 199.0 1.2K 48.68 MB

Recoil is an experimental state management library for React apps. It provides several capabilities that are difficult to achieve with React alone, while being compatible with the newest features of React.

Home Page: https://recoiljs.org/

License: MIT License

JavaScript 96.56% TypeScript 3.16% HTML 0.22% CSS 0.06%

recoil's Introduction

Recoil · NPM Version Node.js CI GitHub license Follow on Twitter

Recoil is an experimental state management framework for React.

Website: https://recoiljs.org

Documentation

Documentation: https://recoiljs.org/docs/introduction/core-concepts

API Reference: https://recoiljs.org/docs/api-reference/core/RecoilRoot

Tutorials: https://recoiljs.org/resources

Installation

The Recoil package lives in npm. Please see the installation guide

To install the latest stable version, run the following command:

npm install recoil

Or if you're using yarn:

yarn add recoil

Or if you're using bower:

bower install --save recoil

Contributing

Development of Recoil happens in the open on GitHub, and we are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving Recoil.

License

Recoil is MIT licensed.

recoil's People

Contributors

aaronabramov avatar bigfootjon avatar bradzacher avatar bsouthga avatar csantos42 avatar csantos4242 avatar davidmccabe avatar dependabot[bot] avatar derekxu16 avatar drarmstr avatar drizzlecrj avatar dsainati1 avatar gkz avatar habond avatar jacques-blom avatar jbrown215 avatar kassens avatar kevinfrei avatar mallchel avatar miorel avatar mondaychen avatar mroch avatar mvitousek avatar naturalclar avatar pieterv avatar pocket7878 avatar rtt63 avatar samchou19815 avatar vjeux avatar wd-fb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

recoil's Issues

Calmm-js

Please, have a look at calmm-js: https://github.com/calmm-js/documentation/blob/master/introduction-to-calmm.md

In a nutshell, it uses atoms like Recoil, but it lets you decompose the state with optics instead of hand-written selectors. Optics are super nice, because they also work in the write direction. They eliminate the need of writing glue code for selecting stuff from the atom or writing stuff in specific form to the atom. Decomposing an atom with an optic yields a new atom that updates each time the parent atom updates. When written to, the atom writes the new value back to the correct part of the parent atom. In essence, optics let you create read-write views on the state contained in the parent atom. There's no limit on the nesting, so you can decompose an atom with optics, even though it's already a decomposed atom itself.

Furthermore, calmm-js does subscribing to the atoms "one level deeper", at the DOM element level. Whenever a div, a, etc. DOM element is created with React.createElement, it checks whether props or children contains atoms. If they do, it subscribes to those atoms and only the element that renders the atom's value to DOM is re-rendered on updates. I think this is a huge point. If you subscribe to the atoms with a hook in the component you wrote, like Recoil does, the whole component (and all its children) will update when the atom updates. With calmm-js, only the actual DOM elements that depend on the value of the atom will update. (Calmm-js achieves this by requiring you to import React from a custom library that provides a slightly modified React.createElement)

@polytypic (the author of calmm-js) has published lots and lots of examples on CodeSandbox, see https://codesandbox.io/u/polytypic/sandboxes.


Calmm-js uses the partial.lenses library for optics. It's a very comprehensive optics library for JavaScript, but there's a downside: TypeScript's type system isn't flexible enough to allow adding typings for it.

I've recently published optics-ts, which is a fully typed optics library for TypeScript. I've also played with a typed atom implementation and a proof of concept for replicating the calmm-js architecture in a typed way. Here's a small demo: https://codesandbox.io/s/goofy-sun-j1zoz?file=/src/App.tsx

Internal React warning when using recoil in stable react releases and not experimental

I bootstraped a react app using create-react-app, then added recoil and just pasted the code from the website demo.

When the app renders for the first time, an internal react warning in console is shown:

Warning: Cannot update a component (`he`) while rendering a different component (`CharacterCount`). To locate the bad setState() call inside `CharacterCount`, follow the stack trace as described in https://fb.me/setstate-in-render
    in CharacterCount (at App.js:31)
    in div (at App.js:29)
    in CharacterCounter (at App.js:7)
    in RecoilRoot (at App.js:6)
    in App (at src/index.js:7)
    in StrictMode (at src/index.js:6)

image

Updating react dependencies to the experimental ones solved the warning.

PS: the issue doesn't occur with react^16.12 but only with ^16.13.1

Here is a codesandbox demo

So, should we use recoil only with the experimental react release ?

Shipping un-minified source

It looks like the currently published version of recoil contains minified source. When debugging issues in recoil itself, it's more challenging to trace the flow of execution, even when using the devtools pretty-printer.

Would it be possible to ship both a development version alongside the minified production version? It looks like the react package takes this strategy.

Screen Shot 2020-05-16 at 19 27 52

Screen Shot 2020-05-16 at 19 27 33

Review re-frame for other ideas?

It appears this project has reinvented an idea that has existed for five years in a ClojureScript framework called re-frame. My guess is that some of the other ideas in that framework might also be of interest. (Warning: I'm the author, I might be biased).

To give you some idea, re-frame had functional components (via Reagent) in 2015. So it has been consistently well ahead of the entire React ecosystem, despite leveraging React.

Remember also that it was the ClojureScript project OM (David Nolen) which introduced the idea of immutable data to the React world, back in the days of Pete Hunt.

So there's likely interesting stuff to learn over there if your goal is to be experimental and try fresh ideas—no need to reinvent the wheel. Have you ever heard of data-oriented design?

https://github.com/day8/re-frame/blob/master/README.md

Mobx-state-tree vs Recoil

I just want to know the differences between mobx-state-tree and this library.

As far my understanding goes mobx-state-tree is independent of UI Library.

Is recoil to used only with react or Will it be a standalone JS library?

use useRecoilState inside selector get?

get and useRecoilState do very similar thing.
Not sure how realistic it would be to useRecoilState inside selector

const filteredTodoListState = selector({
    key: 'filteredTodoListState',
    get: ({get}) => {
        // const filter = useRecoilState(todoListFilterState)
        const filter = get(todoListFilterState);
        return filter;
    }
})

Warning: Cannot update a component (`he`) while rendering a different component

Hi,

Thanks a lot for this library, it looks great and comes at a perfect time :)

Anyone seeing this warning when using useRecoilState/ useRecoilSetState in a component (ie a checkbox) while updating the atom async from another component?

I am not using any useState myself and the on top of the call stack is the line of code that calls useRecoilState although no details from a call stack in recoiljs itself are shown.

If its not a known issue, please let me know what will help the most - I can post the link to my git repo (with a bit more code), or try to re-create a sample.

grab60

Missing selector async example in the API docs

I like where this library is going. :) I'm really interested to see the preferred method for dealing with async calls (fetch, etc.). Is there anywhere I can see an example while waiting for the docs to be updated?

Components using selectors rerender when original atom changes?

I'm having trouble understanding how selectors work. From the documentation I understood that the component will only subscribe to the data returned from the selector and not the atom that the selector is using (because why have the selector in the first place if selectors are for mapping values?).

Here is a link to a codesandbox, please pay attention to the console.log statements: https://codesandbox.io/s/brave-fast-bcv0s?file=/src/App.js

Object as an atom will cause re-render even when use selector?

import * as React from "react";
import { atom, useRecoilValue, useSetRecoilState, selector } from "recoil";
import "./App.css";

const valueState = atom({
  key: "value",
  default: { a: 1, b: 2 },
});

const selectA = selector({
  key: "selectA",
  get: ({ get }) => get(valueState).a,
});

function ValueChild() {
  const { a } = useRecoilValue(valueState);

  return <div>useRecoilValue a:{a}</div>;
}

function SelectorChild() {
  const a = useRecoilValue(selectA);

  return <div>useRecoilValue + Selector a:{a}</div>;
}

function App() {
  const setValues = useSetRecoilState(valueState);
  const onlyChangeB = () => {
    setValues((v) => ({ ...v, b: 3 }));
  };

  return (
    <div className="App">
      <ValueChild />
      <SelectorChild />
      <button onClick={onlyChangeB}>update</button>
    </div>
  );
}

export default App;

Bootstrapped a new create-react-app and play with it.

I declared an atom as an object { a: 1, b: 2 }

with 2 components which consume it:

  • <ValueChild> only subscribe to property a
  • <SelectorChild> only subscribe to property a with selector

Expect:
change property b should not trigger re-render of components who subscribe to property a

Actual behavior
when I click the button to update only b's value, both components will get re-rendered.

Anything I am missing here? Thanks. Very nice API and idea. I love the work. Tried to replace redux/mobx with this. But this will be a blocker

"window is not defined" - SSR support

I received this error when attempting to retrieve the value from a selector. This occurs in a Next.js 9.4 project, which is inherently server side rendered.

Looking through the source code it looks like there are a few references to the window object that aren't available in SSR. Are there any plans to support SSR? I tried to work around it by checking for the presence of the window object but because I'm trying to conditionally use state it breaks the rules of hooks :(

ReferenceError: window is not defined
    at r (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:712)
    at C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:1276
    at e (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:1290)
    at C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:19654
    at i (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:19929)
    at Object.c [as get] (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:19966)
    at x (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:4554)
    at C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:6719
    at Object.replaceState (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:10416)
    at C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:6684
    at Object.trace (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:4312)
    at getRecoilValueAsLoadable (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:6652)
    at n (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:13390)
    at r (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:13682)
    at Object.getRecoilState (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:13775)
    at useRecoilState (C:\git\nextjs-workflows\node_modules\recoil\dist\recoil.js:1:14988)

Example consumer code:


export const projectNameState = selector({
  key: "projectName",
  get: ({ get }) => ...
});

export const ProjectNameProvider = () => {
  const [projectName, setProjectName] = useRecoilState(projectNameState);
  return (
    <ProjectNameComponent
      projectName={projectName}
      setProjectName={setProjectName}
    />
  );
};

Forgetting atoms

I was wondering if we could get some detail about how/when things get removed from Recoil's internal cache. Does Recoil have internal reference counting? I see some code about persistence, but it's hard to follow without any context.

There's also the recommendation to use a memoize function like the itemWithId example from the React Europe talk. Is Recoil going to have a memoization mechanism? Otherwise, how should we manage when to remove things from our memoization cache?

Read/Write outside of component

Hi, first of all big kodus on this library, it looks amazing.

I couldn't find anything in the docs regarding reading/writing state outside of a component (the Redux equivalent would be Store.dispatch(action)). Is this something that is undocumented/in the pipeline or something that will never be possible?

React Native, Expo support? Throw Error: "s" is read-only

Nice library! I am using this in an Expo project (SDK37), it gives me _readOnlyError. I thought I did something wrong with my code, and used the example from document, and output the same errors when I use 'selector' as an argument in useRecoilState. Notice, 'atom' does not throw errors, working fine.

Any solution to this?

import {
  Text,
  Button,
  View,
} from 'react-native'
import { atom, selector, useRecoilState } from 'recoil'

const tempFahrenheit = atom({
  key: 'tempFahrenheit',
  default: 32,
})

const tempCelcius = selector({
  key: 'tempCelcius',
  get: ({ get }) => ((get(tempFahrenheit) - 32) * 5) / 9,
  set: ({ set }, newValue) => set(tempFahrenheit, (newValue * 9) / 5 + 32),
})

const TempCelcius = () => {
  const [tempF, setTempF] = useRecoilState(tempFahrenheit)
  const [tempC, setTempC] = useRecoilState(tempCelcius)

  const addTenCelcius = () => setTempC(tempC + 10)
  const addTenFahrenheit = () => setTempF(tempF + 10)

  return (
    <View>
      <Text>Temp (Celcius): {tempC}</Text>
      <Text>Text>Temp (Fahrenheit): {tempF}</Text>
      <Button
        title='Add 10 Celcius'
        onPress={addTenCelcius} />
      <Button
        title='Add 10 Fahrenheit'
        onPress={addTenFahrenheit}
      />
    </View>
  )
}

export {
  TempCelcius,
}
export default TempCelcius

Error

Error: "s" is read-only

_readOnlyError
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:147369:20
c
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:147190:42
<unknown>
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:147195:24
<unknown>
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:147216:10
i
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:147250:8
<unknown>
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:146334:21
replaceState
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:146567:20
getRecoilValueAsLoadable
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:146332:21
r
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:146808:12
getRecoilState
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:146815:20
useRecoilState
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:146905:47
renderWithHooks
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:20048:33
mountIndeterminateComponent
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:22097:34
beginWork$$1
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:27288:31
performUnitOfWork
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:26441:30
workLoopSync
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:26423:45
renderRoot
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:26187:29
renderRoot
    [native code]:0
runRootCallback
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:25948:34
runRootCallback
    [native code]:0
<unknown>
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:16678:38
unstable_runWithPriority
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:43144:30
flushSyncCallbackQueueImpl
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:16673:28
flushSyncCallbackQueue
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:16662:35
scheduleUpdateOnFiber
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:25830:37
scheduleRootUpdate
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:28255:21
scheduleRoot
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:17226:42
<unknown>
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:41547:35
forEach
    [native code]:0
performReactRefresh
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:41539:30
performReactRefresh
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:41345:48
<unknown>
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:480:40
_callTimer
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:31051:17
callTimers
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:31258:19
__callFunction
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:3225:49
<unknown>
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:2938:31
__guard
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:3179:15
callFunctionReturnFlushedQueue
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:2937:21
callFunctionReturnFlushedQueue
    [native code]:0

Smallest Version: It seems code like this is enough.

import { createContext, useState, useEffect, useContext, useCallback, useMemo } from 'react';

type Atom<T> = {
  key: string;
  value: T;
  listeners: (() => void)[];
  schedules: ((ov: T) => T)[];
};
const RecoilContext = createContext<{ [key: string]: Atom<any> }>({});
export const RecoilRoot = RecoilContext.Provider;

// const RecoilCtxValue = {};
// renderToString(<RecoilRoot value={RecoilCtxValue}><App /></RecoilRoot>);
// JSON.stringify(RecoilCtxValue); // for SSR

export const atom = <T,>(key: string, defaultValue: T) =>
  ({
    key,
    value: defaultValue,
    listeners: [],
    schedules: [],
  } as Atom<T>);

export const useRecoilState = <P,>(atom: Atom<P>) => {
  const ctx = useContext(RecoilContext);
  let found: Atom<P> = ctx[atom.key];
  if (!found) {
    found = { ...atom, listeners: [...atom.listeners], schedules: [...atom.schedules] };
    ctx[atom.key] = found;
  }
  const [_, update] = useState(0);
  useEffect(() => {
    const listener = () => {
      update(s => {
        let schedule = null;
        while ((schedule = found.schedules.shift())) {
          found.value = schedule(found.value);
        }
        return s + 1;
      });
    };
    found.listeners.push(listener);
    return () => found.listeners.splice(found.listeners.indexOf(listener), 1);
  }, [found]);
  const setAtom = useCallback(
    (value: P | ((ov: P) => P)) => {
      let schedule = value;
      if (!(value instanceof Function)) {
        schedule = (_: P) => value;
      }
      found.schedules.push(schedule as any);
      found.listeners.forEach(l => l());
    },
    [found]
  );
  return [found.value, setAtom] as const;
};

const a = atom('counter', 0);
const [av, as] = useRecoilState(a);
const av_plus_1 = useMemo(() => av+1, [av]);
const as_minus_1 = useCallback(() => as(v => v-1), [as]);
// use useMemo instead of selector

It seems code like this is enough.

Consideration of API for decorator about atom() and selector()

It seems that the current use of atom() and selector() is a bit cumbersome, whether to consider supporting decorator forms as well, such as this:

class Counter {
  @atom
  myCounter = 0;

  @action
  increase() {
     this.myCounter += 0;
  }

  @computed
  get num() {
     return this.myCounter + 1;
  }
}

Having examples in the repo

I saw a folder in the other packages repositories called examples. Any new concept introduced into recoil can be put in this folder for others to view and get an idea of it in addition to docs.

Here is my implementation basic(ish) based on docs. We can use it as a basic example.
example link

Proposal for an API change that is based on hooks

Recoil is a great initiative. I'm excited about the idea of a standard state management library for React, that will be heavily adopted in the React community globally.

I would like to suggest 2 changes to the API:

1. Use hooks inside atoms, like how we manage local/context state.

Here is a basic example:

const useTextState = atom(
  {
    // instead of default, we give a function that can call hooks, just like a component/custom hook
    value: () => useState(''),
    key: 'textState',
  }
);

function TextInput() {
  // No need to wrap the subscription hook with useRecoilState
  const [text, setText] = useTextState();

  ...
}

One atom can use other atoms directly (because it relies on hooks):

const useCharCount = atom({
  value: () => {
    const [text] = useTextState();
    return useMemo(() => text.length, [text]);
  },
  key: 'charCount'
});

We can even have side effects inside atoms:

const useTextState = atom({
  value: () => {
    const [text, setText] = useState(() => /* get from local storage */);
    useEffect(() => /* persist to local storage */, [text]);
    return [text, setText];
  },
  key: 'textState'
});

Or, if we already have a custom hook for persistence to localstorage:

const useTextState = atom({
  value: () => usePersistedState('', LOCAL_STORAGE_KEY),
  key: 'textState'
});

And finally, we could also supply selectors to the subscription hook:

function TextInput() {
  // Will only re-render if the selector result is changed
  useTextState(textState => textState[0].length);
  ...
}

Alternative API (value is the first param):

const useTextState = atom(() => useState(''), {key: 'textState'})

Benefits:

  • Easier to migrate from local/context state to shared state, because we manage it with hooks the same way
  • More freedom inside the atom to use all React hooks - useState, useReducer, useMemo, useEffect or a combination of states that are encapsulated (just like a custom hook)
  • Can use existing custom hooks inside atoms (like the usePersistedState example above)
  • We could potentially display atoms inside the React devtools just like components and examine their state
  • We get the benefits of hooks ESLint rules
  • More straightforward way to use one atom from another atom
  • Can use selectors as a parameter, like in redux hooks implementation

Cons:

  • Pretty big refactor
  • Requires a change from React side to support using hooks inside atoms
  • Migration of existing Recoil users

2. Use the reference (of the object, or value function) as a unique key, not a string

Here is a basic example:

const useTextState = atom(
  {
    value: () => useState(''),
    // debugName is only for debugging / displaying in the React devtools
    debugName: 'textState',
  }
);

Pros of using references as keys:

  • Better reusability because we won't use string keys to identify the atoms. If you introduce a 3rd-party atom there won't be a chance for name clash

Cons of using references as keys:

  • Can't serialise/deserialise the entire state tree

I know this is quite different than what you intended, and quite a big change, but if any of the suggestions sparks a positive reaction and makes sense to you I'm willing to help with coding it. I already created a library with a similar API (ReusableJS) that we are using in our company, but I would much rather ditch maintaining another state management library if there was something official from Facebook.

Thanks for your time!

Option to push events into all atoms / or alternative

I'm rewriting my app from redux to recoil, it's going great, but I did have a global 'RESET' action for example when I logout. I want some reducers to reset themselves. I think I can fix this with more explicit approach of using useResetRecoilState() in my logout button but I was wondering if you did have an alternative for that use-case.

import { atom } from 'recoil'

export const answeredFlows = atom({
  key: 'answeredFlows',
  default: [],
  globalEvents: (event: { type: string }) => {
    if (event.type === 'LOGOUT') {
      return []
    }
  },
})

I guess you don't want to recreate Redux but sometimes it would be cool to pass an event to all atoms.

Some hooks cause components to render twice (useRecoilValue, useRecoilState)

Having tried to create a demo app using recoil and looking into the performance I found that some of the hooks cause React components to render twice un updates.

Codesandbox: https://codesandbox.io/s/recoil-double-render-issue-jdrkm?file=/src/App.js

The above demonstrates how updates to the root component(App) cause children using recoil hooks to render twice. The same is true when updating react state internally, in this case, the TextInput component.

The rerenders are logged in console for demo purposes :-)

How to share state operation ?

With ContextAPI :

cosnt CounterContext = React.createContext(null);
const CounterProvider = (props) => {
  const [value, setValue] = useState(0);
  const add = useCallback(() => setValue(s => s+1));
  return <CounterContext.Provider value={{value, add}}>
    {props.children}
  </CounterContext.Provider>
}

But with recoil, can we :

const counterDefault = {
  value: 0,
  add() {
    counterDefault.value += 1;
  },
}
atom({
  key: 'counter',
  default: counterDefault
})

Of course the code above is wrong, but how could we share state operation ?
With context it's easy because operation closure has captured the real setState function.
But with recoil, how to do it ?

By the way, @davidmccabe , can you open source your demo in the introduction video ? I still don't know how to use recoil to handle undefined number of state.

[Feature Request]: Devtools, SideEffects management, Serialization / Deserialization

😍😍😍
I am immensely impressed by the simplicity of this library and looking forward to using it in actual projects.

There are a few things that I'd love to have :

  1. A Devtool, similar to redux dev tool where you can see the state graph and modify them accordingly.
  2. A clean and ergonomic way to handle side-effects. (No middleware magic)
  3. Serialization/Deserialization of whole state tree/graph.

How to represent lists?

If I wanted to represent list items as individual atoms and want to do something with all of my list items, what's the best pattern?

I might have to be incrementing an ID somewhere to use as the key and keep that max ID to know how to iterate over my items, e.g. list-item.1, list-item.2, etc.

A system where we can filter the global state store by keys allows me to give my individual items something like a UUID instead of maintaining a separate counter and still lets me query all of them by looking for keys that start by list-item.. Problems might arise however if these lists need to be nested more than one level deep (e.g. my list has a list). Performance of looping through these would likely be awful too.

A third method that comes to mind would be to give individual items a key with a UUID and store an array of these keys separately. This can nest forever but does duplicate data a bit.

Anything obvious I'm missing?

[Question] Can atoms contain atoms in their values

In the talk, there was an array of ids and a memoized id => atom function. I think in the example the id array was regular react state, but let's pretend it was a recoil atom.

If you then delete an id, you have a caching issue with the memoize function

Is it possible/a good idea to have an atom whose value is an array of atoms?

I know a downside is that the component that has the array has to treat each element inside as a black box, since the rules of hooks will stop it reading their contents. But that might be okay

Logo

An idea of a logo?

I can help if you want :)

Contradiction in documentation?

Here, you're saying that all these hooks subscribe to atoms which contradicts the claim found here

First link:

Most often, you'll use the following hooks to read the value of atoms:

useRecoilState(): use this hook when you intend on both reading and writing to the atom.
useRecoilValue(): use this hook when you intend on only reading the atom.
useSetRecoilState(): use this hook when you intend on only writing to the atom.
Note all of the hooks above result in the component subscribing to the atom, so the component will re-render on any subsequent updates to the atom.

Second link:

Returns a function that allows the value of a RecoilState to be updated, but does

Suggested folder structure for atoms and selectors

Hi guys. This is a very promising library.

Is there a suggested folder structure for this? This is what I currently have in mind:

Screen Shot 2020-05-15 at 10 13 48 PM

atoms/auth.js

import { atom } from "recoil";

export const userState = atom({
  key: "userState",
  default: {
    id: 123,
    name: "John Doe",
    email: "[email protected]",
  },
});

// More atoms related to user

atoms/todo.js

import { atom } from "recoil";

export const todoState = atom({
  key: "todoState",
  default: [],
});

// More atoms related to todo

or we can have all atoms inside atoms/index.js and selectors inside selectors/index.js

import { atom } from "recoil";

export const userState = atom({
  key: "userState",
  default: {
    id: 123,
    name: "John Doe",
    email: "[email protected]",
  },
});

export const todoState = atom({
  key: "todoState",
  default: [],
});

Then we can import it like this:

import { useRecoilState } from "recoil";
import { userState } from "./atoms/auth";

function LoginPage() {
  const [user, setUser] = useRecoilState(userState);

  return (...);
}

What do you guys think?

Warning: Cannot update a component (`xxx`) while rendering a different component (`xxx`).

A warning is occured where useRecoilValue() is used even though I write a code like one which is in the official document here.

スクリーンショット 2020-05-15 4 55 26

this is my code

import React, { useEffect } from 'react';
import { atom, useRecoilState, useRecoilValue, selector } from 'recoil';

function RecoilApp() {
  useEffect(() => {
      console.log('test');
  });
  return(
    <div>
      Recoil App
      <TextInput />
      <CharCount />
    </div>
  );
}

const textState = atom({key: 'textState', default: ''});

function TextInput() {
  const [text, setText] = useRecoilState(textState);

  const onChange = event => {
    setText(event.target.value);
  }

  return(
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
};

function CharCount() {
  const count = useRecoilValue(countState);
  return <>Charset Count: {count}</>;
}

const countState = selector({
  key: 'countState',
  get: ({get}) => {
    const text = get(textState);
    return text.length;
  },
});

export default RecoilApp;

How to refresh/invalidate an asynchronous selector?

Selectors can be used to asynchronously fetch data from an API. But how is it possible to trigger a re-fetch of this data?

Given this selector:

const todosState = selector({
  key: "todosState",
  get: async () => {
    const result = await fetch("https://example.com/todos");
    const todos = await result.json();
    return todos;
  },
});

In a scenario where a user wanted to reload his todos, because he knows his coworker added a new todo to the list. How would he trigger this selector to re-fetch the todos from the API?

Merging selector and selectorFamily

selector and selectorFamily has different api's. Its probably a very big ask but I wonder if they can be somewhat the same.

const currentUserNameState = selector({
    key: 'CurrentUserName',
    get: ({get, params}) => {
        return tableOfUsers[params.userId].name;
    },
});

function UserInfo({userId}) {
    const userName = useRecoilValue(currentUserNameState(userId));
    return <div>{userName}</div>;
}

I think it would be beneficial to abstract the logic into the library.

Subscribing effects to state

I'm wondering if there's plans to allow functionality such as subscribing effects to state. Let me be a bit more specific in what I mean - I'm trying to store a certain atom's value to localstorage, and right now the best solution I've come up with is this:

Declaring my atom like so:

const myState = atom({
  key: 'myState',
  default: JSON.parse(window.localStorage.getItem('myState')) || myDefaultValue,
});

And writing this in my root component:

const myStateValue = useRecoilValue(myState);
useEffect(() => {
  window.localStorage.setItem('myState', JSON.stringify(myStateValue));
}, [myStateValue]);

But it would be really nice if we could specify some kind of subscribed function somewhere that just runs the localstorage setter on value change, so we don't have to rely on the render of the root component. As an example API:

const myState = atom({
  key: 'myState',
  default: JSON.parse(window.localStorage.getItem('myState')) || myDefaultValue,
  subscriber: value => window.localStorage.setItem('myState', JSON.stringify(value)),
});

If this functionality already exists and I'm just missing it, please feel free to point it out and close the issue. Otherwise I'd love to hear the team's thoughts on such functionality!

[Feature request] add a way to use reducers inside selectors: useRecoilSelectorWithReducer and stateful selectors

So I was playing around with recoil and how it can be used in real world/enterprise apps. I had so many AHAs to be honest. I highly congratulate you for the hard work that was done.

I just started digging in the source code, so I am not qualified for contributing to solve bugs and/or add new features.

  1. So, the base entity or the atom in enterprise apps would be form management, and I find that forms are segmented into two types: inline forms and composed forms.
  • Inline forms are the ones only with one level deep: direct properties inside the main object. For this type of forms, they are trivial to manage. The following example manages them pretty good:
const MY_FORM_ATOM = atom({
  key: 'myForm',
  default: { username: '', password: '', dateOfBirth: '', rememberMe: true },
});
const MY_FORM = selector({
  key: 'myFormManagement',
  get: ({ get }) => get(MY_FORM_ATOM),
  set: ({ get, set }, { target: { name, value, type, checked } }) => {
    set(MY_FORM_ATOM, { ...get(MY_FORM_ATOM), [name]: type === 'checkbox' ? checked : value });
  },
});

function MyFormComponent() {
  const [values, updater] = useRecoilState(MY_FORM);
  return (
    <div>
      <input name="username" value={values.username} onChange={updater} />
      {/* And so on for the other fields */}
    </div>
  );
}
  • Composed forms where the main object has multiple levels of nested objects, ie: User { username, fullName, age, addresses: [{city, country, zipCode, description}], todos: [{id, description, completed}]. The previous solution will be unfortunate since the updater will have to preserve the whole state while updating a small portion of it and the code will look very creepy. So, I came out with this: useRecoilSelectorWithReducer
function useRecoilSelectorWithReducer({ selectorKey, selectorAtom, reducer }) {
  return useRecoilState(
    selector({
      key: selectorKey,
      get: ({ get }) => get(selectorAtom),
      set: ({ get, set }, action) => {
        set(selectorAtom, reducer(get(selectorAtom), action));
      },
    }),
  );
}

This allows us to benefit from reducers and their power and do like these things:

function inlineUpdater(e) {
  const { name, value, type, checked } = e.target;
  updateValue({ type: 'change_value', payload: { name, value, type, checked } });
}
function changeAddressField(id, event) {
  const { name, value } = event.target;
  updateValue({ type: 'change_address', payload: {id, name, value } });
}
function addAddress() {
  updateValue({ type: 'add_address', payload: {id, description, country, city } });
}
function removeAddress(id) {
  updateValue({ type: 'remove_address', payload: id });
}

So my request here, Is there any way that you can adopt/design something similar out of the box ? I think the usage of a reducer to update a state will have a major power in managing complex states.
The current implementation I did, changes the value of the setter at every render, and I don't feel ok about it. (May be I have to think more about a way to do it)

  1. Is there any way to include stateful selectors? ie: selectors that accepts and an argument and returns a subscribed sub-state ? for example, If we take the todo list example from the docs, when updating a todo, all todos keep being re-rendered because they are subscribed to the global todo list, One solution is to create an atom per todo, but what If I want to send the whole list to my api ? I did not find any way to select atoms by regex or pattern. I came up with following hack, but again, there are memoization issue regarding the setter:
export const TODO_BY_ID = (id) =>
  selector({
    key: 'getTodoById',
    get: ({ get }) => get(TODOS_ATOM)[id],
    set: ({ get, set }, value) => {
      set(TODOS_ATOM, { ...get(TODOS_ATOM), [value.id]: { ...value} });
    },
  });

I understand that you may be overwhelmed by the huge amount of issues and requests, but for me, this is a sign of success. So I did my best to group my thoughts in this issue.

Thank you again for the great library

Prettier config

What Prettier config do you use internally? It'd be great if we could embed it in the project as a .prettierrc, and perhaps run prettier --check on PRs to make sure contributors have run Prettier.

Tests

Is the plan to use Flow as a test suite? I see many pre-existing tests, and flow-bin is a devDependency, but running yarn run flow produces an error about a missing .flowconfig.

use outside of react

Hi, I just saw React Europe Dave's talk and this sounds like it could be my perfect state management system in the future. However, I do have some three.js objects I update outside of react for performance reasons. Is it possible to observe changes outside of react?

Atom unmounted

When 'atom' is not being used at all, from the source code, it is not currently unmounted. Will this be addressed in the future?

API Request - cancelation of asynchronous selector

Thanks for sharing Recoil with the React community, it looks great. Having worked previously with libraries like RxJs one of the first things I look for with async state is the ability to cancel tasks. From what I can tell, and please do correct me if I am wrong, is that this is not currently supported. Raising a ticket to discuss if it could be part of a future version of Recoil 😀

Use case:
An asynchronous selector makes a network request using the Fetch API. When a selector's dependancies changes and a new request is made then the previous request could ideally be aborted to avoid unnecessary work.

const currentUserInfo = selector({
  key: 'CurrentUserInfo',
  get: async ({get}) => {
    // If the 'currentUserIDState' atom changes this request is not canceled
    const req = await fetch(`/user?id=${get(currentUserIDState)}`);
    return await req.json();
  },
});

Related APIs:
The function passed to React.useEffect may return a clean-up function.
Note: Recoil can't follow this exact same pattern right now because the current API expects a promise to be returned.

Possible approach:

Pass a cleanup function to the selector's get. Passing a function to cleanup would register it ready to be called by recoil when the selector is invalidated.

const currentUserInfo = selector({
  key: 'CurrentUserInfo',
  get: async ({get, cleanup}) => {
    const controller = new AbortController();
    const signal = controller.signal;
    cleanup(() => controller.abort());

    const req = await fetch(`/user?id=${get(currentUserIDState)}`, {signal});
    return await req.json();
  },
});

Alternatives:

Instead of having built-in support for cancelation it might be possible to implement cancelation in user space. This would stop the get being a pure function so is possibly breaking a "rule of recoil" and therefore not safe.

EDIT: This approach is not safe #51 (comment) 🔥

const currentUserInfo = (() => {
    let cleanup = null;
    return selector({
      key: 'CurrentUserInfo',
      get: async ({get}) => {
        cleanup?.();
        const controller = new AbortController();
        const signal = controller.signal;
        cleanup = () => controller.abort();
    
        const req = await fetch(`/user?id=${get(currentUserIDState)}`, {signal});
        return await req.json();
      },
    });
})();

Thanks for reading!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.