implement react-infinite example

This commit is contained in:
Sérgio Ramos 2016-11-21 09:54:50 +00:00
parent af60c141f5
commit c866cfd63c
12 changed files with 356 additions and 0 deletions

View File

@ -0,0 +1,15 @@
{
"sourceMaps": "both",
"presets": [
"react",
"es2015"
],
"plugins": [
"react-hot-loader/babel",
"add-module-exports",
"syntax-async-functions",
["transform-object-rest-spread", {
"useBuiltIns": true
}]
]
}

View File

@ -0,0 +1,34 @@
{
"name": "react-infinite-spike",
"private": true,
"license": "private",
"scripts": {
"start": "webpack-dev-server --config webpack/index.js"
},
"dependencies": {
"build-array": "^1.0.0",
"delay": "^1.3.1",
"lodash.debounce": "^4.0.8",
"react": "^15.4.0",
"react-dom": "^15.4.0",
"react-hot-loader": "^3.0.0-beta.6",
"react-infinite": "^0.10.0",
"react-redux": "^4.4.6",
"redux": "^3.6.0",
"redux-logger": "^2.7.4",
"redux-promise-middleware": "^4.1.0",
"redux-thunk": "^2.1.0"
},
"devDependencies": {
"babel-core": "^6.18.2",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.7",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-transform-object-rest-spread": "^6.19.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"webpack": "^2.1.0-beta.27",
"webpack-dev-server": "^1.16.2"
}
}

View File

@ -0,0 +1,6 @@
# react-infinite
- not fetching when scrolling to the end of a big list
- it's keeping the location when we filter (kinda)
- tried with 100000 rows and it scrolled flawlessly, but it struggled filtering (I assume that the filter itself it the reason)
- because the calculations are based on a fixed container height, we might need to figure out how to handle a responsive ui where the height changes

View File

@ -0,0 +1,88 @@
const buildArray = require('build-array');
const delay = require('delay');
const actions = {
'FETCH_FULFILLED': (state, action) => {
return {
...state,
fetching: false,
items: (state.items || []).concat(action.payload)
};
},
'FETCH_PENDING': (state, action) => {
return {
...state,
fetching: true
};
},
'FILTER_PENDING': (state, action) => {
return {
...state,
fetching: true
};
},
'FILTER_FULFILLED': (state, action) => {
return {
...state,
fetching: false,
filtered: action.payload.length !== state.items.length
? action.payload
: null
};
},
};
const fetch = () => (dispatch, getState) => {
const {
filtered
} = getState();
if (filtered) {
return;
}
return dispatch({
type: 'FETCH',
payload: delay(500).then(() => {
const {
items = []
} = getState();
return buildArray(100000).map((v, i) => {
const id = items.length + i;
return {
id,
title: `test ${id}`
};
});
})
});
};
const filter = (payload) => (dispatch, getState) => {
const regexp = new RegExp(payload);
return dispatch({
type: 'FILTER',
payload: delay(500).then(() => {
const {
items = []
} = getState();
return items.filter((item) => {
return regexp.test(item.title);
}).sort((a, b) => a.id - b.id);
})
});
};
module.exports = (state, action) => {
return actions[action.type]
? actions[action.type](state, action)
: state;
};
module.exports.fetch = fetch;
module.exports.filter = filter;

18
spikes/list/react-infinite/src/index.js vendored Normal file
View File

@ -0,0 +1,18 @@
const Store = require('./store');
const ReactDOM = require('react-dom');
const React = require('react');
const render = () => {
const Root = require('./root');
ReactDOM.render(
<Root store={Store()} />,
document.getElementById('root')
);
};
render();
if (module.hot) {
module.hot.accept('./root', render);
}

76
spikes/list/react-infinite/src/list.js vendored Normal file
View File

@ -0,0 +1,76 @@
const debounce = require('lodash.debounce');
const ReactRedux = require('react-redux');
const Infinite = require('react-infinite');
const actions = require('./actions');
const React = require('react');
const {
connect
} = ReactRedux;
const {
fetch,
filter
} = actions;
const mapStateToProps = (state) => {
return state;
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
fetch: () => {
return dispatch(fetch());
},
filter: (payload) => {
return dispatch(filter(payload));
}
}
};
const List = ({
items = [],
filtered,
input = '',
fetching = false,
fetch,
filter
}) => {
const _items = (filtered || items).map((item) => {
return (
<div key={item.id}>
{item.title}
</div>
);
});
const _loading = (
<div>
Loading...
</div>
);
const _filter = debounce(filter, 100);
const onChange = (ev) => _filter(ev.target.value);
return (
<div>
<input onChange={onChange} />
<Infinite
containerHeight={200}
elementHeight={20}
infiniteLoadBeginEdgeOffset={200}
onInfiniteLoad={fetch}
isInfiniteLoading={fetching}
loadingSpinnerDelegate={_loading}
>
{_items}
</Infinite>
</div>
);
};
module.exports = connect(
mapStateToProps,
mapDispatchToProps,
)(List);

24
spikes/list/react-infinite/src/root.js vendored Normal file
View File

@ -0,0 +1,24 @@
const ReactRedux = require('react-redux');
const ReactHotLoader = require('react-hot-loader');
const React = require('react');
const List = require('./list');
const {
AppContainer
} = ReactHotLoader;
const {
Provider
} = ReactRedux;
module.exports = ({
store
}) => {
return (
<AppContainer>
<Provider store={store}>
<List />
</Provider>
</AppContainer>
);
};

19
spikes/list/react-infinite/src/store.js vendored Normal file
View File

@ -0,0 +1,19 @@
const createLogger = require('redux-logger');
const promiseMiddleware = require('redux-promise-middleware').default;
const thunk = require('redux-thunk').default;
const redux = require('redux');
const reducer = require('./actions');
const {
createStore,
compose,
applyMiddleware
} = redux;
module.exports = (state = Object.freeze({})) => {
return createStore(reducer, state, applyMiddleware(
createLogger(),
promiseMiddleware(),
thunk
));
};

View File

@ -0,0 +1,7 @@
*
!.gitignore
!.gitkeep
!index.html
js/*
!js/.gitkeep

View File

@ -0,0 +1,10 @@
<!doctype html>
<html lang='en-US'>
<head>
<title>Infinite List</title>
</head>
<body>
<div id='root'></div>
<script src='main.js'></script>
</body>
</html>

View File

@ -0,0 +1,30 @@
const webpack = require('webpack');
const path = require('path');
const plugins = {
'no-errors-plugin': new webpack.NoErrorsPlugin(),
};
exports.config = {
context: path.join(__dirname, '../'),
output: {
path: path.join(__dirname, '../static'),
publicPath: '/',
filename: '[name].js'
},
plugins: [
new webpack.NoErrorsPlugin()
],
module: {
loaders: [{
test: /js?$/,
exclude: /node_modules/,
include: [
path.join(__dirname, '../src')
],
loader: 'babel-loader'
}]
}
};
exports.plugins = plugins;

View File

@ -0,0 +1,29 @@
const base = require('./base.js');
const webpack = require('webpack');
const path = require('path');
const devServer = {
contentBase: [
path.join(__dirname, '../static/')
],
hot: true,
compress: true,
lazy: false,
historyApiFallback: {
index: './index.html'
}
};
module.exports = Object.assign(base.config, {
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./src/index.js'
],
plugins: base.config.plugins.concat([
new webpack.HotModuleReplacementPlugin()
]),
devtool: 'source-map',
devServer
});