Skip to content

Commit 5a8933b

Browse files
committed
new example using transducers
1 parent ba1add2 commit 5a8933b

File tree

5 files changed

+264
-3
lines changed

5 files changed

+264
-3
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Github Repo Type N Search Example
2+
3+
https://oyanglul.us/react-most/examples/type-n-search/public/
4+
5+
## install
6+
```sh
7+
npm install
8+
npm run build
9+
npm start
10+
```
11+
12+
## Code Walk Through
13+
14+
it's really simple example(only 40 LOC) that reactively search github repo according to your query
15+
16+
0. create normal React Component
17+
18+
```js
19+
const TypeNsearch = (props)=>{
20+
let {search} = props.actions
21+
return <div>
22+
<input onChange={e=>search(e.target.value)}></input>
23+
<ul>
24+
{props.results&&props.results.map(item=>{
25+
return <li key={item.id}><a href={item.html_url}>{item.full_name} ({item.stargazers_count})</a></li>
26+
})}
27+
</ul>
28+
</div>
29+
}
30+
```
31+
32+
1. HOC(Higher Order Component)
33+
using `connect` to create a HOC over TypeNsearch
34+
35+
```js
36+
const MostTypeNSearch = connect(DATAFLOW)(TypeNsearch)
37+
```
38+
2. Compose Dataflow
39+
you see the place holder `DATAFLOW`, now we gonna fill in the real data flow how we enable the reactive action of our Component
40+
1. filter out stream only with `intent.type` of 'search'
41+
42+
```js
43+
function(intent$){
44+
let updateSink$ = intent$.filter(i=>i.type=='search')
45+
.debounce(500)
46+
...
47+
```
48+
using `debounce` will transform the stream to stream which only bouncing at certain time point
49+
```
50+
---冷笑--冷笑话-->
51+
52+
--------冷笑话-->
53+
```
54+
2. compose a VALID query URL
55+
56+
```js
57+
...
58+
.map(intent=>intent.value)
59+
.filter(query=>query.length > 0)
60+
.map(query=>GITHUB_SEARCH_API + query)
61+
...
62+
```
63+
3. flatMap the Response to our stream
64+
```js
65+
.flatMap(url=>most.fromPromise(
66+
rest(url).then(resp=>({
67+
type: 'dataUpdate',
68+
value: resp.entity
69+
}))))
70+
```
71+
72+
`flatMap` is simply just `map` and then `flat`
73+
74+
> just pretent one `-` as one sec
75+
76+
```
77+
intentStream --urlA---urlB--->
78+
rest(urlA) -------respA---->
79+
rest(urlB) ---------respB-->
80+
flatMap(rest)-------respA--respB--->
81+
```
82+
4. model
83+
now our intent stream become a data stream, let's make it a modle stream.
84+
``` js
85+
.filter(i=>i.type=='dataUpdate')
86+
.map(data=>JSON.parse(data.value).items)
87+
.map(items=>items.slice(0,10))
88+
```
89+
parse it to JS Object and only get the first ten results
90+
5. create state transforming stream
91+
```
92+
.map(items=>state=>({results: items}))
93+
```
94+
95+
```
96+
modleStream ---mA---mB--->
97+
stateStream ---state=>({results:mA})---state=>({results:mB})--->
98+
```
99+
100+
3. return `actions` and `sinks`
101+
```js
102+
return {
103+
search: value=>({type:'search',value}),
104+
updateSink$,
105+
}
106+
```
107+
return `search` then you can use `props.actions.search` in your Component
108+
109+
return `updateSink$` then it can be appled to HOC's state, HOC will pass the state to your Component as props
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"name": "react-most-type-n-search",
3+
"version": "0.0.1",
4+
"description": "",
5+
"browserify": {
6+
"transform": [
7+
[
8+
"babelify",
9+
{
10+
"extensions": [
11+
".es6",
12+
".jsx",
13+
".js"
14+
]
15+
}
16+
]
17+
]
18+
},
19+
"scripts": {
20+
"build": "NODE_ENV=production browserify src/app.jsx --extension=.jsx --extension=.es6| java -jar ../todomvc/bin/compiler.jar > public/app.js",
21+
"start": "ecstatic -p 8000 public",
22+
"watch": "watchify -d src/app.jsx --extension=.jsx --extension=.es6 -o public/app.js -dv",
23+
"test": "jest"
24+
},
25+
"jest": {
26+
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
27+
"testFileExtensions": [
28+
"es6",
29+
"js",
30+
"jsx"
31+
],
32+
"moduleFileExtensions": [
33+
"js",
34+
"json",
35+
"es6",
36+
"jsx"
37+
]
38+
},
39+
"dependencies": {
40+
"classnames": "^2.2.0",
41+
"lodash": "^4.0.0",
42+
"react": "^0.14.2",
43+
"react-dom": "^0.14.2",
44+
"rest": "^1.3.1",
45+
"transducers-js": "^0.4.174"
46+
},
47+
"devDependencies": {
48+
"babel": "^6.1.18",
49+
"babel-jest": "^6.0.0",
50+
"babel-plugin-lodash": "^2.0.1",
51+
"babel-plugin-transform-react-jsx": "^6.1.18",
52+
"babel-preset-es2015": "^6.1.18",
53+
"babelify": "^7.2.0",
54+
"browserify": "^12.0.1",
55+
"ecstatic": "^1.3.1",
56+
"jest-cli": "^0.7.0",
57+
"uglify-js": "^2.6.1",
58+
"watchify": "^3.6.1"
59+
},
60+
"author": "Jichao Ouyang",
61+
"license": "ISC",
62+
"babel": {
63+
"presets": [
64+
"es2015"
65+
],
66+
"plugins": [
67+
"transform-react-jsx",
68+
"lodash"
69+
]
70+
}
71+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {connect} from '../../../lib/react-most'
2+
import {map, filter, comp, mapcat} from 'transducers-js'
3+
import Most from '../../../lib/react-most'
4+
import ReactDOM from 'react-dom'
5+
import React from 'react'
6+
import most from 'most'
7+
import rest from 'rest'
8+
const GITHUB_SEARCH_API = 'https://api.github.com/search/repositories?q=';
9+
const TypeNsearch = (props)=>{
10+
let {search} = props.actions
11+
let error = props.error||{}
12+
return <div>
13+
<input onChange={e=>search(e.target.value)}></input>
14+
<span className={"red " + error.className}>{error.message}</span>
15+
<ul>
16+
{props.results&&props.results.map(item=>{
17+
return <li key={item.id}><a href={item.html_url}>{item.full_name} ({item.stargazers_count})</a></li>
18+
})}
19+
</ul>
20+
</div>
21+
}
22+
23+
const sendApiRequest = comp(
24+
map(i=>i.value),
25+
filter(q=>q.length>0),
26+
map(q=>GITHUB_SEARCH_API + q),
27+
map(url=>rest(url).then(resp=>({
28+
type: 'dataUpdate',
29+
value: resp.entity
30+
})))
31+
);
32+
33+
const generateStateFromResp = comp(
34+
filter(i=>i.type=='dataUpdate'),
35+
map(data=>JSON.parse(data.value).items),
36+
map(items=>items.slice(0,10)),
37+
map(items=>state=>({results: items}))
38+
)
39+
40+
const log = x=>console.log(x)
41+
const MostTypeNSearch = connect(function(intent$){
42+
let updateSink$ = intent$.filter(i=>i.type=='search')
43+
.debounce(500)
44+
.transduce(sendApiRequest)
45+
.flatMap(most.fromPromise)
46+
.transduce(generateStateFromResp)
47+
.flatMapError(error=>{
48+
console.log('[ERROR]:', error);
49+
return most.of({message:error.error,className:'display'})
50+
.merge(most.of({className:'hidden'}).delay(3000))
51+
.map(error=>state=>({error}))
52+
})
53+
54+
return {
55+
search: value=>({type:'search',value}),
56+
updateSink$,
57+
}
58+
})(TypeNsearch);
59+
60+
ReactDOM.render(<Most>
61+
<MostTypeNSearch/>
62+
</Most>, document.getElementById('app'));

