forked from aweary/react-copy-write
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
156 lines (140 loc) · 4.94 KB
/
Copy pathindex.js
File metadata and controls
156 lines (140 loc) · 4.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
* _ _ _
* | | (_| |
* _ __ ___ __ _ ___| |_ ______ ___ ___ _ __ _ _ ________ ___ __ _| |_ ___
* | '__/ _ \/ _` |/ __| __|______/ __/ _ \| '_ \| | | |______\ \ /\ / | '__| | __/ _ \
* | | | __| (_| | (__| |_ | (_| (_) | |_) | |_| | \ V V /| | | | || __/
* |_| \___|\__,_|\___|\__| \___\___/| .__/ \__, | \_/\_/ |_| |_|\__\___|
* | | __/ |
* |_| |___/
*
* Provides a mutable API with immutable state for React. Powered
* by immer and React.createContext.
*/
import React, { Component } from "react";
import produce from "immer";
import invariant from "invariant";
import shallowEqual from "fbjs/lib/shallowEqual";
import createContext from 'create-react-context'
// The default selector is the identity function
function identityFn(n) {
return n;
}
export default function createCopyOnWriteState(baseState) {
let updateState = null;
let currentState = baseState;
const State = createContext(baseState);
// Wraps immer's produce. Only notifies the Provider
// if the returned draft has been changed.
function mutate(fn) {
invariant(
updateState !== null,
`mutate(...): you cannot call mutate when no CopyOnWriteStoreProvider ` +
`instance is mounted. Make sure to wrap your consumer components with ` +
`the returned Provider, and/or delay your mutate calls until the component ` +
`tree is mounted.`
);
updateState(fn);
}
/**
* Currently createSelector is just the identity function. The long-term
* goal is for it to be a way to create optimizable selectors using React's
* unstable_observedBits Context API. The implementation of that
* optimization strategy is currently still in development, but I want people
* to start using createSelector now. Then, when it *does* get optimized, there
* will be changes required from users.
*/
function createSelector(fn) {
return fn;
}
class CopyOnWriteStoreProvider extends React.Component {
state = this.props.initialState || baseState;
componentDidMount() {
invariant(
updateState === null,
`CopyOnWriteStoreProvider(...): There can only be a single ` +
`instance of a provider rendered at any given time.`
);
updateState = this.updateState;
}
componentWillUnmount() {
updateState = null;
}
updateState = fn => {
this.setState(state => {
const nextState = produce(state, draft => fn(draft, state));
if (nextState === state) {
return null;
}
currentState = nextState;
return nextState;
});
};
render() {
return (
<State.Provider value={this.state}>
{this.props.children}
</State.Provider>
);
}
}
class ConsumerMemoization extends React.Component {
shouldComponentUpdate({ state, consume, version }) {
const currentState = this.props.state;
return (
version !== this.props.version ||
state.some(
(observedState, i) => !shallowEqual(observedState, currentState[i])
)
);
}
render() {
const { children, state } = this.props;
return children.apply(null, state);
}
}
class CopyOnWriteConsumer extends React.Component {
static defaultProps = {
select: [identityFn],
consume: null
};
/**
* Consumers need to differentiate between updates coming
* through Context, and updates triggered by a parent re-rendering.
*
* In the case of a Context update, we want to avoid re-rendering the Consumer
* unless state has changed.
*
* In the case of a parent re-rendering, we want to ere on the side of caution
* and render the Consumer again, just in case it's also using values from props.
*
* In order to accomplish this we use gDSFP to track an integer which represents the
* "version" of the Consumer. gDSFP won't be called for a Context update, so if
* the version changes we know that the parent has re-rendered.
*/
static getDerivedStateFromProps(props, state) {
return { version: state.version + 1 };
}
state = { version: 0 };
consumer = state => {
const { version } = this.state;
const { children, select, render } = this.props;
const observedState = select.map(fn => fn(state));
return (
<ConsumerMemoization version={version} state={observedState}>
{typeof render === "function" ? render : children}
</ConsumerMemoization>
);
};
render() {
return <State.Consumer>{this.consumer}</State.Consumer>;
}
}
return {
Provider: CopyOnWriteStoreProvider,
Consumer: CopyOnWriteConsumer,
mutate,
createSelector,
getCurrent: () => currentState
};
}