Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add useCopyWrite and useCopyWriteMemo #63

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 54 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,20 @@ react-copy-write lets you use straightforward mutations to update an immutable s

react-copy-write is currently under-going significant API changes as it's tested in a production environment. Most documentation has been removed until we arrive at a stable API. Below you will find a bare-bones API reference that should get you started.


# `createState`

The default export of the package. Takes in an initial state object and returns a collection of components and methods for reading, rendering, and updating state.


```jsx
import createState from 'react-copy-write'
import createState from "react-copy-write";

const {
Provider,
Consumer,
createSelector,
mutate,
} = createState({name: 'Brandon' });
useCopyWrite
} = createState({ name: "Brandon" });
```

# `Provider`
Expand All @@ -45,32 +44,27 @@ const App = () => (
<Provider>
<AppBody />
</Provider>
)
);
```

If you need to initialize state from props you can use the `initialState` prop to do so. Note that it only initializes state, updating `initialState` will have no effect.

```jsx
const App = ({user}) => (
<Provider initialState={{name: user.name }}>
const App = ({ user }) => (
<Provider initialState={{ name: user.name }}>
<AppBody />
</Provider>
)
);
```


## `Consumer`

A Consumer lets you _consume_ some set of state. It uses a [render prop](https://reactjs.org/docs/render-props.html#use-render-props-for-cross-cutting-concerns) as a child for accessing and rendering state. This is identical to the [React Context Consumer API](https://reactjs.org/docs/context.html#consumer).

```jsx
const Avatar = () => (
<Consumer>
{state => (
<img src={state.user.avatar.src} />
)}
</Consumer>
)
<Consumer>{state => <img src={state.user.avatar.src} />}</Consumer>
);
```

The render callback is always called with a tuple of the observed state, using an array. By default that tuple contains one element: the entire state tree.
Expand All @@ -84,20 +78,49 @@ const Avatar = () => (
<Consumer select={[state => state.user.avatar.src]}>
{src => <img src={src} />}
</Consumer>
)
);
```

Now the Avatar component will only re-render if `state.user.avatar.src` changes. If a component depends on multiple state values you can just pass in more selectors.

```jsx
const Avatar = () => (
<Consumer select={[
state => state.user.avatar.src,
state => state.theme.avatar,
]}>
<Consumer
select={[state => state.user.avatar.src, state => state.theme.avatar]}
>
{(src, avatarTheme) => <img src={src} style={avatarTheme} />}
</Consumer>
)
);
```

## `useCopyWrite` Hook

[Hooks](https://reactjs.org/docs/hooks-intro.html) are a new addition in React 16.8. They let you use `react-copy-write` without using render functions.

Let's write our `Avatar` component in the previous example using the `useCopyWrite` hook instead.

```jsx
const Avatar = () => {
const [src, avatarTheme] = useCopyWrite([
state => state.user.avatar.src,
state => state.theme.avatar
]);
return <img src={src} style={avatarTheme} />;
};
```

### Preventing unnecessary updates with `useCopyWrite`

There is one notable difference between using the render prop approach (`<Consumer />`) and using the hook approach (`useCopyWrite`). Using the render prop will only re-render if selected state changes, however, this is currently not possible with the `useCopyWrite` hook. You can visit [this issue for more information](https://github.com/facebook/react/issues/14110).

There is a workaround however, you can use the `useCopyWriteMemo` hook. This will use the [`useMemo`](https://reactjs.org/docs/hooks-reference.html#usememo) hook internally to check for state changes.

```jsx
const Avatar = () =>
useCopyWriteMemo(
[state => state.user.avatar.src, state => state.theme.avatar],
([src, avatarTheme]) => <img src={src} style={avatarTheme} />
);
```

## Updating State
Expand All @@ -114,27 +137,26 @@ Mutate takes a single function as an argument, which will be passed a "draft" of
const addTodo = todo => {
mutate(draft => {
draft.todos.push(todo);
})
}
});
};
```

You don't have to worry about creating new objects or arrays if you're only updating a single item or property.
You don't have to worry about creating new objects or arrays if you're only updating a single item or property.

```js
const updateUserName = (id, name) => {
mutate(draft => {
// No object spread required 😍
draft.users[id].name = name;
draft.users[id].lastUpdate = Date.now();
})
}
});
};
```

Check out [the Immer docs for more information](https://github.com/mweststrate/immer).

Since `mutate` is returned by `createState` you can call it anywhere. If you've used Redux you can think of it like `dispatch` in that sense.


## Optimized Selectors

`createState` also returns a `createSelector` function which you can use to create an _optimized selector_. This selector should be defined outside of render, and ideally be something you use across multiple components.
Expand All @@ -147,18 +169,19 @@ You can get some really, really nice speed if you use this and follow a few rule

### Don't call `createSelector` in render.


🚫

```jsx
const App = () => (
// Don't do this
// Don't do this
<Consumer select={[createSelector(state => state.user)]}>
{...}
</Consumer>
)
```

👍

```jsx
// Define it outside of render!
const selectUser = createSelector(state => state.user);
Expand All @@ -172,6 +195,7 @@ const App = () => (
### Avoid mixing optimized and un-optimized selectors

🚫

```jsx
const selectUser = createSelector(state => state.user);
const App = () => (
Expand All @@ -184,6 +208,7 @@ const App = () => (
```

👍

```jsx
const selectUser = createSelector(state => state.user);
const selectTheme = createSelector(state => state.theme);
Expand All @@ -193,4 +218,3 @@ const App = () => (
</Consumer>
)
```

Loading