examples/type-n-search/public/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<title>Type N Search</title>
7+
<style>
8+
.display {
9+
display: block;
10+
}
11+
.hidden {
12+
display: none;
13+
}
14+
.red {
15+
color: red;
16+
}
17+
</style>
718
</head>
819
<body>
920
<h1>Github Reactive Repo Search</h1>

examples/type-n-search/src/app.jsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import rest from 'rest'
77
const GITHUB_SEARCH_API = 'https://api.github.com/search/repositories?q=';
88
const TypeNsearch = (props)=>{
99
let {search} = props.actions
10+
let error = props.error||{}
1011
return <div>
1112
<input onChange={e=>search(e.target.value)}></input>
13+
<span className={"red " + error.className}>{error.message}</span>
1214
<ul>
1315
{props.results&&props.results.map(item=>{
1416
return <li key={item.id}><a href={item.html_url}>{item.full_name} ({item.stargazers_count})</a></li>
@@ -23,15 +25,21 @@ const MostTypeNSearch = connect(function(intent$){
2325
.map(intent=>intent.value)
2426
.filter(query=>query.length > 0)
2527
.map(query=>GITHUB_SEARCH_API + query)
26-
.flatMap(url=>most.fromPromise(
27-
rest(url).then(resp=>({
28+
.map(url=>rest(url).then(resp=>({
2829
type: 'dataUpdate',
2930
value: resp.entity
30-
}))))
31+
})))
32+
.flatMap(most.fromPromise)
3133
.filter(i=>i.type=='dataUpdate')
3234
.map(data=>JSON.parse(data.value).items)
3335
.map(items=>items.slice(0,10))
3436
.map(items=>state=>({results: items}))
37+
.flatMapError(error=>{
38+
console.log('[ERROR]:', error);
39+
return most.of({message:error.error,className:'display'})
40+
.merge(most.of({className:'hidden'}).delay(3000))
41+
.map(error=>state=>({error}))
42+
})
3543

3644
return {
3745
search: value=>({type:'search',value}),

0 commit comments

Comments
 (0)