Skip to content

Commit

Permalink
internal: Todos demo setup and scaffolding (#3)
Browse files Browse the repository at this point in the history
* Restructure and rehash todo demo

* Finish scaffolding for demo
  • Loading branch information
spautz committed Jul 25, 2020
1 parent 2221db0 commit e38b7e7
Show file tree
Hide file tree
Showing 34 changed files with 372 additions and 141 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
!**/*.tsx

# Unless they're somewhere we can ignore
build/
dist/
coverage/
node_modules/
78 changes: 31 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Redux-to-Recoil

Create Recoil atoms and selectors from your existing actions, reducers, and selectors
Access your Redux store through Recoil atoms and selectors.

**This package is in active development. Things will change rapidly, and it is not yet production-ready. Feedback is welcome.**

Expand All @@ -9,72 +9,56 @@ Create Recoil atoms and selectors from your existing actions, reducers, and sele
[![test coverage](https://img.shields.io/coveralls/github/spautz/redux-to-recoil.svg)](https://coveralls.io/github/spautz/redux-to-recoil)
[![gzip size](https://img.shields.io/bundlephobia/minzip/redux-to-recoil)](https://bundlephobia.com/result?p=redux-to-recoil)

## Motivation
## Example

Migrating from [Redux](https://redux.js.org/) to [Recoil](https://recoiljs.org/) can be a lot of work.

This library contains utilities which take in your existing Redux-oriented functions and return Recoil state.
The goal is to try things out with Recoil without having to migrate your entire codebase first.

## How it works

There are two different sets of helpers:

`atomFromReduxState` lets you access your existing Redux state through Recoil. Data is still stored in Redux.

`createAtomFactory`, `dispatchToRecoil`, and `wrapSelector` let you use your existing Redux-oriented code to manufacture Recoil atoms and
selectors. All data is stored in Recoil.

## Examples
Use `atomFromRedux` to create a Recoil wrapper around a location in Redux. This works exactly like any other atom or
selector.

```typescript jsx
import { selector, useRecoilState } from 'recoil';
import { atomFromReduxState } from 'redux-to-recoil';
import { myActionCreator, myReducer, mySelector } from 'my-existing-code';
import { selector, useRecoilState, useRecoilValue } from 'recoil';
import { atomFromRedux } from 'redux-to-recoil';

// `atomFromReduxState` replaces Recoil's `atom()` constructor
const todoListAtom = atomFromReduxState('.todos'); // wraps state.todos
const todosAtom = atomFromRedux('.todos'); // wraps state.todos

const todoCountSelector = selector({
key: 'todoCount',
get: ({ get }) => get(todoList).length,
get: ({ get }) => get(todosAtom).length,
});

// Inside your component, use the atoms and selectors as normal
const [todoList, setTodoList] = useRecoilState(todoListAtom);
const todoCount = useRecoilState(todoCountSelector);

// All Redux middleware and tools still work
const [todos, setTodos] = useRecoilState(todosAtom);
const todoCount = useRecoilValue(todoCountSelector);
```

A redux-to-recoil provider syncs state between the two stores.

```typescript jsx
import { useRecoilState } from 'recoil';
import { createAtomFactory, useRecoilDispatch, useRecoilSelector } from 'redux-to-recoil';
import { addTodo, editTodo, todosReducer, todosSelector } from 'my-existing-code';
import { SyncReduxToRecoil } from 'redux-to-recoil';

<Provider store={store}>
<RecoilRoot>
<SyncReduxToRecoil enabled={true} />
<MyApp />
</RecoilRoot>
</Provider>;
```

const createTodoAtom = createAtomFactory({
namespace: 'todos',
reducer: todosReducer,
});
## Do I need this?

const todoAtom = createTodoAtom('.'); // represents state.todos
const todoCountAtom = createTodoAtom('.length'); // represents state.todos.length
You probably don't need this. Redux and Recoil work fine side-by-side. You can already use values from Redux and Recoil
together in a component.

// Inside your component, use the atoms and selectors as normal
const [todos, setTodos] = useRecoilState(todoAtom);
const todoCount = useRecoilState(todoCountAtom);
This library is useful for accessing Redux state from _within_ a Recoil selector -- which lets you call selectors
conditionally, or within loops. `useSelector` can't do that.

// `useDispatch` becomes `useRecoilDispatch`, `useSelector` becomes `useRecoilSelector`
const todoCount = useRecoilSelector(todosSelector);
<button onClick={() => dispatchToRecoil(addTodo())}>
```
This library can help you avoid unnecessary rerenders in some situations. It can also facilitate a migration from
Redux to Recoil.

## Roadmap

- [ ] Core functionality: atomFromReduxState
- [ ] Core functionality: createAtomFactory
- [ ] Core functionality: useRecoilDispatch
- [ ] Core functionality: useRecoilSelector
- [ ] Core functionality: SyncReduxToRecoil
- [ ] Core functionality: atomFromRedux
- [ ] Core functionality: selectorFromReselect
- [ ] Tests
- [ ] Demo
- [ ] Initial release
12 changes: 8 additions & 4 deletions demos/todo-list/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
## Todo List demo

This project takes the ["Todo List" example from Redux](https://redux.js.org/basics/example) and wraps the Redux state
in `atomFromReduxState` so that it can be interacted with via Recoil.
This demo project takes the ["Todo List" example from Redux](https://redux.js.org/basics/example) and uses
[Redux-to-Recoil](https://github.com/spautz/redux-to-recoil) to access the Redux state via Recoil.

This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
There are three different implementations of the UI, running side-by-side:

[See the original readme here](./create-react-app.md).
- The original plain Redux version: read from Redux state, dispatch actions to Redux
- A Recoil state version: read from Recoil state, dispatch actions to Redux
- A bidirectional Recoil state version: read from Recoil state, write to Recoil state

This project was bootstrapped with [Create React ReduxApp](https://github.com/facebook/create-react-app).
6 changes: 3 additions & 3 deletions demos/todo-list/create-react-app.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
This project was bootstrapped with [Create React ReduxApp](https://github.com/facebook/create-react-app).

## Available Scripts

Expand Down Expand Up @@ -39,7 +39,7 @@ You don’t have to ever use `eject`. The curated feature set is suitable for sm

## Learn More

You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
You can learn more in the [Create React ReduxApp documentation](https://facebook.github.io/create-react-app/docs/getting-started).

To learn React, check out the [React documentation](https://reactjs.org/).

Expand All @@ -51,7 +51,7 @@ This section has moved here: https://facebook.github.io/create-react-app/docs/co

This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size

### Making a Progressive Web App
### Making a Progressive Web ReduxApp

This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app

Expand Down
1 change: 1 addition & 0 deletions demos/todo-list/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"react-dom": "^16.13.1",
"react-redux": "^7.2.0",
"react-scripts": "3.4.1",
"recoil": "^0.0.10",
"redux": "^4.0.5",
"reselect": "^4.0.0"
},
Expand Down
10 changes: 10 additions & 0 deletions demos/todo-list/src/BidirectionalRecoilApp.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

const ReduxApp = () => (
<fieldset>
<legend>Bidirectional Redux-to-Recoil</legend>
TODO
</fieldset>
);

export default ReduxApp;
25 changes: 25 additions & 0 deletions demos/todo-list/src/RecoilApp.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';

import { VisibilityFilters } from './actions';
import AddTodo from './components.recoil/AddTodo';
import FilterLink from './components.recoil/FilterLink';
import VisibleTodoList from './components.recoil/VisibleTodoList';

const ReduxApp = () => (
<fieldset>
<legend>
<legend>Redux-to-Recoil</legend>
</legend>
<AddTodo />
<VisibleTodoList />

<div>
<span>Show: </span>
<FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>Completed</FilterLink>
</div>
</fieldset>
);

export default ReduxApp;
15 changes: 8 additions & 7 deletions demos/todo-list/src/App.jsx → demos/todo-list/src/ReduxApp.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';

import { VisibilityFilters } from './actions';
import AddTodo from './components/AddTodo';
import FilterLink from './components/FilterLink';
import VisibleTodoList from './components/VisibleTodoList';
import AddTodo from './components.redux/AddTodo';
import FilterLink from './components.redux/FilterLink';
import VisibleTodoList from './components.redux/VisibleTodoList';

const App = () => (
<div>
const ReduxApp = () => (
<fieldset>
<legend>Plain Redux</legend>
<AddTodo />
<VisibleTodoList />

Expand All @@ -16,7 +17,7 @@ const App = () => (
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>Completed</FilterLink>
</div>
</div>
</fieldset>
);

export default App;
export default ReduxApp;
2 changes: 2 additions & 0 deletions demos/todo-list/src/components.recoil/AddTodo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Because AddTodo doesn't read from the store, it's the same between Redux and Redux-to-Recoil
export { default } from '../components.redux/AddTodo';
30 changes: 30 additions & 0 deletions demos/todo-list/src/components.recoil/FilterLink.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { connect } from 'react-redux';

import { setVisibilityFilter } from '../actions';

const FilterLink = (props) => {
const { children, active, onClick } = props;

return (
<button
onClick={onClick}
disabled={active}
style={{
marginLeft: '4px',
}}
>
{children}
</button>
);
};

const mapStateToProps = (state, ownProps) => ({
active: ownProps.filter === state.visibilityFilter,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
});

export default connect(mapStateToProps, mapDispatchToProps)(FilterLink);
2 changes: 2 additions & 0 deletions demos/todo-list/src/components.recoil/Todo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Because Todo doesn't read from the store, it's the same between Redux and Redux-to-Recoil
export { default } from '../components.redux/Todo';
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import React from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { toggleTodo } from '../actions';
import visibleTodosSelector from '../selectors/visibleTodosSelector';
import getVisibleTodos from '../selectors/getVisibleTodos';
import Todo from './Todo';

const VisibleTodoList = (props) => {
const dispatch = useDispatch();

const todos = useSelector(visibleTodosSelector);
const todos = useSelector(getVisibleTodos);

return (
<ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { connect, useDispatch } from 'react-redux';
import { useDispatch } from 'react-redux';

import { addTodo } from '../actions';

Expand Down Expand Up @@ -27,4 +27,4 @@ const AddTodo = () => {
);
};

export default connect()(AddTodo);
export default AddTodo;
30 changes: 30 additions & 0 deletions demos/todo-list/src/components.redux/FilterLink.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { connect } from 'react-redux';

import { setVisibilityFilter } from '../actions';

const FilterLink = (props) => {
const { children, active, onClick } = props;

return (
<button
onClick={onClick}
disabled={active}
style={{
marginLeft: '4px',
}}
>
{children}
</button>
);
};

const mapStateToProps = (state, ownProps) => ({
active: ownProps.filter === state.visibilityFilter,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
});

export default connect(mapStateToProps, mapDispatchToProps)(FilterLink);
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';

const Todo = ({ onClick, completed, text }) => (
<li
Expand All @@ -12,10 +11,4 @@ const Todo = ({ onClick, completed, text }) => (
</li>
);

Todo.propTypes = {
onClick: PropTypes.func.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired,
};

export default Todo;
22 changes: 22 additions & 0 deletions demos/todo-list/src/components.redux/VisibleTodoList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { toggleTodo } from '../actions';
import getVisibleTodos from '../selectors/getVisibleTodos';
import Todo from './Todo';

const VisibleTodoList = (props) => {
const dispatch = useDispatch();

const todos = useSelector(getVisibleTodos);

return (
<ul>
{todos.map((todo) => (
<Todo key={todo.id} {...todo} onClick={() => dispatch(toggleTodo(todo.id))} />
))}
</ul>
);
};

export default VisibleTodoList;
Loading

0 comments on commit e38b7e7

Please sign in to comment.