Skip to content

Commit 13cb73c

Browse files
Add initial version of README
1 parent 8008c8c commit 13cb73c

File tree

1 file changed

+274
-0
lines changed

1 file changed

+274
-0
lines changed

README.md

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,277 @@
2525
</p>
2626

2727
---
28+
29+
There are numerous solutions to managing state in React including powerful libraries like [Redux][] and architectures like [Flux] which evolves around the concept of a "reducer": A single handler for all actions in one store. Recent development of [Reason][] builds upon this concepts and integrates it tightly into [ReasonReact][] by the use of a "reducer component".
30+
31+
A reducer component is used like a regular, stateful, React component with the added twist that `setState` is not allowed. Instead, state is updated through a `reducer` which is triggered by sending actions to it. ReComponent brings this concept to your React application today.
32+
33+
## Installation
34+
35+
```
36+
npm install react-recomponent --save
37+
```
38+
39+
## Getting Started
40+
41+
Using `ReComponent` works in the same way as using a regular React component. State is usually initialized using the `initialState()` callback and can only be modified by sending actions to the `reducer()` function. To help with that, you can use `createSender()`. Take a look at a simple counter example:
42+
43+
```js
44+
import React from "react";
45+
import { ReComponent, Update } from "reason-recomponent";
46+
47+
class Counter extends ReComponent {
48+
constructor() {
49+
super();
50+
this.handleClick = this.createSender("CLICK");
51+
}
52+
53+
initialState(props) {
54+
return {
55+
count: 0
56+
};
57+
}
58+
59+
reducer(action, state) {
60+
switch (action.type) {
61+
case "CLICK":
62+
return Update({ count: state.count + 1 });
63+
}
64+
}
65+
66+
render() {
67+
return (
68+
<button onClick={this.handleClick}>
69+
You’ve clicked this {this.state.count} times(s)
70+
</button>
71+
);
72+
}
73+
}
74+
```
75+
76+
The `Counter` component starts with an initial state of `{ count: 0 }`. Note that this state is in fact a regular React component state. To update it, we use a click action which we identify by its type `"CLICK"` (This is similar to the way we actions are identified in Redux).
77+
78+
The `reducer` will receive this action and act accordingly. In our case, it will return an `Update()` effect with the modified state.
79+
80+
ReComponent comes with four different types of effects:
81+
82+
- `NoUpdate()` to signalize that nothing should happen.
83+
- `Update(state)` to update the state.
84+
- `SideEffects(fn)` to run an arbitrary function to issue a [side effect]. Side effects may never be run directly inside the reducer. A reducer should always be pure: For the same action applied onto the same state, it should return the same effects. This paves the way to future react features like async React and make it easier to maintain your code.
85+
- `UpdateWithSideEffects(state, fn)` to do both: update the state and then trigger the side effect.
86+
87+
By intelligently returning any of the four types, it is possible to transition between states at one place and without the need to use `setState()` manually which drastically simplifies our mental model: Whatever happens, it must go through the reducer first.
88+
89+
## Advanced Usage
90+
91+
Now that we‘ve learned how to use reducer components with React, it‘s time to look into more advanced use cases to effectively handle state transitions across bigger portions of your app.
92+
93+
### Manage State Across the Tree
94+
95+
You often want to pass state properties down to other children in order for them to behave properly. Some times, however, this tree is very deep and it might be inefficient to go through the whole tree (and thereby updating it) to pass a value down.
96+
97+
Fortunately, React 16.3.0 introduced a new API called [`createContext()`](https://reactjs.org/docs/context.html#reactcreatecontext) which we can leverage to implement this.
98+
99+
```js
100+
import React from "react";
101+
import { ReComponent, Update } from "reason-recomponent";
102+
103+
const { Provider, Consumer } = React.createContext();
104+
105+
class Counter extends React.Component {
106+
render() {
107+
return (
108+
<Consumer>
109+
{({ state, handleClick }) => (
110+
<button onClick={handleClick}>
111+
You’ve clicked this {state.count} times(s)
112+
</button>
113+
)}
114+
</Consumer>
115+
);
116+
}
117+
}
118+
119+
class DeepTree extends React.Component {
120+
render() {
121+
return <Counter />;
122+
}
123+
}
124+
125+
class Container extends ReComponent {
126+
constructor() {
127+
super();
128+
this.handleClick = this.createSender("CLICK");
129+
}
130+
131+
initialState(props) {
132+
return {
133+
count: 0
134+
};
135+
}
136+
137+
reducer(action, state) {
138+
switch (action.type) {
139+
case "CLICK":
140+
return Update({ count: state.count + 1 });
141+
}
142+
}
143+
144+
render() {
145+
return (
146+
<Provider value={{ state: this.state, handleClick: this.handleClick }}>
147+
<DeepTree />
148+
</Provider>
149+
);
150+
}
151+
}
152+
```
153+
154+
If you‘re having troubles understanding this example, I recommend the fantastic documentation written by the React team about [Context](https://reactjs.org/docs/context.html#reactcreatecontext).
155+
156+
### Flow
157+
158+
_[Flow][] is a static type checker for JavaScript. This section is only relevant for you if you‘re using Flow in your application._
159+
160+
ReComponent comes with first class Flow support built in. By default, a ReComponent will behave like a regular Component and will require props and state to be typed:
161+
162+
```js
163+
import * as React from "react";
164+
import { ReComponent, Update } from "reason-recomponent";
165+
166+
type Props = {};
167+
type State = { count: number };
168+
169+
class UntypedActionTypes extends ReComponent<Props, State> {
170+
handleClick = this.createSender("CLICK");
171+
172+
initialState(props) {
173+
return {
174+
count: 0
175+
};
176+
}
177+
178+
reducer(action, state) {
179+
switch (action.type) {
180+
case "CLICK":
181+
return Update({ count: state.count + 1 });
182+
default:
183+
return NoUpdate();
184+
}
185+
}
186+
187+
render() {
188+
return (
189+
<button onClick={this.handleClick}>
190+
You’ve clicked this {this.state.count} times(s)
191+
</button>
192+
);
193+
}
194+
}
195+
```
196+
197+
Without specifying our action types any further, we will allow all `string` values. It is, however, recommended that we type all action types using a union of string literals. This will further tighten the type checks and will even allow [exhaustiveness testing] to verify that every action is indeed handled.
198+
199+
```js
200+
import * as React from "react";
201+
import { ReComponent, Update } from "reason-recomponent";
202+
203+
type Props = {};
204+
type State = { count: number };
205+
type ActionTypes = "CLICK";
206+
207+
class TypedActionTypes extends ReComponent<Props, State, ActionTypes> {
208+
handleClick = this.createSender("CLICK");
209+
210+
initialState(props) {
211+
return {
212+
count: 0
213+
};
214+
}
215+
216+
reducer(action, state) {
217+
switch (action.type) {
218+
case "CLICK":
219+
return Update({ count: state.count + 1 });
220+
default: {
221+
return NoUpdate();
222+
}
223+
}
224+
}
225+
226+
render() {
227+
return (
228+
<button onClick={this.handleClick}>
229+
You’ve clicked this {this.state.count} times(s)
230+
</button>
231+
);
232+
}
233+
}
234+
```
235+
236+
Check out the [type definition tests](https://github.com/philipp-spiess/react-recomponent/blob/master/type-definitions/__tests__/ReComponent.js) for an example on exhaustive checking.
237+
238+
**Known Limitations With Flow:**
239+
240+
- While it is possible to exhaustively type check the reducer, Flow will still require every branch to return an effect. This is why the above examples returns `NoUpdate()` even though the branch can never be reached.
241+
242+
##
243+
244+
## API Reference
245+
246+
### Classes
247+
248+
- `ReComponent`
249+
- `initialState(props): state`
250+
251+
Initialize the state of the component based on its props.
252+
253+
- `reducer(action, state): effect`
254+
255+
Translates an action into an effect. This is the main place to update your component‘s state.
256+
257+
**Note:** Reducers should never trigger side effects directly. Instead, return them as effects.
258+
259+
- `send(action): void`
260+
261+
Sends an action to the reducer. The action *must* have a `type` property so the reducer can identify it.
262+
263+
- `createSender(actionType): fn`
264+
265+
Shorthand function to create a function that will send an action of the `actionType` type to the reducer.
266+
267+
If the sender function is called with an argument (for example a React event), this will be available at the `payload` prop. This follows the [flux-standard-actions][] naming convention.
268+
269+
- `RePureComponent`
270+
- Same `ReComponent` but based on [`React.PureComponent`](https://reactjs.org/docs/react-api.html#reactpurecomponent) instead.
271+
272+
### Effects
273+
274+
- `NoUpdate()`
275+
276+
Returning this effect will not cause the state to be updated.
277+
278+
- `Update(state)`
279+
280+
Returning this effect will update the state. Internally, this will use `setState()` with an updater function.
281+
282+
- `SideEffects(fn)`
283+
284+
Enqueues side effects to be run but will not update the component‘s state.
285+
286+
- `UpdateWithSideEffects(state, fn)`
287+
288+
Updates the component‘s state and _then_ calls the side effect function.
289+
290+
## License
291+
292+
[MIT](https://github.com/philipp-spiess/react-recomponent/blob/master/README.md)
293+
294+
[redux]: https://github.com/reduxjs/redux
295+
[reason]: https://reasonml.github.io/
296+
[reasonreact]: https://reasonml.github.io/reason-react/docs/en/state-actions-reducer.html
297+
[flux]: https://facebook.github.io/flux/
298+
[side effect]: https://en.wikipedia.org/wiki/Side_effect_(computer_science)
299+
[flow]: https://flow.org/en/
300+
[exhaustiveness testing]: https://blog.jez.io/flow-exhaustiveness/
301+
[flux-standard-actions]: https://github.com/redux-utilities/flux-standard-action

0 commit comments

Comments
 (0)