├── .eslintrc.js ├── .gitattributes ├── .github └── dependabot.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── header.png └── screenshots │ ├── example-section-list.jpg │ └── performance.gif ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── basics │ │ ├── _category_.json │ │ ├── columns-list.md │ │ ├── example.md │ │ ├── sections-list.md │ │ └── standard-list.md │ ├── extras │ │ ├── _category_.json │ │ └── migrate-flatlist.md │ ├── getting-started.md │ ├── how-contribute.md │ ├── intro.md │ ├── methods.md │ └── props.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ └── css │ │ └── custom.css ├── static │ ├── .nojekyll │ └── img │ │ ├── favicon.ico │ │ └── logo.svg └── yarn.lock ├── example ├── .expo-shared │ ├── README.md │ └── assets.json ├── .gitignore ├── App.js ├── app.json ├── assets │ ├── adaptive-icon.png │ ├── favicon.ico │ ├── favicon.png │ ├── icon.png │ └── splash.png ├── babel.config.js ├── package.json ├── src │ ├── Home.jsx │ ├── data │ │ ├── data.json │ │ └── sections.json │ ├── lists │ │ ├── ColumnsList.jsx │ │ ├── CompareList.jsx │ │ ├── List.jsx │ │ ├── MultiSelectList.jsx │ │ ├── SectionList.jsx │ │ ├── SelectList.jsx │ │ └── components │ │ │ └── Block.jsx │ └── utils │ │ └── generate.js └── yarn.lock ├── lib ├── BigList.jsx ├── BigListItem.jsx ├── BigListItemRecycler.js ├── BigListPlaceholder.jsx ├── BigListProcessor.js ├── BigListSection.jsx ├── assets │ └── placeholder.png ├── index.d.ts ├── index.js └── utils.js ├── package.json ├── scripts └── dist.js ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "babel-eslint", 4 | extends: [ 5 | "@react-native-community", 6 | "plugin:react/recommended", 7 | "plugin:react-native/all", 8 | "standard", 9 | "prettier", 10 | ], 11 | plugins: ["jest", "react-hooks", "prettier", "simple-import-sort", "import"], 12 | rules: { 13 | "simple-import-sort/exports": "error", 14 | "simple-import-sort/imports": [ 15 | "warn", 16 | { 17 | groups: [ 18 | [ 19 | // Packages. `react` related packages come first. 20 | "^react$", 21 | "^prop-types$", 22 | "^react-native$", 23 | "^react-native", 24 | // Others libs 25 | "^[A-Za-z0-9]", 26 | "^", 27 | ], 28 | // Relative imports 29 | [ 30 | // Side effect imports. 31 | "^\\u0000", 32 | // Parent imports. Put `..` last. 33 | "^\\.\\.(?!/?$)", 34 | "^\\.\\./?$", 35 | // Other relative imports. Put same-folder imports and `.` last. 36 | "^\\./(?=.*/)(?!/?$)", 37 | "^\\.(?!/?$)", 38 | "^\\./?$", 39 | ], 40 | ], 41 | }, 42 | ], 43 | "import/first": "warn", 44 | "import/newline-after-import": "warn", 45 | "import/no-duplicates": "warn", 46 | "react-native/no-raw-text": 0, 47 | "react-native/no-inline-styles": 0, 48 | "react/prop-types": 0, 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | package-lock.json 6 | 7 | node_modules 8 | 9 | # Xcode 10 | # 11 | build/ 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata 21 | *.xccheckout 22 | *.moved-aside 23 | DerivedData 24 | *.hmap 25 | *.ipa 26 | *.xcuserstate 27 | project.xcworkspace 28 | 29 | # Android/IntelliJ 30 | # 31 | build/ 32 | .idea 33 | .gradle 34 | local.properties 35 | *.iml 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # BUCK 44 | buck-out/ 45 | \.buckd/ 46 | *.keystore 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://docs.fastlane.tools/best-practices/source-control/ 54 | 55 | */fastlane/report.xml 56 | */fastlane/Preview.html 57 | */fastlane/screenshots 58 | 59 | # Bundle artifact 60 | *.jsbundle 61 | 62 | # Distribute 63 | dist/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Node Modules 2 | **/node_modules 3 | node_modules 4 | # Example 5 | example 6 | # Github 7 | .github/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | example/.expo 2 | example/.expo-shared 3 | example/web-build 4 | example/node_modules 5 | dist/ 6 | docs/.docusaurus 7 | docs/build -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | bracketSameLine: false, 4 | singleQuote: false, 5 | trailingComma: "all", 6 | tabWidth: 2, 7 | semi: true, 8 | }; 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.6.0](https://github.com/marcocesarato/react-native-big-list/compare/v1.5.6...v1.6.0) (2022-11-06) 6 | 7 | 8 | ### Features 9 | 10 | * add support for native animation on scroll ([2a84fb7](https://github.com/marcocesarato/react-native-big-list/commit/2a84fb7a0ff59e3482d3fc3ba97b517989ad9751)) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * rename animatedOffsetValues to nativeOffsetValues in index.d.ts ([06e3f66](https://github.com/marcocesarato/react-native-big-list/commit/06e3f66914e846e65791d0cca9e1df00ebf87b88)) 16 | 17 | ### [1.5.6](https://github.com/marcocesarato/react-native-big-list/compare/v1.5.5...v1.5.6) (2022-10-22) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * allow overwriting elevation in style of section header ([be68fb1](https://github.com/marcocesarato/react-native-big-list/commit/be68fb16382bc2a10dd0728059e4726f81d1a4eb)) 23 | 24 | ### [1.5.5](https://github.com/marcocesarato/react-native-big-list/compare/v1.5.4...v1.5.5) (2022-10-12) 25 | 26 | ### [1.5.4](https://github.com/marcocesarato/react-native-big-list/compare/v1.5.3...v1.5.4) (2022-02-28) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * regex-ify UNSAFE_ autobind ([b9205ec](https://github.com/marcocesarato/react-native-big-list/commit/b9205ec608f7ddd8977f0a2afc3b42e19bc68260)) 32 | 33 | ### [1.5.3](https://github.com/marcocesarato/react-native-big-list/compare/v1.5.2...v1.5.3) (2022-02-23) 34 | 35 | ### Bug Fixes 36 | 37 | * onViewableItemsChanged function issues ([7c67cb](https://github.com/marcocesarato/react-native-big-list/commit/7c67cbd0e09461e9e8025f08932a5053723e43a4)), closes [#120](https://github.com/marcocesarato/react-native-big-list/issues/141) 38 | 39 | 40 | ### [1.5.2](https://github.com/marcocesarato/react-native-big-list/compare/v1.5.1...v1.5.2) (2022-01-20) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * fallback scroll view component ([76719a7](https://github.com/marcocesarato/react-native-big-list/commit/76719a7af578c2db3ef3028d88777e635fda969f)) 46 | 47 | ### [1.5.1](https://github.com/marcocesarato/react-native-big-list/compare/v1.5.0...v1.5.1) (2022-01-19) 48 | 49 | 50 | ### Bug Fixes 51 | 52 | * add scroll view components types ([131c706](https://github.com/marcocesarato/react-native-big-list/commit/131c7065319024aa2b6e7f1a5db10d10c4fd3bc3)), closes [#141](https://github.com/marcocesarato/react-native-big-list/issues/141) 53 | 54 | ## [1.5.0](https://github.com/marcocesarato/react-native-big-list/compare/v1.4.3...v1.5.0) (2022-01-15) 55 | 56 | 57 | ### Features 58 | 59 | * custom scroll view component ([0c7f425](https://github.com/marcocesarato/react-native-big-list/commit/0c7f425bcc7f25c80514414b1ff50473adb63848)) 60 | 61 | ### [1.4.3](https://github.com/marcocesarato/react-native-big-list/compare/v1.4.2...v1.4.3) (2021-09-30) 62 | 63 | ### Features 64 | 65 | - Improve typing and documentation 66 | 67 | ## [1.4.2](https://github.com/marcocesarato/react-native-big-list/compare/v1.4.1...v1.4.2) (2021-09-02) 68 | 69 | ### Bug Fixes 70 | 71 | - Get item offset with height as function [#47](https://github.com/marcocesarato/react-native-big-list/issues/47) ([435fe4](https://github.com/marcocesarato/react-native-big-list/commit/435fe489d3b3a445ef81a6abd9d63353703681f5)) 72 | - Get item offset with height as function on sections [#47](https://github.com/marcocesarato/react-native-big-list/issues/47) ([048b5a](https://github.com/marcocesarato/react-native-big-list/commit/048b5a6a76a302c62b9c792bbab702916f1b8e73)) 73 | - Scroll to methods [#57](https://github.com/marcocesarato/react-native-big-list/issues/57) ([5bc699](https://github.com/marcocesarato/react-native-big-list/commit/5bc699573521319136254d39d69b42c7643c754c)) 74 | 75 | ### [1.4.1](https://github.com/marcocesarato/react-native-big-list/compare/v1.4.0...v1.4.1) (2021-08-08) 76 | 77 | ### Bug Fixes 78 | 79 | - make style prop merge writable ([56d8a49](https://github.com/marcocesarato/react-native-big-list/commit/56d8a49a18170dd2d446bc41e434c9c11ceac905)), closes [#43](https://github.com/marcocesarato/react-native-big-list/issues/43) 80 | 81 | ## [1.4.0](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.16...v1.4.0) (2021-08-03) 82 | 83 | ### Features 84 | 85 | - add inverted property ([a26f5c9](https://github.com/marcocesarato/react-native-big-list/commit/a26f5c92ece71d57322334bd3f2b64c313f2fea5)), closes [#31](https://github.com/marcocesarato/react-native-big-list/issues/31) 86 | 87 | ### Bug Fixes 88 | 89 | - section elevation on android ([114a831](https://github.com/marcocesarato/react-native-big-list/commit/114a8316b8d4cc6c919e48b7983c048430e27fb0)), closes [#38](https://github.com/marcocesarato/react-native-big-list/issues/38) 90 | 91 | ### [1.3.16](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.15...v1.3.16) (2021-07-21) 92 | 93 | ### Bug Fixes 94 | 95 | - empty item after header ([a7297ac](https://github.com/marcocesarato/react-native-big-list/commit/a7297acfd22849599a688c7cd5a26ed59b658473)), closes [#35](https://github.com/marcocesarato/react-native-big-list/issues/35) 96 | 97 | ## [1.3.15](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.14...v1.3.15) (2021-06-29) 98 | 99 | ### Bug Fixes 100 | 101 | ##### On Momentum End 102 | 103 | - Trigger on momentum end [#21](https://github.com/marcocesarato/react-native-big-list/issues/21) ([e823a0](https://github.com/marcocesarato/react-native-big-list/commit/e823a0beb85737c5c280778cc90feac777ed149a)) 104 | 105 | --- 106 | 107 | ## [1.3.14](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.13...v1.3.14) (2021-06-24) 108 | 109 | ### Bug Fixes 110 | 111 | - Scrolling methods ([c9207f](https://github.com/marcocesarato/react-native-big-list/commit/c9207fa24ce4ee401bed2c063c3250d8c5a3c357)) 112 | 113 | --- 114 | 115 | ## [1.3.13](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.12...v1.3.13) (2021-06-23) 116 | 117 | ### Features 118 | 119 | - Add key extractor to assign item state without recycle it [#13](https://github.com/marcocesarato/react-native-big-list/issues/13) ([383b31](https://github.com/marcocesarato/react-native-big-list/commit/383b31be68fff5b266db3244f03d6559fbc6b571)) 120 | 121 | --- 122 | 123 | ## [1.3.12](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.10...v1.3.12) (2021-06-20) 124 | 125 | ### Features 126 | 127 | - Hide and show marginals on empty list [#8](https://github.com/marcocesarato/react-native-big-list/issues/8) ([cf5c9f](https://github.com/marcocesarato/react-native-big-list/commit/cf5c9ff8059cfdd70d674153ae93965ab3b304be)) 128 | 129 | ### Bug Fixes 130 | 131 | - On viewable items on addition [#8](https://github.com/marcocesarato/react-native-big-list/issues/8) ([b8816d](https://github.com/marcocesarato/react-native-big-list/commit/b8816de462ccd66a3dbef030aa770846a761c3b5)) 132 | 133 | --- 134 | 135 | ## [1.3.10](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.9...v1.3.10) (2021-06-20) 136 | 137 | ### Bug Fixes 138 | 139 | ##### TypeScript 140 | 141 | - Make num columns and on viewable items change optional ([58f661](https://github.com/marcocesarato/react-native-big-list/commit/58f6618ffcb638f1913f68741482393e3e3457d8)) 142 | - Replace unknown with null and undefined ([e60f2c](https://github.com/marcocesarato/react-native-big-list/commit/e60f2c625ae3719e037bf19bda0668c2173c7556)) 143 | 144 | --- 145 | 146 | ## [1.3.9](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.8...v1.3.9) (2021-06-17) 147 | 148 | ### Bug Fixes 149 | 150 | - Static header with sections ([298d2b](https://github.com/marcocesarato/react-native-big-list/commit/298d2bd52c9e99e3477f12ce41b50dec87b6b1c2)) 151 | 152 | --- 153 | 154 | ## [1.3.8](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.7...v1.3.8) (2021-06-17) 155 | 156 | --- 157 | 158 | ## [1.3.7](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.6...v1.3.7) (2021-06-17) 159 | 160 | ### Features 161 | 162 | - Add column wrapper style [#6](https://github.com/marcocesarato/react-native-big-list/issues/6) ([2e28c9](https://github.com/marcocesarato/react-native-big-list/commit/2e28c93274cdd1909af98c7e0cf9a8e38facf0df)) 163 | - Add on viewable items changed [#8](https://github.com/marcocesarato/react-native-big-list/issues/8) ([0c709e](https://github.com/marcocesarato/react-native-big-list/commit/0c709e29e926a1c85745f0d04a8ba44a28124e1a)) 164 | 165 | ##### Placeholder 166 | 167 | - Add placeholder on fast scrolling [#7](https://github.com/marcocesarato/react-native-big-list/issues/7) ([6dc75b](https://github.com/marcocesarato/react-native-big-list/commit/6dc75b4fc2e1688cfc79b9eafc45150b134bf29b)) 168 | 169 | ### Bug Fixes 170 | 171 | - Disable placeholder prop ([1e085b](https://github.com/marcocesarato/react-native-big-list/commit/1e085b1555e57f8c9a0b4f51d974e00851f170f1)) 172 | - Force push header and footer items [#6](https://github.com/marcocesarato/react-native-big-list/issues/6) ([7f6f3a](https://github.com/marcocesarato/react-native-big-list/commit/7f6f3ad4a0b3cd805452c8668055a7f823c7be67)) 173 | - Issues on performances using sections ([f4413e](https://github.com/marcocesarato/react-native-big-list/commit/f4413ef7a9550072144aa9c97c9289313e6a03e0)) 174 | - Viewable items changed result [#8](https://github.com/marcocesarato/react-native-big-list/issues/8) ([f89c7c](https://github.com/marcocesarato/react-native-big-list/commit/f89c7cb077c8129c17e34a047bf5313802cd1a3b)) 175 | 176 | --- 177 | 178 | ## [1.3.6](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.5...v1.3.6) (2021-06-13) 179 | 180 | ### Bug Fixes 181 | 182 | - Sections condensed issue ([7517b0](https://github.com/marcocesarato/react-native-big-list/commit/7517b071e61c03bc90a7844a74abb1cbba2b964e)) 183 | 184 | --- 185 | 186 | ## [1.3.5](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.4...v1.3.5) (2021-06-12) 187 | 188 | --- 189 | 190 | ## [1.3.4](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.3...v1.3.4) (2021-06-12) 191 | 192 | --- 193 | 194 | ## [1.3.3](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.2...v1.3.3) (2021-06-11) 195 | 196 | ### Bug Fixes 197 | 198 | - Content container style overwritable ([a1dcf9](https://github.com/marcocesarato/react-native-big-list/commit/a1dcf9f63d052575e4c003a6c9ad363b72d584ff)) 199 | 200 | --- 201 | 202 | ## [1.3.2](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.1...v1.3.2) (2021-06-11) 203 | 204 | ### Bug Fixes 205 | 206 | - Num columns processor [#5](https://github.com/marcocesarato/react-native-big-list/issues/5) ([815735](https://github.com/marcocesarato/react-native-big-list/commit/815735214eb01e78bb7b66f10b4c8e977cb1c320)) 207 | 208 | --- 209 | 210 | ## [1.3.1](https://github.com/marcocesarato/react-native-big-list/compare/v1.3.0...v1.3.1) (2021-06-11) 211 | 212 | ### Bug Fixes 213 | 214 | ##### TypeScript 215 | 216 | - Add num columns type [#5](https://github.com/marcocesarato/react-native-big-list/issues/5) ([54e8fa](https://github.com/marcocesarato/react-native-big-list/commit/54e8fa419f61945ee8b569450ace58954c3c62ee)) 217 | 218 | --- 219 | 220 | ## [1.3.0](https://github.com/marcocesarato/react-native-big-list/compare/v1.2.5...v1.3.0) (2021-06-11) 221 | 222 | ### Features 223 | 224 | - Add batch size threshold ([988fa0](https://github.com/marcocesarato/react-native-big-list/commit/988fa0f79210579769ccf85a4196e2a0e09c6edc)) 225 | - Number of columns [#5](https://github.com/marcocesarato/react-native-big-list/issues/5) ([e1a4ab](https://github.com/marcocesarato/react-native-big-list/commit/e1a4ab2a2316fd193626fb8b966814634643fd2e)) 226 | 227 | ### Bug Fixes 228 | 229 | - Increase batch size ([e4c20c](https://github.com/marcocesarato/react-native-big-list/commit/e4c20c0704d0264426760f22579f9dc60f0f5d14)) 230 | 231 | --- 232 | 233 | ## [1.2.5](https://github.com/marcocesarato/react-native-big-list/compare/v1.2.4...v1.2.5) (2021-06-10) 234 | 235 | ### Features 236 | 237 | - Add on refresh event ([1ec3a4](https://github.com/marcocesarato/react-native-big-list/commit/1ec3a4d7016649e47bd906b410e8f545483b7c47)) 238 | 239 | --- 240 | 241 | ## [1.2.4](https://github.com/marcocesarato/react-native-big-list/compare/v1.2.3...v1.2.4) (2021-06-10) 242 | 243 | ### Features 244 | 245 | - Add on end reached event [#4](https://github.com/marcocesarato/react-native-big-list/issues/4) ([dc2ce4](https://github.com/marcocesarato/react-native-big-list/commit/dc2ce437bd73911c59a245dac5c50ae2e56f03ae)) 246 | 247 | --- 248 | 249 | ## [1.2.3](https://github.com/marcocesarato/react-native-big-list/compare/v1.2.2...v1.2.3) (2021-06-10) 250 | 251 | --- 252 | 253 | ## [1.2.2](https://github.com/marcocesarato/react-native-big-list/compare/v1.2.1...v1.2.2) (2021-06-10) 254 | 255 | --- 256 | 257 | ## [1.2.1](https://github.com/marcocesarato/react-native-big-list/compare/v1.2.0...v1.2.1) (2021-06-10) 258 | 259 | --- 260 | 261 | ## [1.2.0](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.10...v1.2.0) (2021-06-09) 262 | 263 | ### Features 264 | 265 | - Add sticky section headers enabled prop ([f39231](https://github.com/marcocesarato/react-native-big-list/commit/f39231072e1b4c1e953973f049a10951227deb44)) 266 | 267 | ### Bug Fixes 268 | 269 | - Sticky headers for web ([38198c](https://github.com/marcocesarato/react-native-big-list/commit/38198c9fd8a8276a3412aed36ba6fa5a5fe8662b)) 270 | 271 | --- 272 | 273 | ## [1.1.10](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.9...v1.1.10) (2021-06-09) 274 | 275 | ### Bug Fixes 276 | 277 | ##### TypeScript 278 | 279 | - Better typing ([052899](https://github.com/marcocesarato/react-native-big-list/commit/0528993c75194786ffbf716e54b916ffa82183cb)) 280 | 281 | --- 282 | 283 | ## [1.1.9](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.8...v1.1.9) (2021-06-08) 284 | 285 | ### Bug Fixes 286 | 287 | - Add scroll to location method ([d5e288](https://github.com/marcocesarato/react-native-big-list/commit/d5e28890af778c854138e0d3a8bd50d434cad6cf)) 288 | - Is visible method ([56fbe9](https://github.com/marcocesarato/react-native-big-list/commit/56fbe92930181d45694e17932b6357abad11e8ab)) 289 | 290 | ##### TypeScript 291 | 292 | - Render accessory declaration ([ace0aa](https://github.com/marcocesarato/react-native-big-list/commit/ace0aaea80a8c44163d67f2489d1ee00f74d357e)) 293 | 294 | --- 295 | 296 | ## [1.1.8](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.7...v1.1.8) (2021-06-07) 297 | 298 | ### Bug Fixes 299 | 300 | ##### TypeScript 301 | 302 | - Item height declaration issues ([313895](https://github.com/marcocesarato/react-native-big-list/commit/313895abfd3bf2d30047f5676a377f7268258f3d)) 303 | 304 | --- 305 | 306 | ## [1.1.7](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.6...v1.1.7) (2021-06-07) 307 | 308 | ### Bug Fixes 309 | 310 | ##### TypeScript 311 | 312 | - Some declaration issues ([02f50d](https://github.com/marcocesarato/react-native-big-list/commit/02f50d3a99005a5a9a5ac7757da543583333b54b)) 313 | 314 | --- 315 | 316 | ## [1.1.6](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.5...v1.1.6) (2021-06-07) 317 | 318 | ### Features 319 | 320 | - Add some new methods from scroll view ([81abd5](https://github.com/marcocesarato/react-native-big-list/commit/81abd595d9ea66c668cf2be59a7e2a73219f751a)) 321 | 322 | ### Bug Fixes 323 | 324 | - Get item call ([bf82d7](https://github.com/marcocesarato/react-native-big-list/commit/bf82d7383b2f3eea313eda18dfb2d573409f6f81)) 325 | 326 | --- 327 | 328 | ## [1.1.5](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.4...v1.1.5) (2021-06-07) 329 | 330 | ### Features 331 | 332 | - Add get item layout comaptibility ([1d93b5](https://github.com/marcocesarato/react-native-big-list/commit/1d93b5f3cfd2581a20290af9a625a1fe16f69211)) 333 | - Add scroll to item and scroll to offset ([182463](https://github.com/marcocesarato/react-native-big-list/commit/182463381213036406c7b922c8401910141824f4)) 334 | 335 | ### Bug Fixes 336 | 337 | ##### TypeScript 338 | 339 | - Render declarations ([5b9513](https://github.com/marcocesarato/react-native-big-list/commit/5b951392c9db6f98dc57b389bfae719325d22df5)) 340 | 341 | --- 342 | 343 | ## [1.1.4](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.3...v1.1.4) (2021-06-07) 344 | 345 | ### Bug Fixes 346 | 347 | - Check if empty on create element ([01885b](https://github.com/marcocesarato/react-native-big-list/commit/01885b95acd931ba59898a36839e9cbd520e6d21)) 348 | - Replace reflect methods ([001dfa](https://github.com/marcocesarato/react-native-big-list/commit/001dfa2fc818c97cd0b8e73151a1d2c50f5e73c7)) 349 | 350 | --- 351 | 352 | ## [1.1.3](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.2...v1.1.3) (2021-06-07) 353 | 354 | ### Bug Fixes 355 | 356 | - Get list processor class ([108664](https://github.com/marcocesarato/react-native-big-list/commit/10866498cebf5f41aa65a3d801bbece760afc00d)) 357 | 358 | --- 359 | 360 | ## [1.1.2](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.1...v1.1.2) (2021-06-07) 361 | 362 | ### Features 363 | 364 | - Add scroll methods ([e28447](https://github.com/marcocesarato/react-native-big-list/commit/e284477d4e5b0c4a3ba5893f02908c6d9d478791)) 365 | 366 | --- 367 | 368 | ## [1.1.1](https://github.com/marcocesarato/react-native-big-list/compare/v1.1.0...v1.1.1) (2021-06-06) 369 | 370 | ### Features 371 | 372 | ##### Props 373 | 374 | - Adjust component header, footer and empty types ([34d4c4](https://github.com/marcocesarato/react-native-big-list/commit/34d4c439d995d087b0e34c8ecf43e40c80da7990)) 375 | 376 | --- 377 | 378 | ## [1.1.0](https://github.com/marcocesarato/react-native-big-list/compare/v1.0.0...v1.1.0) (2021-06-06) 379 | 380 | ### Features 381 | 382 | - Add list component like flatlist for header, footer and empty ([796c1f](https://github.com/marcocesarato/react-native-big-list/commit/796c1f78062ab47bb1c5d5ad3a395161a33168eb)) 383 | 384 | ##### Prop Types 385 | 386 | - Add prop types ([2e852a](https://github.com/marcocesarato/react-native-big-list/commit/2e852ad17313915640ec00978d919f47b8191411)) 387 | 388 | ##### TypeScript 389 | 390 | - Add list component header, footer and empty types ([0129b6](https://github.com/marcocesarato/react-native-big-list/commit/0129b6f3bbe19b00b2ff9347b707f4396b5b6d2e)) 391 | - Add typescript types ([de7b52](https://github.com/marcocesarato/react-native-big-list/commit/de7b52eb8f88e9bc3dc6a39b93ca63e873733fa8)) 392 | - Add types for subcomponents ([48cc67](https://github.com/marcocesarato/react-native-big-list/commit/48cc67517b94c97e78c2ba883dd4c7b3349a9d38)) 393 | 394 | ### Bug Fixes 395 | 396 | - Component for header, footer and empty multi usage ([443ead](https://github.com/marcocesarato/react-native-big-list/commit/443ead6b5f5b6dfc8fd2fce8cea4031de3225fc5)) 397 | 398 | ##### TypeScript 399 | 400 | - Children to null and optional on subcomponents ([56dc20](https://github.com/marcocesarato/react-native-big-list/commit/56dc2006cdef33213390fd6a7ae7d09a92cc5762)) 401 | - Component header, footer and empty types ([af9066](https://github.com/marcocesarato/react-native-big-list/commit/af906653adee3a96835639cd16ddebe0e7dbdb73)) 402 | 403 | --- 404 | 405 | ## [1.0.0](https://github.com/marcocesarato/react-native-big-list/compare/2f2db97741a9ff0e69f204965e040152883eafab...v1.0.0) (2021-06-05) 406 | 407 | --- 408 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | React Native Big List 4 | 5 | ### If this project has helped you out, please support us with a star 🌟 6 | 7 |
8 | 9 | [![NPM version](http://img.shields.io/npm/v/react-native-big-list.svg?style=for-the-badge)](http://npmjs.org/package/react-native-big-list) 10 | [![js-prittier-style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=for-the-badge)](https://prettier.io/) 11 | [![Compatibility](https://img.shields.io/badge/platform-android%20%7C%20ios%20%7C%20Web%20%7C%20expo-blue.svg?style=for-the-badge)](http://npmjs.org/package/react-native-big-list) 12 | 13 | 14 | 15 | [Documentation](https://marcocesarato.github.io/react-native-big-list-docs/) 16 | 17 |
18 | 19 | ## 📘 Description 20 | 21 | #### What is this? 22 | 23 | This is a high performance list view for React Native with support for complex layouts using a similar FlatList usage to make easy the replacement. 24 | This list implementation for big list rendering on React Native works with a recycler focused on performance and memory usage and so it permits processing thousands items on the list. 25 | 26 | You can also try it on the published web app: [Demo](https://marcocesarato.github.io/react-native-big-list/) 27 | 28 | #### Why another list library? 29 | 30 | React Native's FlatList is great but when it comes to big lists it has some flaws because of its item caching. 31 | Exists some alternatives like `react-native-largelist` and `recyclerlistview` but both have some issues. 32 | 33 | The `react-native-largelist` isn't compatible with web and Expo, has native code that sometimes need to be readjusted and maintained, have a weird list item recycles (because it never has blank items), need data restructure and have some issues when trying to process a lot of data (eg: 100,000 items) because it would freeze the CPU. 34 | 35 | The `recyclerlistview` is performant but suffers from an empty frame on mount, weird scroll positions when trying to scroll to an element on mount, and the implementation of sticky headers conflicts with `Animated`. 36 | 37 | #### How it works? 38 | 39 | Recycler makes it easy to efficiently display large sets of data. You supply the data and define how each item looks, and the recycler library dynamically creates the elements when they're needed. 40 | As the name implies, the recycler recycles those individual elements. When an item scrolls off the screen, the recycler doesn't destroy its view. Instead, the recycler reuses the view for new items that have scrolled onscreen. This reuse vastly improves performance, improving your app's responsiveness and reducing power consumption. 41 | 42 | When list can't render your items fast enough the non-rendered components will appear as blank space. 43 | 44 | This library is fully JS native, so it's compatible with all available platforms: _Android, iOS, Windows, MacOS, Web and Expo_. 45 | 46 | ## 📖 Install 47 | 48 | Install the library from npm or yarn just running one of the following command lines: 49 | 50 | | npm | yarn | 51 | | ------------------------------------------ | -------------------------------- | 52 | | `npm install react-native-big-list --save` | `yarn add react-native-big-list` | 53 | 54 | ## 💻 Usage 55 | 56 | > Read also [How to migrate from FlatList](https://marcocesarato.github.io/react-native-big-list-docs/extras/migrate-flatlist/) 57 | 58 | Basic example: 59 | 60 | ```javascript 61 | import BigList from "react-native-big-list"; 62 | // ... 63 | const MyExample = ({ data }) => { 64 | const renderItem = ({ item, index }) => ; 65 | return ; 66 | }; 67 | ``` 68 | 69 | For more examples check the `example` directory the `list` directory or check the [Documentation](https://marcocesarato.github.io/react-native-big-list-docs/basics/standard-list) 70 | 71 | ## 🎨 Screenshots 72 | 73 | | BigList vs FlatList | Section List | 74 | | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | 75 | | | | 76 | 77 | ## ⚡️ Example 78 | 79 | ### Snippets 80 | 81 | - [Standard List](https://marcocesarato.github.io/react-native-big-list-docs/basics/standard-list) 82 | - [Columns List](https://marcocesarato.github.io/react-native-big-list-docs/basics/columns-list) 83 | - [Sections List](https://marcocesarato.github.io/react-native-big-list-docs/basics/sections-list) 84 | 85 | ### Expo 86 | 87 | Clone or download repo and after: 88 | 89 | ```shell 90 | cd Example 91 | yarn install # or npm install 92 | expo start 93 | ``` 94 | 95 | Open Expo Client on your device. Use it to scan the QR code printed by `expo start`. You may have to wait a minute while your project bundles and loads for the first time. 96 | 97 | You can also try it on the published web app: [Demo](https://marcocesarato.github.io/react-native-big-list/) 98 | 99 | ## 💡 Props and Methods 100 | 101 | The list has the same props of the [ScrollView](https://reactnative.dev/docs/scrollview#props) in addition to its specific [Props](https://marcocesarato.github.io/react-native-big-list-docs/props) and [Methods](https://marcocesarato.github.io/react-native-big-list-docs/methods). 102 | 103 | ## 🤔 How to contribute 104 | 105 | Have an idea? Found a bug? Please raise to [ISSUES](https://github.com/marcocesarato/react-native-big-list/issues) or [PULL REQUEST](https://github.com/marcocesarato/react-native-big-list/pulls). 106 | Contributions are welcome and are greatly appreciated! Every little bit helps, and credit will always be given. 107 | 108 |

109 |
110 | 111 | 112 | 113 |

114 | -------------------------------------------------------------------------------- /assets/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/assets/header.png -------------------------------------------------------------------------------- /assets/screenshots/example-section-list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/assets/screenshots/example-section-list.jpg -------------------------------------------------------------------------------- /assets/screenshots/performance.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/assets/screenshots/performance.gif -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ## Installation 6 | 7 | ```console 8 | yarn install 9 | ``` 10 | 11 | ## Local Development 12 | 13 | ```console 14 | yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ## Build 20 | 21 | ```console 22 | yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ## Deployment 28 | 29 | ```console 30 | GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve("@docusaurus/core/lib/babel/preset")], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/basics/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Basics", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/basics/columns-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Columns list 6 | 7 | Like the [standard list](standard-list.md), the prop `data` is required. You just need to specify the number of columns using the prop `numColumns` that will format the list in column format by placing the elements side by side with x elements per x number of columns per row. 8 | 9 | ### Props 10 | 11 | #### `numColumns` 12 | 13 | | Type | Required | Default | 14 | | ------ | -------- | ------- | 15 | | number | No | `1` | 16 | 17 | ## Example 18 | 19 | Here and example 20 | 21 | ```javascript 22 | import BigList from "react-native-big-list"; 23 | 24 | /* ... */ 25 | 26 | const data = [ 27 | { label: "1", value: 1 /* ... */ }, 28 | { label: "2", value: 2 /* ... */ }, 29 | { label: "3", value: 3 /* ... */ }, 30 | { label: "4", value: 4 /* ... */ }, 31 | { label: "5", value: 5 /* ... */ }, 32 | /* ... */ 33 | ]; 34 | 35 | const renderItem = ({ item, index }) => ( 36 | 37 | ); 38 | const renderEmpty = () => ; 39 | const renderHeader = () => ; 40 | const renderFooter = () => ; 41 | 42 | return ( 43 | 54 | ); 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/docs/basics/example.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Example 6 | 7 | ## Expo 8 | 9 | Clone or download repo and after: 10 | 11 | ```shell 12 | cd Example 13 | yarn install # or npm install 14 | expo start 15 | ``` 16 | 17 | Open Expo Client on your device. 18 | Use it to scan the QR code printed by expo start. You may have to wait a minute while your project bundles and loads for the first time. 19 | -------------------------------------------------------------------------------- /docs/docs/basics/sections-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Sections List 6 | 7 | To create a section list you need to specify the `sections` prop. For simplicity, `sections` is a plain array containing another plain array with the items (section items) to render. If specified `data` prop will be ignored and so it'll replace the `data` prop. 8 | 9 | :::info 10 | It's required if no data is specified or if you want to use sticky headers (look at **renderSectionHeader** prop) with sections.
11 | It enables also the **renderSectionHeader** and **renderSectionFooter** props. 12 | ::: 13 | 14 | ### Data examples 15 | 16 | ```js 17 | [ 18 | // Section 1 19 | [1, 2], 20 | // Section 2 21 | [3, 4], 22 | /* ... */ 23 | ]; 24 | ``` 25 | 26 | ```js 27 | [ 28 | [ 29 | // Section 1 30 | { label: "1", value: 1 /* ... */ }, 31 | { label: "2", value: 2 /* ... */ }, 32 | ], 33 | [ 34 | // Section 2 35 | { label: "3", value: 3 /* ... */ }, 36 | { label: "4", value: 4 /* ... */ }, 37 | ], 38 | /* ... */ 39 | ]; 40 | ``` 41 | 42 | ## Example 43 | 44 | ```javascript 45 | import BigList from "react-native-big-list"; 46 | 47 | /* ... */ 48 | 49 | const sections = [ 50 | [ 51 | // Section 0 52 | { label: "1", value: 1 /* ... */ }, 53 | { label: "2", value: 2 /* ... */ }, 54 | ], 55 | [ 56 | // Section 1 57 | { label: "3", value: 3 /* ... */ }, 58 | { label: "4", value: 4 /* ... */ }, 59 | ], 60 | [ 61 | // Section 2 62 | { label: "6", value: 6 /* ... */ }, 63 | { label: "6", value: 6 /* ... */ }, 64 | ], 65 | /* ... */ 66 | ]; 67 | 68 | const renderItem = ({ item, index }) => ( 69 | 70 | ); 71 | const renderHeader = () => ; 72 | const renderFooter = () => ; 73 | const renderSectionHeader = () => ; 74 | const renderSectionFooter = () => ; 75 | 76 | return ( 77 | 90 | ); 91 | ``` 92 | -------------------------------------------------------------------------------- /docs/docs/basics/standard-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Standard list 6 | 7 | The prop `data` is required for a standard list. For simplicity, `data` is a plain array containing the items to render. 8 | 9 | ### Data examples 10 | 11 | ```js 12 | [1, 2, 3, 4, 5, 6 /* ... */]; 13 | ``` 14 | 15 | ```js 16 | [ 17 | { label: "1", value: 1 /* ... */ }, 18 | { label: "2", value: 2 /* ... */ }, 19 | /* ... */ 20 | ]; 21 | ``` 22 | 23 | ## Example 24 | 25 | ```javascript 26 | import BigList from "react-native-big-list"; 27 | 28 | /* ... */ 29 | 30 | const data = [ 31 | { label: "1", value: 1 /* ... */ }, 32 | { label: "2", value: 2 /* ... */ }, 33 | { label: "3", value: 3 /* ... */ }, 34 | { label: "4", value: 4 /* ... */ }, 35 | { label: "5", value: 5 /* ... */ }, 36 | /* ... */ 37 | ]; 38 | 39 | const renderItem = ({ item, index }) => ( 40 | 41 | ); 42 | const renderEmpty = () => ; 43 | const renderHeader = () => ; 44 | const renderFooter = () => ; 45 | 46 | return ( 47 | 57 | ); 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/docs/extras/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": " Extras", 3 | "position": 4 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/extras/migrate-flatlist.md: -------------------------------------------------------------------------------- 1 | # Migrate from FlatList 2 | 3 | Migration and then the replacement of a FlatList is very simple. 4 | 5 | BigList permit a fast way replacement of the FlatList component using some aliases of props that **replace** the default props. 6 | The props compatibles are listed on [Props List](props.md#flatlist). 7 | All of them should be replaced with their related props of BigList _(recommended)_. 8 | 9 | The main props of FlatList are compatible with BigList like `data` and its structure, `ListHeaderComponent`, `ListHeaderComponentStyle` etc... 10 | 11 | ## Getting started 12 | 13 | You just need to: 14 | 15 | - Import the component 16 | - Replace the name of the component from `FlatList` to `BigList`. 17 | - Add the props for the heights 18 | 19 | :::note 20 | 21 | BigList need to define a static height of the items for maintain great performances. 22 | If you use `getItemLayout` you don't need to define `itemHeight`
23 | 24 | - `itemHeight` for items _(default 50)_ 25 | - `headerHeight` for the header _(default 0)_ 26 | - `footerHeight` for the footer _(default 0)_ 27 | 28 | ::: 29 | 30 | ### Example 31 | 32 | #### Before: 33 | 34 | ```js 35 | import { FlatList } from "react-native"; 36 | 37 | const ITEM_HEIGHT = 50; 38 | 39 | /* ... */ 40 | 41 | ({ 49 | length: ITEM_HEIGHT, 50 | offset: ITEM_HEIGHT * index, 51 | index, 52 | })} 53 | renderItem={renderItem} 54 | keyExtractor={(item) => item.value} 55 | />; 56 | ``` 57 | 58 | #### After: 59 | 60 | ```js 61 | import BigList from "react-native-big-list"; 62 | 63 | const ITEM_HEIGHT = 50; 64 | 65 | /* ... */ 66 | 67 | item.value} 72 | ListHeaderComponent={renderHeader} // Replaceable with `renderHeader` 73 | ListFooterComponent={renderFooter} // Replaceable with `renderFooter` 74 | ListFooterComponentStyle={styles.footer} // This works only with `ListFooterComponent` 75 | ListEmptyComponent={renderEmpty} // Replaceable with `renderEmpty` 76 | getItemLayout={(data, index) => ({ 77 | length: ITEM_HEIGHT, 78 | offset: ITEM_HEIGHT * index, 79 | index, 80 | })} // Replaceable with `itemHeight={ITEM_HEIGHT}` 81 | keyExtractor={(item) => item.value} 82 | // New props 83 | headerHeight={100} // Default 0, need to specify the header height 84 | footerHeight={100} // Default 0, need to specify the foorer height 85 | />; 86 | ``` 87 | 88 | ## Optional Next steps 89 | 90 | :::info 91 | 92 | These steps are recommended but, if you want turn back to FlatList in anytime, you can keep only the first steps without any problems. 93 | 94 | ::: 95 | 96 | :::note 97 | 98 | To have more details about props check the [Props list](props.md) 99 | 100 | ::: 101 | 102 | #### Replacing 103 | 104 | - Replace `ListHeaderComponent` with `renderHeader` 105 | - Replace `ListFooterComponent` with `renderFooter` 106 | - Replace `ListEmptyComponent` with `renderEmpty` 107 | - Replace `getItemLayout` with `itemHeight` 108 | 109 | #### Removing 110 | 111 | - Remove `ListFooterComponentStyle` 112 | - Remove `ListHeaderComponentStyle` 113 | 114 | ### Final result 115 | 116 | ```js 117 | import BigList from "react-native-big-list"; 118 | 119 | const ITEM_HEIGHT = 50; 120 | 121 | /* ... */ 122 | 123 | item.value} 127 | renderItem={renderItem} 128 | renderHeader={renderHeader} 129 | renderFooter={renderFooter} 130 | renderEmpty={renderEmpty} 131 | itemHeight={ITEM_HEIGHT} 132 | headerHeight={100} 133 | footerHeight={100} 134 | />; 135 | ``` 136 | -------------------------------------------------------------------------------- /docs/docs/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Getting Started 6 | 7 | ## Install 8 | 9 | Install the library from npm or yarn just running one of the following command lines: 10 | 11 | | npm | yarn | 12 | | ------------------------------------------ | -------------------------------- | 13 | | `npm install react-native-big-list --save` | `yarn add react-native-big-list` | 14 | 15 | ## Usage 16 | 17 | :::info 18 | 19 | You come from the FlatList? Read also "_Migrate from FlatList_" on extras section. 20 | 21 | ::: 22 | 23 | ** Simple usage: ** 24 | 25 | ```javascript 26 | import BigList from "react-native-big-list"; 27 | // ... 28 | const MyExample = ({ data }) => { 29 | const renderItem = ({ item, index }) => ; 30 | return ; 31 | }; 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/docs/how-contribute.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Have an idea? Found a bug? Please raise to [ISSUES](https://github.com/marcocesarato/react-native-big-list/issues). 4 | Contributions are welcome and are greatly appreciated! Every little bit helps, and credit will always be given. 5 | -------------------------------------------------------------------------------- /docs/docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | slug: / 4 | --- 5 | 6 | # Intro 7 | 8 | ## React Native Big List 9 | 10 | This is a high performance list view for React Native with support for complex layouts using a similar FlatList usage to make easy the replacement. 11 | This list implementation for big list rendering on React Native works with a recycler focused on performance and memory usage and so it permits processing thousands items on the list. 12 | 13 | :::info 14 | 15 | This library is fully JS native, so it's compatible with all available platforms: _Android, iOS, Windows, MacOS, Web and Expo_. 16 | 17 | ::: 18 | 19 | ## Why another list library? 20 | 21 | React Native's FlatList is great but when it comes to big lists it has some flaws because of its item caching. 22 | Exists some alternatives like `react-native-largelist` and `recyclerlistview` but both have some issues. 23 | 24 | The `react-native-largelist` isn't compatible with web and Expo, has native code that sometimes need to be readjusted and maintained, have a weird list item recycles (because it never has blank items), need data restructure and have some issues when trying to process a lot of data (eg: 100,000 items) because it would freeze the CPU. 25 | 26 | The `recyclerlistview` is performant but suffers from an empty frame on mount, weird scroll positions when trying to scroll to an element on mount, and the implementation of sticky headers conflicts with `Animated`. 27 | 28 | ## How it works? 29 | 30 | Recycler makes it easy to efficiently display large sets of data. You supply the data and define how each item looks, and the recycler library dynamically creates the elements when they're needed. 31 | As the name implies, the recycler recycles those individual elements. When an item scrolls off the screen, the recycler doesn't destroy its view. Instead, the recycler reuses the view for new items that have scrolled onscreen. This reuse vastly improves performance, improving your app's responsiveness and reducing power consumption. 32 | 33 | When list can't render your items fast enough the non-rendered components will appear as blank space. 34 | -------------------------------------------------------------------------------- /docs/docs/methods.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # Methods 6 | 7 | ### `scrollTo()` 8 | 9 | ```ts 10 | scrollTo({ x: number, y: number, animated: boolean }); 11 | ``` 12 | 13 | Scrolls to a given x, y offset, either immediately, with a smooth animation. 14 | 15 | **Example:** 16 | 17 | ```js 18 | scrollTo({ x: 0, y: 0, animated: true }); 19 | ``` 20 | 21 | ### `scrollToTop()` 22 | 23 | ```js 24 | scrollToTop({ animated = true }) 25 | ``` 26 | 27 | Scrolls to the top of the content. 28 | 29 | ### `scrollToEnd()` 30 | 31 | ```js 32 | scrollToEnd({ animated = true }) 33 | ``` 34 | 35 | Scrolls to the end of the content. 36 | 37 | ### `scrollToIndex()` 38 | 39 | ```js 40 | scrollToIndex({ index, section = 0, animated = true }) 41 | ``` 42 | 43 | Scrolls to the item at the specified index such that it is positioned. 44 | 45 | ### `scrollToItem()` 46 | 47 | ```js 48 | scrollToItem({ item, animated = false }) 49 | ``` 50 | 51 | Requires linear scan through data - use scrollToIndex instead if possible. 52 | 53 | ### `scrollToOffset()` 54 | 55 | ```js 56 | scrollToOffset({ offset, animated = false }) 57 | ``` 58 | 59 | Scroll to a specific content pixel offset in the list vertically. 60 | 61 | ### `scrollToLocation()` 62 | 63 | ```js 64 | scrollToLocation({ 65 | section: number, 66 | index: number, 67 | animated = true 68 | }) 69 | ``` 70 | 71 | Scrolls to the item at the specified sectionIndex and itemIndex (within the section). 72 | 73 | ### `scrollToSection()` 74 | 75 | ```js 76 | scrollToSection({ section?: number, animated = true }) 77 | ``` 78 | 79 | Scrolls to the top of the section. 80 | 81 | ### `flashScrollIndicators()` 82 | 83 | Displays the scroll indicators momentarily. 84 | 85 | ### `getNativeScrollRef()` 86 | 87 | Provides a reference to the underlying scroll component. 88 | 89 | ### `getItemOffset()` 90 | 91 | ```js 92 | getItemOffset({index: number, section?: number}) 93 | ``` 94 | 95 | Provides the scroll vertical offset of a list item giving its section and row. 96 | 97 | ### `getItem()` 98 | 99 | ```js 100 | getItem({index: number, section?: number}) 101 | ``` 102 | 103 | Provides a list item giving its section and row. 104 | 105 | ### `getItems()` 106 | 107 | Provides an `array` with all the items of the list. 108 | 109 | ### `isVisible()` 110 | 111 | ```js 112 | isVisible({ index, section = 0 }) 113 | ``` 114 | 115 | Provides a `boolean` giving its section and row and return if the item is visible or not on the list, useful for tests. 116 | 117 | ### `isEmpty()` 118 | 119 | Provides a `boolean` returning if the state of the list is empty, useful for tests. 120 | -------------------------------------------------------------------------------- /docs/docs/props.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Props 6 | 7 | ## [ScrollView Props](https://reactnative.dev/docs/scrollview#props) 8 | 9 | Inherits ScrollView Props. 10 | 11 | --- 12 | 13 | ## Standard Props 14 | 15 | ### Required `data` 16 | 17 | For simplicity, `data` is a plain array containing the items to render. 18 | 19 | | Type | Required | 20 | | ----- | ------------------------------------ | 21 | | array | Yes, if `sections` are not specified | 22 | 23 | #### Examples 24 | 25 | ```js 26 | [1, 2, 3, 4, 5, 6 /* ... */]; 27 | ``` 28 | 29 | ```js 30 | [ 31 | { label: "1", value: 1 /* ... */ }, 32 | { label: "2", value: 2 /* ... */ }, 33 | /* ... */ 34 | ]; 35 | ``` 36 | 37 | ### Required `renderItem` 38 | 39 | ```ts 40 | renderItem({ item: unknown, index: number, section: number }); 41 | ``` 42 | 43 | Takes an item from data and renders it into the list. 44 | 45 | Using `data` arguments will be an object with the `item` and its `index`. 46 | 47 | Using `sections` arguments will be an object with the `item` and its `section` index and row `index`. 48 | 49 | :::note 50 | You need also to specify the height using [**itemHeight**](#required-itemheight). 51 | ::: 52 | 53 | | Type | Required | 54 | | -------- | -------- | 55 | | function | Yes | 56 | 57 | ### `controlItemRender` 58 | 59 | Removes the wrapping view component that is added by BigList. 60 | 61 | Using `controlItemRender` will add more arguments to `renderItem`. 62 | 63 | ```ts 64 | renderItem({ 65 | item: unknown, 66 | index: number, 67 | section: number, 68 | key: string, 69 | style: object, 70 | }); 71 | ``` 72 | 73 | :::note 74 | You will need to apply `style` to your rendered `View` for this to work properly. 75 | ::: 76 | 77 | | Type | Required | Default | 78 | | ------- | -------- | ------- | 79 | | boolean | No | `false` | 80 | 81 | ### Required `itemHeight` 82 | 83 | Specify the item height. 84 | 85 | This is needed to have a great performance boost for lists of several thousands items. 86 | 87 | **Function example:** 88 | 89 | ```ts 90 | itemHeight(section: number, index: number) 91 | ``` 92 | 93 | | Type | Required | Default | 94 | | ---------------- | -------- | ------- | 95 | | number, function | Yes | `50` | 96 | 97 | ### Required `sections` 98 | 99 | For simplicity, `sections` is a plain array containing another plain array with the items (section items) to render. 100 | 101 | :::caution 102 | Specifying this prop you'll overwrite _data_ prop and so it'll be ignored. 103 | It's required if no _data_ is specified or if you want to use sticky headers/sections separators (look also at [**renderSectionHeader**](#rendersectionheader) and [**renderSectionFooter**](#rendersectionfooter)). 104 | ::: 105 | 106 | | Type | Required | 107 | | ----- | -------------------------------------------- | 108 | | array | Yes, if you want to use it instead of `data` | 109 | 110 | #### Examples 111 | 112 | ```js 113 | [ 114 | // Section 1 115 | [1, 2], 116 | // Section 2 117 | [3, 4], 118 | /* ... */ 119 | ]; 120 | ``` 121 | 122 | ```js 123 | [ 124 | [ 125 | // Section 1 126 | { label: "1", value: 1 /* ... */ }, 127 | { label: "2", value: 2 /* ... */ }, 128 | ], 129 | [ 130 | // Section 2 131 | { label: "3", value: 3 /* ... */ }, 132 | { label: "4", value: 4 /* ... */ }, 133 | ], 134 | /* ... */ 135 | ]; 136 | ``` 137 | 138 | ### `keyExtractor` 139 | 140 | ```js 141 | (item: object, index: number) => string; 142 | ``` 143 | 144 | Used to extract a unique key for a given item at the specified index. Key is used for caching and as the react key to track item re-ordering. The default extractor checks item.key, then item.id, and then falls back to using the index, like React does. 145 | 146 | | Type | 147 | | -------- | 148 | | function | 149 | 150 | ### `renderEmpty` 151 | 152 | Rendered when the list is empty. 153 | 154 | | Type | Required | 155 | | -------- | -------- | 156 | | function | No | 157 | 158 | ### `renderHeader` 159 | 160 | Rendered at the top of all the items. 161 | 162 | :::note 163 | You need also to specify the height using [**headerHeight**](#headerheight). 164 | ::: 165 | 166 | | Type | Required | 167 | | -------- | -------- | 168 | | function | No | 169 | 170 | ### `renderFooter` 171 | 172 | Rendered at the bottom of all the items. 173 | 174 | :::note 175 | You need also to specify the height using [**footerHeight**](#footerheight). 176 | ::: 177 | 178 | | Type | Required | 179 | | -------- | -------- | 180 | | function | No | 181 | 182 | ### `renderSectionHeader` 183 | 184 | ```ts 185 | renderSectionHeader(section: number) 186 | ``` 187 | 188 | Rendered at the top of all the section's items. 189 | 190 | :::note 191 | You need also to specify the height using [**sectionHeaderHeight**](#sectionheaderheight). 192 | ::: 193 | 194 | | Type | Required | 195 | | -------- | -------- | 196 | | function | No | 197 | 198 | ### `renderSectionFooter` 199 | 200 | ```ts 201 | renderSectionFooter(section: number) 202 | ``` 203 | 204 | Rendered at the bottom of all the section's items. 205 | 206 | :::note 207 | You need also to specify the height using [**sectionFooterHeight**](#sectionfooterheight). 208 | ::: 209 | 210 | | Type | Required | 211 | | -------- | -------- | 212 | | function | No | 213 | 214 | ### `renderScrollViewWrapper` 215 | 216 | Wrap the entire list into an accessory component. 217 | 218 | | Type | Required | 219 | | -------- | -------- | 220 | | function | No | 221 | 222 | ### `ScrollViewComponent` 223 | 224 | Custom component to use instead of react-native's `ScrollView` (e.g. `ScrollView` from `react-native-gesture-handler`) 225 | 226 | | Type | Required | Default | 227 | | --------- | -------- | --------------------------- | 228 | | component | No | react-native's `ScrollView` | 229 | 230 | ### `renderAccessory` 231 | 232 | ```ts 233 | renderAccessory(list: BigList) 234 | ``` 235 | 236 | Rendered accessory at the bottom of the list. 237 | 238 | | Type | Required | 239 | | -------- | -------- | 240 | | function | No | 241 | 242 | ### `headerHeight` 243 | 244 | Specify the header item height. 245 | 246 | | Type | Required | Default | 247 | | ---------------- | -------- | ------- | 248 | | number, function | No | `0` | 249 | 250 | ### `footerHeight` 251 | 252 | Specify the footer item height. 253 | 254 | | Type | Required | Default | 255 | | ---------------- | -------- | ------- | 256 | | number, function | No | `0` | 257 | 258 | ### `sectionHeaderHeight` 259 | 260 | Specify the section header height. 261 | 262 | **Function example:** 263 | 264 | ```ts 265 | sectionHeaderHeight(section: number) 266 | ``` 267 | 268 | | Type | Required | Default | 269 | | ---------------- | -------- | ------- | 270 | | number, function | No | `0` | 271 | 272 | ### `sectionFooterHeight` 273 | 274 | Specify the section footer height. 275 | 276 | **Function example:** 277 | 278 | ```ts 279 | sectionFooterHeight(section: number) 280 | ``` 281 | 282 | | Type | Required | Default | 283 | | ---------------- | -------- | ------- | 284 | | number, function | No | `0` | 285 | 286 | ### `placeholder` 287 | 288 | Enable placeholder on fast scrolling. Disabled as default on web. 289 | 290 | When list can't render your items fast enough the non-rendered components will appear as blank space. 291 | The placeholder so let you fill the blank spaces that usually can be seen scrolling fast. 292 | The list has two main blank spaces. 293 | The first space starts from the top of the scroll view area end to the first loaded item. 294 | The second space starts from the last loaded item to the end of the scroll area. 295 | 296 | This setting permit you to replace or add a repeated background to this two blank spaces and so to make the illusion of a shimmer scrolling fast. 297 | 298 | | Type | Required | Default | 299 | | ------- | -------- | ------- | 300 | | boolean | No | `false` | 301 | 302 | ### `placeholderImage` 303 | 304 | Placeholder background repeated on fast scrolling. 305 | 306 | :::note 307 | 308 | Be careful to different screens resolutions, this solution could be no responsive if you use a single image per screen resolution. 309 | 310 | ::: 311 | 312 | | Type | Required | 313 | | ------------------------------------------------------------- | -------- | 314 | | [ImageSource](https://reactnative.dev/docs/image#imagesource) | No | 315 | 316 | ### `placeholderComponent` 317 | 318 | Placeholder component on fast scrolling. This item could be an SVG pattern or whatever you want to fill the blank space on fast scrolling. 319 | 320 | :::note 321 | 322 | This will replace the **placeholderImage** and need **placeholder** equal to _true_. 323 | 324 | ::: 325 | 326 | | Type | Required | 327 | | ------------------ | -------- | 328 | | component, element | No | 329 | 330 | ### `stickySectionHeadersEnabled` 331 | 332 | Makes section headers stick to the top of the screen until the next one pushes it off. 333 | 334 | | Type | Required | Default | 335 | | ------- | -------- | ------- | 336 | | boolean | No | `true` | 337 | 338 | ### `numColumns` 339 | 340 | Format the list in column format by placing the elements side by side with x elements per x number of columns per row. 341 | 342 | | Type | Required | Default | 343 | | ------ | -------- | ------- | 344 | | number | No | `1` | 345 | 346 | ### `inverted` 347 | 348 | Reverses the direction of scroll. Uses scale transforms of `-1`. 349 | 350 | | Type | Required | Default | 351 | | ------- | -------- | ------- | 352 | | boolean | No | `false` | 353 | 354 | ### `initialScrollIndex` 355 | 356 | Specify the initial scroll from the top of the list. 357 | 358 | | Type | Required | Default | 359 | | ------ | -------- | ------- | 360 | | number | No | `0` | 361 | 362 | ### `insetTop` 363 | 364 | Specify the top inset. 365 | 366 | | Type | Required | Default | 367 | | ------ | -------- | ------- | 368 | | number | No | `0` | 369 | 370 | ### `insetBottom` 371 | 372 | Specify the bottom inset. 373 | 374 | | Type | Required | Default | 375 | | ------ | -------- | ------- | 376 | | number | No | `0` | 377 | 378 | ### `contentInset` 379 | 380 | The amount by which the scroll view content is inset from the edges of the scroll view. 381 | 382 | | Type | Required | Default | 383 | | ------------------------------------------------------------------ | -------- | ------------------------------------------ | 384 | | object: {top: number, left: number, bottom: number, right: number} | No | `{ top: 0, right: 0, left: 0, bottom: 0 }` | 385 | 386 | ### `columnWrapperStyle` 387 | 388 | Optional custom style for multi-item rows generated when [`numColumns`](#numcolumns) > 1. 389 | 390 | | Type | Required | 391 | | ----------------------------------------------------------- | -------- | 392 | | [View Style](https://reactnative.dev/docs/view-style-props) | No | 393 | 394 | ### `refreshing` 395 | 396 | Set this true while waiting for new data from a refresh. 397 | 398 | | Type | Required | Default | 399 | | ------- | -------- | ------- | 400 | | boolean | No | `false` | 401 | 402 | ### `onViewableItemsChanged` 403 | 404 | Called when the viewability of rows changes. 405 | 406 | | Type | Required | 407 | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | 408 | | (callback: { changed: array of [ViewTokens](https://reactnative.dev/docs/viewtoken), viewableItems: array of [ViewTokens](https://reactnative.dev/docs/viewtoken) }) => void | No | 409 | 410 | ### `onRefresh` 411 | 412 | If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the `refreshing` prop correctly. 413 | 414 | | Type | Required | 415 | | -------- | -------- | 416 | | function | No | 417 | 418 | ### `onEndReached` 419 | 420 | ```ts 421 | onEndReached(info: {distanceFromEnd: number}) 422 | ``` 423 | 424 | | Type | Required | 425 | | -------- | -------- | 426 | | function | No | 427 | 428 | Called once when the scroll position gets within `onEndReachedThreshold` of the rendered content. 429 | 430 | ### `onEndReachedThreshold` 431 | 432 | How far from the end (in units of visible length of the list) the bottom edge of the list must be from the end of the content to trigger the `onEndReached` callback. Thus a value of `0.5` will trigger `onEndReached` when the end of the content is within half the visible length of the list. 433 | 434 | | Type | Required | Default | 435 | | ------ | -------- | ------- | 436 | | number | No | `0` | 437 | 438 | ### `batchSizeThreshold` 439 | 440 | :::info 441 | **A lower value of this prop improve performance and memory usage.**
442 | Minimum value limited to _0.5_ to display all elements on the visible list with no gaps. 443 | ::: 444 | 445 | How much threshold must be applied to the batch size to render the elements before rendering the other batch of elements. Thus a value of `0.5` will make the batch size equal to half the visible length of the list (above and below from the current position of the batch). 446 | 447 | | Type | Required | Default | 448 | | ------ | -------- | ------- | 449 | | number | No | `1` | 450 | 451 | ### `hideMarginalsOnEmpty` 452 | 453 | Hide header and footer on empty list. 454 | 455 | | Type | Required | Default | 456 | | ------- | -------- | ------- | 457 | | boolean | No | `false` | 458 | 459 | ### `hideHeaderOnEmpty` 460 | 461 | Hide header on empty list. 462 | 463 | | Type | Required | Default | 464 | | ------- | -------- | ------- | 465 | | boolean | No | `false` | 466 | 467 | ### `hideFooterOnEmpty` 468 | 469 | Hide footer on empty list. 470 | 471 | | Type | Required | Default | 472 | | ------- | -------- | ------- | 473 | | boolean | No | `false` | 474 | 475 | ### `nativeOffsetValues` 476 | 477 | Cast the content offset to animated values using the native driver on scroll. 478 | 479 | ```ts 480 | nativeOffsetValues={{x?: Animated.Value, y?: Animated.Value}} 481 | ``` 482 | 483 | | Type | Required | Default | 484 | | ------------------------------------------------ | -------- | ----------- | 485 | | object: { x: Animated.Value, y: Animated.Value } | No | `undefined` | 486 | 487 | ## FlatList Props 488 | 489 | :::caution Compatibility 490 | 491 | These are **compatibility** props for a faster FlatList replacement and all these props have an alias. 492 | These props will **replace** their related alias standard props. 493 | All of them should be replaced with their related props as best practice. 494 | 495 | ::: 496 | 497 | ### `getItemLayout` 498 | 499 | ```ts 500 | (data, index) => {length: number, offset: number, index: number} 501 | ``` 502 | 503 | `getItemLayout` is an optional optimization that allows skipping the measurement of dynamic content if you know the size (height or width) of items ahead of time. getItemLayout is efficient if you have fixed size items, for example: 504 | 505 | ```ts 506 | getItemLayout={(data, index) => ( 507 | {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index} 508 | )} 509 | ``` 510 | 511 | Adding getItemLayout can be a great performance boost for lists of several thousands items. 512 | 513 | :::note Compatibility 514 | 515 | This is a **compatibility** prop for FlatList replacement and so it'll replace the [**itemHeight**](#required-itemheight) prop. 516 | 517 | ::: 518 | 519 | | Type | Required | 520 | | -------- | -------- | 521 | | function | No | 522 | 523 | ### `ListEmptyComponent` 524 | 525 | Rendered when the list is empty. Can be a React Component (e.g. `SomeComponent`), or a React element (e.g. ``). 526 | 527 | :::note Compatibility 528 | 529 | This is a **compatibility** prop for FlatList replacement and so it'll replace the [**renderEmpty**](#renderempty) prop. 530 | 531 | ::: 532 | 533 | | Type | Required | 534 | | ------------------ | -------- | 535 | | component, element | No | 536 | 537 | ### `ListFooterComponent` 538 | 539 | Rendered at the bottom of all the items. Can be a React Component (e.g. `SomeComponent`), or a React element (e.g. ``). 540 | 541 | :::note 542 | You need also to specify the footer height with [**footerHeight**](#footerheight) prop or using [**ListFooterComponentStyle**](#listfootercomponentstyle). 543 | ::: 544 | 545 | :::note Compatibility 546 | 547 | This is a **compatibility** prop for FlatList replacement and so it'll replace the [**renderFooter**](#renderfooter) prop. 548 | If you are creating for the first time the list we suggest to use [**renderFooter**](#renderfooter) prop instead. 549 | 550 | ::: 551 | 552 | | Type | Required | 553 | | ------------------ | -------- | 554 | | component, element | No | 555 | 556 | ### `ListFooterComponentStyle` 557 | 558 | :::info 559 | 560 | This will works only if [**ListFooterComponent**](#listfootercomponent) is specified. 561 | 562 | ::: 563 | 564 | Styling for internal View for [**ListFooterComponent**](#listfootercomponent). 565 | 566 | | Type | Required | 567 | | ----------------------------------------------------------- | -------- | 568 | | [View Style](https://reactnative.dev/docs/view-style-props) | No | 569 | 570 | ### `ListHeaderComponent` 571 | 572 | Rendered at the top of all the items. Can be a React Component (e.g. `SomeComponent`), or a React element (e.g. ``). 573 | 574 | :::note 575 | You need also to specify the header height with [**headerHeight**](#headerheight) prop or using [**ListHeaderComponentStyle**](#listfootercomponentstyle). 576 | ::: 577 | 578 | :::note Compatibility 579 | 580 | This is a **compatibility** prop for FlatList replacement and so it'll replace the [**renderHeader**](#renderheader) prop. 581 | If you are creating for the first time the list we suggest to use [**renderHeader**](#renderheader) prop instead. 582 | 583 | ::: 584 | 585 | | Type | Required | 586 | | ------------------ | -------- | 587 | | component, element | No | 588 | 589 | ### `ListHeaderComponentStyle` 590 | 591 | :::info 592 | 593 | This will works only if [**ListHeaderComponent**](#listfootercomponent) is specified. 594 | 595 | ::: 596 | 597 | Styling for internal View for [**ListHeaderComponent**](#listfootercomponent). 598 | 599 | | Type | Required | 600 | | ----------------------------------------------------------- | -------- | 601 | | [View Style](https://reactnative.dev/docs/view-style-props) | No | 602 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@docusaurus/types').DocusaurusConfig} */ 2 | module.exports = { 3 | title: "React Native Big List", 4 | tagline: "Dinosaurs are cool", 5 | url: "https://marcocesarato.github.io/react-native-big-list-docs/", 6 | baseUrl: "/react-native-big-list-docs/", 7 | onBrokenLinks: "throw", 8 | onBrokenMarkdownLinks: "warn", 9 | favicon: "img/favicon.ico", 10 | organizationName: "marcocesarato", // Usually your GitHub org/user name. 11 | projectName: "react-native-big-list-docs", // Usually your repo name. 12 | themeConfig: { 13 | navbar: { 14 | title: "React Native Big List", 15 | logo: { 16 | alt: "React Native Big List Logo", 17 | src: "img/logo.svg", 18 | }, 19 | items: [ 20 | { 21 | type: "doc", 22 | docId: "intro", 23 | position: "left", 24 | label: "Documentation", 25 | }, 26 | { 27 | href: "https://marcocesarato.github.io/react-native-big-list/", 28 | label: "Demo", 29 | position: "left", 30 | }, 31 | { 32 | href: "https://github.com/marcocesarato/react-native-big-list", 33 | label: "GitHub", 34 | position: "right", 35 | }, 36 | ], 37 | }, 38 | footer: { 39 | style: "dark", 40 | links: [ 41 | { 42 | title: "Links", 43 | items: [ 44 | { 45 | label: "Open an issue", 46 | href: "https://github.com/marcocesarato/react-native-big-list/issues", 47 | }, 48 | { 49 | label: "Stack Overflow", 50 | href: "https://stackoverflow.com/questions/tagged/react-native-big-list", 51 | }, 52 | ], 53 | }, 54 | { 55 | title: "More", 56 | items: [ 57 | { 58 | label: "Demo", 59 | href: "https://marcocesarato.github.io/react-native-big-list/", 60 | }, 61 | { 62 | label: "GitHub", 63 | href: "https://github.com/marcocesarato/react-native-big-list", 64 | }, 65 | ], 66 | }, 67 | ], 68 | }, 69 | }, 70 | presets: [ 71 | [ 72 | "@docusaurus/preset-classic", 73 | { 74 | docs: { 75 | routeBasePath: "/", 76 | sidebarPath: require.resolve("./sidebars.js"), 77 | editUrl: 78 | "https://github.com/marcocesarato/react-native-big-list/edit/master/docs/", 79 | }, 80 | theme: { 81 | customCss: require.resolve("./src/css/custom.css"), 82 | }, 83 | }, 84 | ], 85 | ], 86 | plugins: [ 87 | // ... Your other plugins. 88 | [ 89 | require.resolve("@easyops-cn/docusaurus-search-local"), 90 | { 91 | // ... Your options. 92 | // `hashed` is recommended as long-term-cache of index file is possible. 93 | hashed: true, 94 | indexPages: true, 95 | indexDocs: true, 96 | // For Docs using Chinese, The `language` is recommended to set to: 97 | // ``` 98 | // language: ["en", "zh"], 99 | // ``` 100 | // When applying `zh` in language, please install `nodejieba` in your project. 101 | }, 102 | ], 103 | ], 104 | }; 105 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "GIT_USER=marcocesarato docusaurus deploy", 11 | "deploy-win": "cmd /C \"set \"GIT_USER=marcocesarato\" && docusaurus deploy\"", 12 | "clear": "docusaurus clear", 13 | "serve": "docusaurus serve", 14 | "write-translations": "docusaurus write-translations", 15 | "write-heading-ids": "docusaurus write-heading-ids" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "^2.0.0-beta.14", 19 | "@docusaurus/preset-classic": "^2.0.0-beta.14", 20 | "@easyops-cn/docusaurus-search-local": "^0.18.1", 21 | "@mdx-js/react": "^1.6.22", 22 | "@svgr/webpack": "^5.5.0", 23 | "clsx": "^1.1.1", 24 | "file-loader": "^6.2.0", 25 | "react": "^17.0.2", 26 | "react-dom": "^17.0.2", 27 | "url-loader": "^4.1.1" 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.5%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | module.exports = { 13 | tutorialSidebar: [{ type: "autogenerated", dirName: "." }], 14 | }; 15 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #6267ff; 11 | } 12 | 13 | html[data-theme='dark'] { 14 | --ifm-color-primary: #6267ff; 15 | --ifm-footer-background-color: #242526; 16 | --ifm-navbar-search-input-background-color: #DDD; 17 | --ifm-navbar-search-input-color: #242526; 18 | } 19 | 20 | html .navbar__brand img { 21 | margin-right: 15px; 22 | margin-left: 5px; 23 | } 24 | 25 | .docusaurus-highlight-code-line { 26 | background-color: rgb(72, 77, 91); 27 | display: block; 28 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 29 | padding: 0 var(--ifm-pre-padding); 30 | } 31 | 32 | .navbar__search-input { 33 | outline: none; 34 | } 35 | .navbar__search-input::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ 36 | color: #888; 37 | opacity: 1; /* Firefox */ 38 | } 39 | .navbar__search-input:-ms-input-placeholder { /* Internet Explorer 10-11 */ 40 | color: #888; 41 | } 42 | .navbar__search-input::-ms-input-placeholder { /* Microsoft Edge */ 43 | color: #888; 44 | } 45 | 46 | .required, .optional { 47 | margin-left: 5px; 48 | border-radius: 3px; 49 | border-style: solid; 50 | border-width: 2px; 51 | line-height: 20px; 52 | font-size: 65%; 53 | font-weight: 500; 54 | padding: 2px 1em; 55 | } 56 | 57 | .required { 58 | border-color: #fa5035; 59 | color: #fa5035; 60 | } 61 | .optional { 62 | border-color: #54c7ec; 63 | color: #54c7ec; 64 | } 65 | 66 | .table-of-contents .required, .table-of-contents .optional { 67 | color: transparent; 68 | border-radius: 100%; 69 | height: 6px; 70 | width: 6px; 71 | border-width: 3px; 72 | margin: 0; 73 | margin-right: 6px; 74 | overflow: hidden; 75 | padding: 0; 76 | white-space: nowrap; 77 | font-size: 0; 78 | line-height: 0; 79 | vertical-align: middle; 80 | } -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/.expo-shared/README.md: -------------------------------------------------------------------------------- 1 | > Why do I have a folder named ".expo-shared" in my project? 2 | 3 | The ".expo-shared" folder is created when running commands that produce state that is intended to be shared with all developers on the project. For example, "npx expo-optimize". 4 | 5 | > What does the "assets.json" file contain? 6 | 7 | The "assets.json" file describes the assets that have been optimized through "expo-optimize" and do not need to be processed again. 8 | 9 | > Should I commit the ".expo-shared" folder? 10 | 11 | Yes, you should share the ".expo-shared" folder with your collaborators. 12 | -------------------------------------------------------------------------------- /example/.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "5f4c0a732b6325bf4071d9124d2ae67e037cb24fcc9c482ef82bea742109a3b8": true, 3 | "24272cdaeff82cc5facdaccd982a6f05b60c4504704bbf94c19a6388659880bb": true, 4 | "74c64047eb557b1341bba7a2831eedde9ddb705e6451a9ad9f5552bf558f13de": true, 5 | "052227dc810848b3a2fc99161a392256ea9ef37da10b69b6630ccf6dbb5e2ca6": true 6 | } 7 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | 12 | # macOS 13 | .DS_Store 14 | 15 | lib -------------------------------------------------------------------------------- /example/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Provider as PaperProvider } from "react-native-paper"; 3 | import { SafeAreaProvider } from "react-native-safe-area-context"; 4 | 5 | import Home from "./src/Home"; 6 | 7 | export default function App() { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "BigListExample", 4 | "slug": "BigListexample", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": ["**/*"], 17 | "ios": { 18 | "supportsTablet": true 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#FFFFFF" 24 | } 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | }, 29 | "description": "" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/example/assets/adaptive-icon.png -------------------------------------------------------------------------------- /example/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/example/assets/favicon.ico -------------------------------------------------------------------------------- /example/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/example/assets/favicon.png -------------------------------------------------------------------------------- /example/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/example/assets/icon.png -------------------------------------------------------------------------------- /example/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/example/assets/splash.png -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | env: { 6 | production: { 7 | plugins: ["react-native-paper/babel"], 8 | }, 9 | }, 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "homepage": "https://marcocesarato.github.io/react-native-big-list/", 4 | "scripts": { 5 | "deploy": "gh-pages -d web-build", 6 | "predeploy": "expo build:web", 7 | "start": "expo start", 8 | "android": "expo start --android", 9 | "ios": "expo start --ios", 10 | "web": "expo start --web", 11 | "eject": "expo eject" 12 | }, 13 | "dependencies": { 14 | "expo": "^44.0.0", 15 | "expo-status-bar": "~1.2.0", 16 | "faker": "^5.5.3", 17 | "react": "17.0.1", 18 | "react-dom": "17.0.1", 19 | "react-native": "0.64.3", 20 | "react-native-big-list": "^1.6.1", 21 | "react-native-paper": "^4.9.1", 22 | "react-native-safe-area-context": "3.3.2", 23 | "react-native-web": "0.17.1" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.12.9", 27 | "gh-pages": "^3.2.0" 28 | }, 29 | "private": true 30 | } 31 | -------------------------------------------------------------------------------- /example/src/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Platform, StyleSheet, TouchableOpacity, View } from "react-native"; 3 | import { Appbar, TextInput, useTheme } from "react-native-paper"; 4 | import { useSafeAreaInsets } from "react-native-safe-area-context"; 5 | 6 | import ColumnsList from "./lists/ColumnsList"; 7 | import CompareList from "./lists/CompareList"; 8 | import List from "./lists/List"; 9 | import MultiSelectList from "./lists/MultiSelectList"; 10 | import SectionList from "./lists/SectionList"; 11 | import SelectList from "./lists/SelectList"; 12 | 13 | const Home = () => { 14 | const { 15 | colors: { background, surface }, 16 | } = useTheme(); 17 | const [openSelector, setOpenSelector] = useState(false); 18 | const [selected, setSelected] = useState("standard"); 19 | const [insetBottom, setInsetBottom] = useState(0); 20 | const insets = useSafeAreaInsets(); 21 | const options = [ 22 | { label: "Standard List", value: "standard" }, 23 | { label: "Columns List", value: "columns" }, 24 | { label: "Sections List", value: "sections" }, 25 | { label: "Multiselect List", value: "multiselect" }, 26 | { label: "Compare List", value: "compare" }, 27 | ]; 28 | const selectedOption = options.find((item) => item.value === selected); 29 | return ( 30 | 39 | 40 | 41 | 42 | setOpenSelector(!openSelector)} 48 | onLayout={(event) => { 49 | setInsetBottom(event.height || 0); 50 | }} 51 | > 52 | setOpenSelector(true)} 56 | value={selectedOption.label} 57 | right={ 58 | setOpenSelector(!openSelector)} 61 | /> 62 | } 63 | /> 64 | 65 | {selected === "standard" ? ( 66 | 67 | ) : selected === "columns" ? ( 68 | 69 | ) : selected === "sections" ? ( 70 | 71 | ) : selected === "multiselect" ? ( 72 | 73 | ) : selected === "compare" ? ( 74 | 75 | ) : null} 76 | 77 | {openSelector && ( 78 | 84 | 85 | 89 | 90 | { 94 | setSelected(value); 95 | setOpenSelector(false); 96 | }} 97 | /> 98 | 99 | )} 100 | 101 | ); 102 | }; 103 | 104 | const styles = StyleSheet.create({ 105 | container: { 106 | flex: 1, 107 | position: "relative", 108 | ...Platform.select({ web: { maxHeight: "100vh" }, default: {} }), 109 | }, 110 | containerBottom: { 111 | bottom: 0, 112 | elevation: 999, 113 | left: 0, 114 | position: "absolute", 115 | width: "100%", 116 | zIndex: 999, 117 | }, 118 | header: { 119 | elevation: 0, 120 | marginBottom: Platform.select({ web: 0, default: -5 }), 121 | }, 122 | }); 123 | 124 | export default Home; 125 | -------------------------------------------------------------------------------- /example/src/lists/ColumnsList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | KeyboardAvoidingView, 4 | SafeAreaView, 5 | StyleSheet, 6 | View, 7 | } from "react-native"; 8 | import BigList from "react-native-big-list"; 9 | import { List, Subheading, TextInput } from "react-native-paper"; 10 | import { StatusBar } from "expo-status-bar"; 11 | 12 | import data from "../data/data.json"; 13 | import Block from "./components/Block"; 14 | 15 | export default function SectionList() { 16 | const [numberColumns, setNumberColumns] = useState(3); 17 | const renderItem = ({ item }) => { 18 | return ( 19 | } 24 | /> 25 | ); 26 | }; 27 | const renderEmpty = () => ; 28 | const renderHeader = () => ( 29 | 30 | { 36 | const num = parseInt(value, 10) || ""; 37 | setNumberColumns(num); 38 | }} 39 | /> 40 | 41 | ); 42 | const renderFooter = () => ( 43 | 44 | No more items available... 45 | 46 | ); 47 | return ( 48 | 49 | 50 | 51 | 69 | 70 | 71 | 72 | 73 | ); 74 | } 75 | 76 | const styles = StyleSheet.create({ 77 | compare: { 78 | flex: 1, 79 | flexDirection: "row", 80 | }, 81 | container: { 82 | flex: 1, 83 | }, 84 | }); 85 | -------------------------------------------------------------------------------- /example/src/lists/CompareList.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-shadow */ 2 | import React from "react"; 3 | import { 4 | FlatList, 5 | KeyboardAvoidingView, 6 | Platform, 7 | SafeAreaView, 8 | StyleSheet, 9 | View, 10 | } from "react-native"; 11 | import { List, Subheading } from "react-native-paper"; 12 | import { StatusBar } from "expo-status-bar"; 13 | 14 | import BigList from "react-native-big-list"; 15 | import data from "../data/data.json"; 16 | import Block from "./components/Block"; 17 | 18 | const ITEM_HEIGHT = 50; 19 | 20 | export default function CompareList() { 21 | const renderItem = ({ item }) => { 22 | return ( 23 | } 28 | /> 29 | ); 30 | }; 31 | const renderEmpty = () => ; 32 | 33 | const renderBigHeader = () => ( 34 | 39 | ); 40 | const renderFlatHeader = () => ( 41 | 46 | ); 47 | const renderFooter = () => ( 48 | 49 | No more items available... 50 | 51 | ); 52 | return ( 53 | 54 | 55 | 56 | ({ 64 | length: ITEM_HEIGHT, 65 | offset: ITEM_HEIGHT * index, 66 | index, 67 | })} 68 | ListHeaderComponent={renderBigHeader} 69 | ListFooterComponent={renderFooter} 70 | ListEmptyComponent={renderEmpty} 71 | headerHeight={100} // Default 0, need to specify the header height 72 | footerHeight={100} // Default 0, need to specify the footer height 73 | /> 74 | ({ 82 | length: ITEM_HEIGHT, 83 | offset: ITEM_HEIGHT * index, 84 | index, 85 | })} // Replaceable with `itemHeight={ITEM_HEIGHT}` 86 | ListHeaderComponent={renderFlatHeader} // Replaceable with `renderHeader` 87 | ListFooterComponent={renderFooter} // Replaceable with `renderFooter` 88 | ListEmptyComponent={renderEmpty} // Replaceable with `renderEmpty` 89 | keyExtractor={(item) => String(item.id)} // Removable 90 | /> 91 | 92 | 93 | 94 | 95 | ); 96 | } 97 | 98 | const styles = StyleSheet.create({ 99 | compare: { 100 | flex: 1, 101 | flexDirection: "row", 102 | }, 103 | container: { 104 | flex: 1, 105 | }, 106 | header: { 107 | flex: 1, 108 | paddingTop: 20, 109 | }, 110 | item: { 111 | flex: 1, 112 | maxHeight: ITEM_HEIGHT, 113 | }, 114 | }); 115 | -------------------------------------------------------------------------------- /example/src/lists/List.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | KeyboardAvoidingView, 4 | SafeAreaView, 5 | StyleSheet, 6 | View, 7 | } from "react-native"; 8 | import { List, Subheading } from "react-native-paper"; 9 | import { StatusBar } from "expo-status-bar"; 10 | 11 | import BigList from "react-native-big-list"; 12 | import data from "../data/data.json"; 13 | import Block from "./components/Block"; 14 | 15 | export default function SectionList() { 16 | const renderItem = ({ item }) => { 17 | return ( 18 | } 23 | /> 24 | ); 25 | }; 26 | const renderEmpty = () => ; 27 | const renderHeader = () => ( 28 | } 33 | /> 34 | ); 35 | const renderFooter = () => ( 36 | 37 | No more items available... 38 | 39 | ); 40 | return ( 41 | 42 | 43 | 44 | 58 | 59 | 60 | 61 | 62 | ); 63 | } 64 | 65 | const styles = StyleSheet.create({ 66 | compare: { 67 | flex: 1, 68 | flexDirection: "row", 69 | }, 70 | container: { 71 | flex: 1, 72 | }, 73 | }); 74 | -------------------------------------------------------------------------------- /example/src/lists/MultiSelectList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { KeyboardAvoidingView, SafeAreaView, StyleSheet } from "react-native"; 3 | import BigList from "react-native-big-list"; 4 | import { Checkbox } from "react-native-paper"; 5 | 6 | import data from "../data/data.json"; 7 | 8 | const SelectList = () => { 9 | const [selected, setSelected] = useState([]); 10 | 11 | const onSelect = (value, isSelected) => { 12 | if (!isSelected) { 13 | const selectedIndex = selected.indexOf(value); 14 | const newSelectedItems = [...selected]; 15 | newSelectedItems.splice(selectedIndex, 1); 16 | setSelected(newSelectedItems); 17 | } else { 18 | setSelected([...selected, value]); 19 | } 20 | 21 | // TODO: your logics 22 | 23 | console.log( 24 | "The value", 25 | value, 26 | "is " + (isSelected ? "selected" : "unselected"), 27 | ); 28 | }; 29 | 30 | const renderItem = ({ item }) => { 31 | return ( 32 | { 37 | onSelect(item.id, !selected.includes(item.id)); 38 | }} 39 | /> 40 | ); 41 | }; 42 | 43 | return ( 44 | 45 | 46 | 47 | 48 | 49 | ); 50 | }; 51 | 52 | const styles = StyleSheet.create({ 53 | container: { 54 | flex: 1, 55 | }, 56 | }); 57 | 58 | export default SelectList; 59 | -------------------------------------------------------------------------------- /example/src/lists/SectionList.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { KeyboardAvoidingView, SafeAreaView, StyleSheet } from "react-native"; 3 | import { Appbar, List, Subheading } from "react-native-paper"; 4 | import { StatusBar } from "expo-status-bar"; 5 | 6 | import BigList from "react-native-big-list"; 7 | import sections from "../data/sections.json"; 8 | import Block from "./components/Block"; 9 | 10 | export default function SectionList() { 11 | const renderItem = ({ item }) => { 12 | return ( 13 | } 18 | /> 19 | ); 20 | }; 21 | const renderEmpty = () => ; 22 | const renderHeader = () => ( 23 | } 28 | /> 29 | ); 30 | const renderFooter = () => ( 31 | 32 | No more items available... 33 | 34 | ); 35 | const renderSectionHeader = (section) => ( 36 | 37 | 42 | 43 | ); 44 | const renderSectionFooter = (section) => ( 45 | 46 | Footer Section {section} 47 | 48 | ); 49 | return ( 50 | 51 | 52 | 72 | 73 | 74 | 75 | ); 76 | } 77 | 78 | const styles = StyleSheet.create({ 79 | container: { 80 | flex: 1, 81 | }, 82 | header: { elevation: 0, height: 50 }, 83 | headerContent: { alignItems: "center", height: 50, justifyContent: "center" }, 84 | }); 85 | -------------------------------------------------------------------------------- /example/src/lists/SelectList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import BigList from "react-native-big-list"; 3 | import { Checkbox } from "react-native-paper"; 4 | 5 | const SelectList = ({ data, value, onSelect }) => { 6 | const [selected, setSelected] = useState(value); 7 | const renderItem = ({ item }) => { 8 | return ( 9 | { 14 | setSelected(item.value); 15 | onSelect(item.value); 16 | }} 17 | /> 18 | ); 19 | }; 20 | 21 | return ; 22 | }; 23 | 24 | export default SelectList; 25 | -------------------------------------------------------------------------------- /example/src/lists/components/Block.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { StyleSheet, View } from "react-native"; 3 | 4 | const Block = (props) => { 5 | return ; 6 | }; 7 | 8 | const styles = StyleSheet.create({ 9 | block: { 10 | flex: 1, 11 | justifyContent: "center", 12 | padding: 10, 13 | }, 14 | }); 15 | 16 | export default Block; 17 | -------------------------------------------------------------------------------- /example/src/utils/generate.js: -------------------------------------------------------------------------------- 1 | import faker from "faker"; 2 | 3 | const generate = (num) => { 4 | const result = []; 5 | for (let i = 0; i <= num; i++) { 6 | result.push({ 7 | id: i, 8 | title: faker.name.findName(), 9 | description: faker.lorem.sentence(), 10 | }); 11 | } 12 | return result; 13 | }; 14 | const generateSection = (num, sections) => { 15 | const result = []; 16 | for (let i = 0; i <= sections; i++) { 17 | const section = []; 18 | for (let y = 0; y <= num / sections; y++) { 19 | section.puLsh({ 20 | id: (i + 1) * y, 21 | title: faker.name.findName(), 22 | description: faker.lorem.sentence(), 23 | }); 24 | } 25 | result.push(section); 26 | } 27 | return result; 28 | }; 29 | const data = generate(10000); 30 | const sections = generateSection(10000, 500); 31 | 32 | const text = JSON.stringify({ data, sections }); 33 | 34 | navigator.clipboard.writeText(text).then( 35 | function () { 36 | console.log("Async: Copying to clipboard was successful!"); 37 | }, 38 | function (err) { 39 | console.error("Async: Could not copy text: ", err); 40 | }, 41 | ); 42 | -------------------------------------------------------------------------------- /lib/BigList.jsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { 4 | Animated, 5 | Platform, 6 | RefreshControl, 7 | ScrollView, 8 | View, 9 | } from "react-native"; 10 | 11 | import BigListItem, { BigListItemType } from "./BigListItem"; 12 | import BigListPlaceholder from "./BigListPlaceholder"; 13 | import BigListProcessor from "./BigListProcessor"; 14 | import BigListSection from "./BigListSection"; 15 | import { 16 | autobind, 17 | createElement, 18 | isNumeric, 19 | mergeViewStyle, 20 | processBlock, 21 | } from "./utils"; 22 | 23 | class BigList extends PureComponent { 24 | /** 25 | * Constructor. 26 | * @param props 27 | */ 28 | constructor(props) { 29 | super(props); 30 | autobind(this); 31 | // Initialize properties and state 32 | this.containerHeight = 0; 33 | this.scrollTop = 0; 34 | this.scrollTopValue = 35 | this.props.initialScrollIndex || new Animated.Value(0); 36 | this.scrollView = React.createRef(); 37 | this.state = this.getListState(); 38 | this.viewableItems = []; 39 | } 40 | 41 | /** 42 | * Get list state. 43 | * @param {array} data 44 | * @param {array[]|object|null|undefined} sections 45 | * @param {array} prevItems 46 | * @param {number|null} batchSizeThreshold 47 | * @param {number|function|null|undefined} headerHeight 48 | * @param {number|function|null|undefined} footerHeight 49 | * @param {number|function|null|undefined} sectionHeaderHeight 50 | * @param {number|function|null|undefined} itemHeight 51 | * @param {number|function|null|undefined} sectionFooterHeight 52 | * @param {number|null|undefined} insetTop 53 | * @param {number|null|undefined} insetBottom 54 | * @param {number|null|undefined} numColumns 55 | * @param {number|null|undefined} batchSize 56 | * @param {number|null|undefined} blockStart 57 | * @param {number|null|undefined} blockEnd 58 | * @param {function|null|undefined} getItemLayout 59 | * @returns {{blockStart: *, batchSize: *, blockEnd: *, items: [], height: *}|{blockStart, batchSize, blockEnd, items: [], height: *}} 60 | */ 61 | static getListState( 62 | { 63 | data, 64 | sections, 65 | batchSizeThreshold, 66 | headerHeight, 67 | footerHeight, 68 | sectionHeaderHeight, 69 | itemHeight, 70 | sectionFooterHeight, 71 | insetTop, 72 | insetBottom, 73 | numColumns, 74 | getItemLayout, 75 | }, 76 | { batchSize, blockStart, blockEnd, items: prevItems }, 77 | ) { 78 | if (batchSize === 0) { 79 | return { 80 | batchSize, 81 | blockStart, 82 | blockEnd, 83 | height: insetTop + insetBottom, 84 | items: [], 85 | }; 86 | } 87 | const self = BigList; 88 | const layoutItemHeight = self.getItemHeight(itemHeight, getItemLayout); 89 | const sectionLengths = self.getSectionLengths(sections, data); 90 | const processor = new BigListProcessor({ 91 | sections: sectionLengths, 92 | itemHeight: layoutItemHeight, 93 | headerHeight, 94 | footerHeight, 95 | sectionHeaderHeight, 96 | sectionFooterHeight, 97 | insetTop, 98 | insetBottom, 99 | numColumns, 100 | }); 101 | return { 102 | ...{ 103 | batchSize, 104 | blockStart, 105 | blockEnd, 106 | }, 107 | ...processor.process( 108 | blockStart - batchSize, 109 | blockEnd + batchSize, 110 | prevItems || [], 111 | ), 112 | }; 113 | } 114 | 115 | /** 116 | * Get list state 117 | * @param {object} props 118 | * @param {object} options. 119 | * @return {{blockStart: *, batchSize: *, blockEnd: *, items: *[], height: *}|{blockStart, batchSize, blockEnd, items: *[], height: *}} 120 | */ 121 | getListState(props, options) { 122 | const stateProps = props || this.props; 123 | return this.constructor.getListState( 124 | stateProps, 125 | options || 126 | processBlock({ 127 | containerHeight: this.containerHeight, 128 | scrollTop: this.scrollTop, 129 | batchSizeThreshold: stateProps.batchSizeThreshold, 130 | }), 131 | ); 132 | } 133 | 134 | /** 135 | * Get sections item lengths. 136 | * @param {array[]|object|null|undefined} sections 137 | * @param {array} data 138 | * @returns {int[]} 139 | */ 140 | static getSectionLengths(sections = null, data = null) { 141 | if (sections !== null) { 142 | return sections.map((section) => { 143 | return section.length; 144 | }); 145 | } 146 | return [data?.length]; 147 | } 148 | 149 | /** 150 | * Get sections item lengths. 151 | * @returns {int[]} 152 | */ 153 | getSectionLengths() { 154 | const { sections, data } = this.props; 155 | return this.constructor.getSectionLengths(sections, data); 156 | } 157 | 158 | /** 159 | * Get item height. 160 | * @param {number} itemHeight 161 | * @param {function|null|undefined} getItemLayout 162 | * @return {null|*} 163 | */ 164 | static getItemHeight(itemHeight, getItemLayout) { 165 | if (getItemLayout) { 166 | const itemLayout = getItemLayout([], 0); 167 | return itemLayout.length; 168 | } 169 | if (itemHeight) { 170 | return itemHeight; 171 | } 172 | return null; 173 | } 174 | 175 | /** 176 | * Get item height. 177 | * @return {null|*} 178 | */ 179 | getItemHeight() { 180 | const { itemHeight, getItemLayout } = this.props; 181 | return this.constructor.getItemHeight(itemHeight, getItemLayout); 182 | } 183 | 184 | /** 185 | * Is item visible. 186 | * @param {int} index 187 | * @param {int} section 188 | * @returns {boolean} 189 | */ 190 | isVisible({ index, section = 0 }) { 191 | const position = this.getItemOffset({ index, section }); 192 | return ( 193 | position >= this.scrollTop && 194 | position <= this.scrollTop + this.containerHeight 195 | ); 196 | } 197 | 198 | /** 199 | * Provides a reference to the underlying scroll component. 200 | * @returns {ScrollView|null} 201 | */ 202 | getNativeScrollRef() { 203 | return this.scrollView.current; 204 | } 205 | 206 | /** 207 | * Get list processor, 208 | * @returns {BigListProcessor} 209 | */ 210 | getListProcessor() { 211 | const scrollView = this.getNativeScrollRef(); 212 | if (scrollView != null) { 213 | const { 214 | headerHeight, 215 | footerHeight, 216 | sectionHeaderHeight, 217 | sectionFooterHeight, 218 | insetTop, 219 | insetBottom, 220 | numColumns, 221 | } = this.props; 222 | const itemHeight = this.getItemHeight(); 223 | const sectionLengths = this.getSectionLengths(); 224 | return new BigListProcessor({ 225 | sections: sectionLengths, 226 | headerHeight, 227 | footerHeight, 228 | sectionHeaderHeight, 229 | sectionFooterHeight, 230 | itemHeight, 231 | insetTop, 232 | insetBottom, 233 | scrollView, 234 | numColumns, 235 | }); 236 | } 237 | return null; 238 | } 239 | 240 | /** 241 | * Displays the scroll indicators momentarily. 242 | */ 243 | flashScrollIndicators() { 244 | const scrollView = this.getNativeScrollRef(); 245 | if (scrollView != null) { 246 | scrollView.flashScrollIndicators(); 247 | } 248 | } 249 | 250 | /** 251 | * Scrolls to a given x, y offset, either immediately, with a smooth animation. 252 | * @param {int} x 253 | * @param {int} y 254 | * @param {bool} animated 255 | */ 256 | scrollTo({ x = 0, y = 0, animated = true } = {}) { 257 | const scrollView = this.getNativeScrollRef(); 258 | if (scrollView != null) { 259 | scrollView.scrollTo({ 260 | x: x, 261 | y: y, 262 | animated, 263 | }); 264 | } 265 | } 266 | 267 | /** 268 | * Scroll to index. 269 | * @param {int} index 270 | * @param {int} section 271 | * @param {bool} animated 272 | * @returns {bool} 273 | */ 274 | scrollToIndex({ index, section = 0, animated = true }) { 275 | const processor = this.getListProcessor(); 276 | if (processor != null && index != null && section != null) { 277 | return processor.scrollToPosition(section, index, animated); 278 | } 279 | return false; 280 | } 281 | 282 | /** 283 | * Alias to scrollToIndex with polyfill for SectionList. 284 | * @see scrollToIndex 285 | * @param {int} itemIndex 286 | * @param {int} sectionIndex 287 | * @param {bool} animated 288 | * @returns {bool} 289 | */ 290 | scrollToLocation({ itemIndex, sectionIndex, animated = true }) { 291 | return this.scrollToIndex({ 292 | section: sectionIndex, 293 | index: itemIndex, 294 | animated, 295 | }); 296 | } 297 | 298 | /** 299 | * Scroll to item. 300 | * @param {object} item 301 | * @param {bool} animated 302 | * @returns {bool} 303 | */ 304 | scrollToItem({ item, animated = false }) { 305 | let index; 306 | if (this.hasSections()) { 307 | const coords = JSON.stringify( 308 | this.map((a) => { 309 | return a[0] + "|" + a[1]; 310 | }), 311 | ); 312 | index = coords.indexOf(item[0] + "|" + item[1]) !== -1; 313 | } else { 314 | index = this.props.data.indexOf(item); 315 | } 316 | return this.scrollToIndex({ index, animated }); 317 | } 318 | 319 | /** 320 | * Scroll to offset. 321 | * @param {number} offset 322 | * @param {bool} animated 323 | * @returns {bool} 324 | */ 325 | scrollToOffset({ offset, animated = false }) { 326 | const scrollRef = this.getNativeScrollRef(); 327 | if (scrollRef != null) { 328 | scrollRef.scrollTo({ 329 | x: 0, 330 | y: offset, 331 | animated, 332 | }); 333 | return true; 334 | } 335 | return false; 336 | } 337 | 338 | /** 339 | * Scroll to top. 340 | * @param {bool} animated 341 | * @returns {bool} 342 | */ 343 | scrollToTop({ animated = true } = {}) { 344 | return this.scrollTo({ x: 0, y: 0, animated }); 345 | } 346 | 347 | /** 348 | * Scroll to end. 349 | * @param {bool} animated 350 | * @returns {bool} 351 | */ 352 | scrollToEnd({ animated = true } = {}) { 353 | const { data } = this.props; 354 | let section = 0; 355 | let index = 0; 356 | if (this.hasSections()) { 357 | const sectionLengths = this.getSectionLengths(); 358 | section = sectionLengths[sectionLengths.length - 1]; 359 | } else { 360 | index = data.length; 361 | } 362 | return this.scrollToIndex({ section, index, animated }); 363 | } 364 | 365 | /** 366 | * Scroll to section. 367 | * @param {int} section 368 | * @param {bool} animated 369 | * @returns {bool} 370 | */ 371 | scrollToSection({ section, animated = true }) { 372 | return this.scrollToIndex({ index: 0, section, animated }); 373 | } 374 | 375 | /** 376 | * On viewable items changed. 377 | */ 378 | onViewableItemsChanged() { 379 | const { onViewableItemsChanged } = this.props; 380 | if (onViewableItemsChanged) { 381 | const prevItems = this.viewableItems; 382 | const currentItems = this.state.items 383 | .map(({ type, section, index, key }) => { 384 | if (type === BigListItemType.ITEM) { 385 | return { 386 | item: this.getItem({ section, index }), 387 | section: section, 388 | key: key, 389 | index: (section + 1) * index, 390 | isViewable: this.isVisible({ section, index }), 391 | }; 392 | } 393 | return false; 394 | }) 395 | .filter(Boolean); 396 | this.viewableItems = currentItems.filter((item) => item.isViewable); 397 | const changed = prevItems 398 | .filter( 399 | ({ index: prevIndex }) => 400 | !this.viewableItems.some( 401 | ({ index: nextIndex }) => nextIndex === prevIndex, 402 | ), 403 | ) 404 | .map((item) => { 405 | item.isViewable = this.isVisible({ 406 | section: item.section, 407 | index: item.index, 408 | }); 409 | return item; 410 | }); 411 | 412 | const prevViewableItem = prevItems.length; 413 | const currentViewableItem = this.viewableItems.length; 414 | 415 | if (changed.length > 0 || prevViewableItem !== currentViewableItem) { 416 | onViewableItemsChanged({ viewableItems: this.viewableItems, changed }); 417 | } 418 | } 419 | } 420 | 421 | /** 422 | * Handle scroll. 423 | * @param event 424 | */ 425 | onScroll(event) { 426 | const { nativeEvent } = event; 427 | const { contentInset, batchSizeThreshold, onViewableItemsChanged } = 428 | this.props; 429 | this.containerHeight = 430 | nativeEvent.layoutMeasurement.height - 431 | (contentInset.top || 0) - 432 | (contentInset.bottom || 0); 433 | this.scrollTop = Math.min( 434 | Math.max(0, nativeEvent.contentOffset.y), 435 | nativeEvent.contentSize.height - this.containerHeight, 436 | ); 437 | 438 | const nextState = processBlock({ 439 | containerHeight: this.containerHeight, 440 | scrollTop: this.scrollTop, 441 | batchSizeThreshold, 442 | }); 443 | 444 | if ( 445 | nextState.batchSize !== this.state.batchSize || 446 | nextState.blockStart !== this.state.blockStart || 447 | nextState.blockEnd !== this.state.blockEnd 448 | ) { 449 | this.setState(nextState); 450 | } 451 | 452 | if (onViewableItemsChanged) { 453 | this.onViewableItemsChanged(); 454 | } 455 | 456 | const { onScroll, onEndReached, onEndReachedThreshold } = this.props; 457 | if (onScroll != null) { 458 | onScroll(event); 459 | } 460 | const { layoutMeasurement, contentOffset, contentSize } = nativeEvent; 461 | const distanceFromEnd = 462 | contentSize.height - (layoutMeasurement.height + contentOffset.y); 463 | if (distanceFromEnd <= layoutMeasurement.height * onEndReachedThreshold) { 464 | if (!this.endReached) { 465 | this.endReached = true; 466 | onEndReached && onEndReached({ distanceFromEnd }); 467 | } 468 | } else { 469 | this.endReached = false; 470 | } 471 | } 472 | 473 | /** 474 | * Handle layout. 475 | * @param event 476 | */ 477 | onLayout(event) { 478 | const { nativeEvent } = event; 479 | const { contentInset, batchSizeThreshold } = this.props; 480 | this.containerHeight = 481 | nativeEvent.layout.height - 482 | (contentInset.top || 0) - 483 | (contentInset.bottom || 0); 484 | const nextState = processBlock({ 485 | containerHeight: this.containerHeight, 486 | scrollTop: this.scrollTop, 487 | batchSizeThreshold, 488 | }); 489 | if ( 490 | nextState.batchSize !== this.state.batchSize || 491 | nextState.blockStart !== this.state.blockStart || 492 | nextState.blockEnd !== this.state.blockEnd 493 | ) { 494 | this.setState(nextState); 495 | } 496 | const { onLayout } = this.props; 497 | if (onLayout) { 498 | onLayout(event); 499 | } 500 | } 501 | 502 | /** 503 | * Handle scroll end. 504 | * @param event 505 | */ 506 | onScrollEnd(event) { 507 | const { renderAccessory, onScrollEnd } = this.props; 508 | if (renderAccessory != null) { 509 | this.forceUpdate(); 510 | } 511 | if (onScrollEnd) { 512 | onScrollEnd(event); 513 | } 514 | } 515 | 516 | /** 517 | * Handle scroll end. 518 | * @param event 519 | */ 520 | onMomentumScrollEnd(event) { 521 | const { onMomentumScrollEnd } = this.props; 522 | this.onScrollEnd(event); 523 | if (onMomentumScrollEnd) { 524 | onMomentumScrollEnd(event); 525 | } 526 | } 527 | 528 | /** 529 | * Is empty 530 | * @returns {boolean} 531 | */ 532 | isEmpty() { 533 | const sectionLengths = this.getSectionLengths(); 534 | const length = sectionLengths.reduce((total, sectionLength) => { 535 | return total + sectionLength; 536 | }, 0); 537 | return length === 0; 538 | } 539 | 540 | /** 541 | * Get derived state. 542 | * @param props 543 | * @param state 544 | * @returns {{blockStart: *, batchSize: *, blockEnd: *, items: *[], height: *}|{blockStart, batchSize, blockEnd, items: *[], height: *}} 545 | */ 546 | static getDerivedStateFromProps(props, state) { 547 | return BigList.getListState(props, state); 548 | } 549 | 550 | /** 551 | * Has sections. 552 | * @returns {boolean} 553 | */ 554 | hasSections() { 555 | return this.props.sections !== null; 556 | } 557 | 558 | /** 559 | * Get item scroll view offset. 560 | * @param {int} section 561 | * @param {int} index 562 | * @returns {*} 563 | */ 564 | getItemOffset({ section = 0, index }) { 565 | const { 566 | insetTop, 567 | headerHeight, 568 | sectionHeaderHeight, 569 | sectionFooterHeight, 570 | numColumns, 571 | itemHeight, 572 | } = this.props; 573 | 574 | // Header + inset 575 | let offset = 576 | insetTop + isNumeric(headerHeight) 577 | ? Number(headerHeight) 578 | : headerHeight(); 579 | 580 | const sections = this.getSectionLengths(); 581 | let foundIndex = false; 582 | let s = 0; 583 | 584 | while (s <= section) { 585 | const rows = Math.ceil(sections[s] / numColumns); 586 | if (rows === 0) { 587 | s += 1; 588 | continue; 589 | } 590 | 591 | // Section header 592 | offset += isNumeric(sectionHeaderHeight) 593 | ? Number(sectionHeaderHeight) 594 | : sectionHeaderHeight(s); 595 | 596 | // Items 597 | if (isNumeric(itemHeight)) { 598 | const uniformHeight = this.getItemHeight(section); 599 | if (s === section) { 600 | offset += uniformHeight * Math.ceil(index / numColumns); 601 | foundIndex = true; 602 | } else { 603 | offset += uniformHeight * rows; 604 | } 605 | } else { 606 | for (let i = 0; i < rows; i++) { 607 | if (s < section || (s === section && i < index)) { 608 | offset += itemHeight(s, Math.ceil(i / numColumns)); 609 | } else if (s === section && i === index) { 610 | foundIndex = true; 611 | break; 612 | } 613 | } 614 | } 615 | 616 | // Section footer 617 | if (!foundIndex) { 618 | offset += isNumeric(sectionFooterHeight) 619 | ? Number(sectionFooterHeight) 620 | : sectionFooterHeight(s); 621 | } 622 | s += 1; 623 | } 624 | 625 | return offset; 626 | } 627 | 628 | /** 629 | * Get item data. 630 | * @param {int} section 631 | * @param {int} index 632 | * @returns {*} 633 | */ 634 | getItem({ index, section = 0 }) { 635 | if (this.hasSections()) { 636 | return this.props.sections[section][index]; 637 | } else { 638 | return this.props.data[index]; 639 | } 640 | } 641 | 642 | /** 643 | * Get items data. 644 | * @returns {*} 645 | */ 646 | getItems() { 647 | return this.hasSections() ? this.props.sections : this.props.data; 648 | } 649 | 650 | /** 651 | * Render all list items. 652 | * @returns {[]|*} 653 | */ 654 | renderItems() { 655 | const { 656 | keyExtractor, 657 | numColumns, 658 | hideMarginalsOnEmpty, 659 | hideHeaderOnEmpty, 660 | hideFooterOnEmpty, 661 | columnWrapperStyle, 662 | controlItemRender, 663 | placeholder, 664 | placeholderComponent, 665 | placeholderImage, 666 | ListEmptyComponent, 667 | ListFooterComponent, 668 | ListFooterComponentStyle, 669 | ListHeaderComponent, 670 | ListHeaderComponentStyle, 671 | renderHeader, 672 | renderFooter, 673 | renderSectionHeader, 674 | renderItem, 675 | renderSectionFooter, 676 | renderEmpty, 677 | } = this.props; 678 | const { items = [] } = this.state; 679 | 680 | const itemStyle = this.getBaseStyle(); 681 | const fullItemStyle = mergeViewStyle(itemStyle, { 682 | width: "100%", 683 | }); 684 | 685 | // On empty list 686 | const isEmptyList = this.isEmpty(); 687 | const emptyItem = ListEmptyComponent 688 | ? createElement(ListEmptyComponent, {style: fullItemStyle}) 689 | : renderEmpty 690 | ? createElement(renderEmpty(), {style: fullItemStyle}) 691 | : null; 692 | 693 | if (isEmptyList && emptyItem) { 694 | if (hideMarginalsOnEmpty || (hideHeaderOnEmpty && hideFooterOnEmpty)) { 695 | // Render empty 696 | return emptyItem; 697 | } else { 698 | // Add empty item 699 | const headerIndex = items.findIndex( 700 | (item) => item.type === BigListItemType.HEADER, 701 | ); 702 | items.splice(headerIndex + 1, 0, { 703 | type: BigListItemType.EMPTY, 704 | key: 'empty', 705 | }); 706 | if (hideHeaderOnEmpty) { 707 | // Hide header 708 | items.splice(headerIndex, 1); 709 | } 710 | if (hideFooterOnEmpty) { 711 | // Hide footer 712 | const footerIndex = items.findIndex( 713 | (item) => item.type === BigListItemType.FOOTER, 714 | ); 715 | items.splice(footerIndex, 1); 716 | } 717 | } 718 | } 719 | 720 | // Sections positions 721 | const sectionPositions = []; 722 | items.forEach(({ type, position }) => { 723 | if (type === BigListItemType.SECTION_HEADER) { 724 | sectionPositions.push(position); 725 | } 726 | }); 727 | 728 | // Render items 729 | const children = []; 730 | items.forEach(({ type, key, position, height, section, index }) => { 731 | const itemKey = key || position; // Fallback fix 732 | let uniqueKey = String((section + 1) * index); 733 | let child; 734 | let style; 735 | switch (type) { 736 | case BigListItemType.HEADER: 737 | if (ListHeaderComponent != null) { 738 | child = createElement(ListHeaderComponent); 739 | style = mergeViewStyle(fullItemStyle, ListHeaderComponentStyle); 740 | } else { 741 | child = renderHeader(); 742 | style = fullItemStyle; 743 | } 744 | // falls through 745 | case BigListItemType.FOOTER: 746 | if (type === BigListItemType.FOOTER) { 747 | if (ListFooterComponent != null) { 748 | child = createElement(ListFooterComponent); 749 | style = mergeViewStyle(fullItemStyle, ListFooterComponentStyle); 750 | } else { 751 | child = renderFooter(); 752 | style = fullItemStyle; 753 | } 754 | } 755 | // falls through 756 | case BigListItemType.SECTION_FOOTER: 757 | if (type === BigListItemType.SECTION_FOOTER) { 758 | height = isEmptyList ? 0 : height; // Hide section footer on empty 759 | child = renderSectionFooter(section); 760 | style = fullItemStyle; 761 | } 762 | // falls through 763 | case BigListItemType.ITEM: 764 | if (type === BigListItemType.ITEM) { 765 | const item = this.getItem({ section, index }); 766 | uniqueKey = keyExtractor 767 | ? keyExtractor(item, uniqueKey) 768 | : uniqueKey; 769 | style = 770 | numColumns > 1 771 | ? mergeViewStyle(itemStyle, columnWrapperStyle || {}) 772 | : itemStyle; 773 | 774 | const renderArguments = { 775 | item, 776 | index, 777 | section: undefined, 778 | key: undefined, 779 | style: undefined, 780 | }; 781 | 782 | if (this.hasSections()) { 783 | renderArguments.section = section; 784 | } 785 | if (controlItemRender) { 786 | renderArguments.key = uniqueKey; 787 | renderArguments.style = mergeViewStyle(style, { 788 | height, 789 | width: 100 / numColumns + "%", 790 | }); 791 | } 792 | child = renderItem(renderArguments); 793 | } 794 | if (child != null) { 795 | children.push( 796 | type === BigListItemType.ITEM && controlItemRender ? ( 797 | child 798 | ) : ( 799 | 806 | {child} 807 | 808 | ), 809 | ); 810 | } 811 | break; 812 | case BigListItemType.EMPTY: 813 | children.push( 814 | 815 | {emptyItem} 816 | 817 | ); 818 | break; 819 | case BigListItemType.SPACER: 820 | children.push( 821 | placeholder ? ( 822 | 828 | ) : ( 829 | 830 | ), 831 | ); 832 | break; 833 | case BigListItemType.SECTION_HEADER: 834 | height = isEmptyList ? 0 : height; // Hide section header on empty 835 | sectionPositions.shift(); 836 | child = renderSectionHeader(section); 837 | if (child != null) { 838 | children.push( 839 | 847 | {child} 848 | , 849 | ); 850 | } 851 | break; 852 | } 853 | }); 854 | return children; 855 | } 856 | 857 | /** 858 | * Component did mount. 859 | */ 860 | componentDidMount() { 861 | const { stickySectionHeadersEnabled, nativeOffsetValues } = this.props; 862 | const scrollView = this.getNativeScrollRef(); 863 | if ( 864 | stickySectionHeadersEnabled && 865 | scrollView != null && 866 | Platform.OS !== "web" 867 | ) { 868 | // Disabled on web 869 | this.scrollTopValueAttachment = Animated.attachNativeEvent( 870 | scrollView, 871 | "onScroll", 872 | [{ nativeEvent: { contentOffset: { y: this.scrollTopValue } } }], 873 | ); 874 | } 875 | if (nativeOffsetValues && scrollView != null && Platform.OS !== "web") { 876 | Animated.attachNativeEvent(scrollView, "onScroll", [ 877 | { 878 | nativeEvent: { 879 | contentOffset: nativeOffsetValues, 880 | }, 881 | }, 882 | ]); 883 | } 884 | } 885 | 886 | /** 887 | * Component did update. 888 | * @param prevProps 889 | */ 890 | componentDidUpdate(prevProps) { 891 | if (prevProps.initialScrollIndex !== this.props.initialScrollIndex) { 892 | throw new Error("scrollTopValue cannot changed after mounting"); 893 | } 894 | } 895 | 896 | /** 897 | * Component will unmount. 898 | */ 899 | componentWillUnmount() { 900 | if (this.scrollTopValueAttachment != null) { 901 | this.scrollTopValueAttachment.detach(); 902 | } 903 | } 904 | 905 | /** 906 | * Get base style. 907 | * @return {{transform: [{scaleX: number}]}|{transform: [{scaleY: number}]}} 908 | */ 909 | getBaseStyle() { 910 | const { inverted, horizontal } = this.props; 911 | if (inverted) { 912 | if (horizontal) { 913 | return { 914 | transform: [{ scaleX: -1 }], 915 | }; 916 | } else { 917 | return { 918 | transform: [{ scaleY: -1 }], 919 | }; 920 | } 921 | } 922 | return {}; 923 | } 924 | 925 | /** 926 | * Render. 927 | * @returns {JSX.Element} 928 | */ 929 | render() { 930 | // Reduce list properties 931 | const { 932 | data, 933 | keyExtractor, 934 | inverted, 935 | horizontal, // Disabled 936 | placeholder, 937 | placeholderImage, 938 | placeholderComponent, 939 | sections, 940 | initialScrollIndex, 941 | columnWrapperStyle, 942 | renderHeader, 943 | renderFooter, 944 | renderSectionHeader, 945 | renderItem, 946 | renderSectionFooter, 947 | renderScrollViewWrapper, 948 | renderEmpty, 949 | renderAccessory, 950 | itemHeight, 951 | footerHeight, 952 | headerHeight, 953 | sectionHeaderHeight, 954 | sectionFooterHeight, 955 | insetTop, 956 | insetBottom, 957 | actionSheetScrollRef, 958 | stickySectionHeadersEnabled, 959 | onEndReached, 960 | onEndReachedThreshold, 961 | onRefresh, 962 | refreshing, 963 | ListEmptyComponent, 964 | ListFooterComponent, 965 | ListFooterComponentStyle, 966 | ListHeaderComponent, 967 | ListHeaderComponentStyle, 968 | hideMarginalsOnEmpty, 969 | hideFooterOnEmpty, 970 | hideHeaderOnEmpty, 971 | ScrollViewComponent, 972 | ...props 973 | } = this.props; 974 | 975 | const wrapper = renderScrollViewWrapper || ((val) => val); 976 | const handleScroll = 977 | stickySectionHeadersEnabled && Platform.OS === "web" 978 | ? Animated.event( 979 | [{ nativeEvent: { contentOffset: { y: this.scrollTopValue } } }], 980 | { 981 | listener: (event) => this.onScroll(event), 982 | useNativeDriver: false, 983 | }, 984 | ) 985 | : this.onScroll; 986 | 987 | const defaultProps = { 988 | refreshControl: 989 | onRefresh && !this.props.refreshControl ? ( 990 | 991 | ) : null, 992 | contentContainerStyle: { 993 | maxWidth: "100%", 994 | }, 995 | }; 996 | 997 | const overwriteProps = { 998 | ref: (ref) => { 999 | this.scrollView.current = ref; 1000 | if (actionSheetScrollRef) { 1001 | actionSheetScrollRef.current = ref; 1002 | } 1003 | }, 1004 | onScroll: handleScroll, 1005 | onLayout: this.onLayout, 1006 | onMomentumScrollEnd: this.onMomentumScrollEnd, 1007 | onScrollEndDrag: this.onScrollEnd, 1008 | }; 1009 | 1010 | const scrollViewProps = { 1011 | ...defaultProps, 1012 | ...props, 1013 | ...overwriteProps, 1014 | }; 1015 | 1016 | // Content container style merge 1017 | scrollViewProps.contentContainerStyle = mergeViewStyle( 1018 | props.contentContainerStyle, 1019 | defaultProps.contentContainerStyle, 1020 | ); 1021 | 1022 | const ListScrollView = ScrollViewComponent || Animated.ScrollView; 1023 | 1024 | const scrollView = wrapper( 1025 | 1026 | {this.renderItems()} 1027 | , 1028 | ); 1029 | 1030 | const scrollStyle = mergeViewStyle( 1031 | { 1032 | flex: 1, 1033 | maxHeight: Platform.select({ web: "100vh", default: "100%" }), 1034 | }, 1035 | this.getBaseStyle(), 1036 | ); 1037 | 1038 | return ( 1039 | 1040 | {scrollView} 1041 | {renderAccessory != null ? renderAccessory(this) : null} 1042 | 1043 | ); 1044 | } 1045 | } 1046 | 1047 | BigList.propTypes = { 1048 | inverted: PropTypes.bool, 1049 | horizontal: PropTypes.bool, 1050 | actionSheetScrollRef: PropTypes.any, 1051 | batchSizeThreshold: PropTypes.number, 1052 | bottom: PropTypes.number, 1053 | numColumns: PropTypes.number, 1054 | columnWrapperStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), 1055 | contentInset: PropTypes.shape({ 1056 | bottom: PropTypes.number, 1057 | left: PropTypes.number, 1058 | right: PropTypes.number, 1059 | top: PropTypes.number, 1060 | }), 1061 | controlItemRender: PropTypes.bool, 1062 | data: PropTypes.array, 1063 | placeholder: PropTypes.bool, 1064 | placeholderImage: PropTypes.any, 1065 | placeholderComponent: PropTypes.oneOfType([ 1066 | PropTypes.elementType, 1067 | PropTypes.element, 1068 | PropTypes.node, 1069 | ]), 1070 | footerHeight: PropTypes.oneOfType([ 1071 | PropTypes.string, 1072 | PropTypes.number, 1073 | PropTypes.func, 1074 | ]), 1075 | getItemLayout: PropTypes.func, 1076 | headerHeight: PropTypes.oneOfType([ 1077 | PropTypes.string, 1078 | PropTypes.number, 1079 | PropTypes.func, 1080 | ]), 1081 | insetBottom: PropTypes.number, 1082 | insetTop: PropTypes.number, 1083 | itemHeight: PropTypes.oneOfType([ 1084 | PropTypes.string, 1085 | PropTypes.number, 1086 | PropTypes.func, 1087 | ]), 1088 | keyboardDismissMode: PropTypes.string, 1089 | keyboardShouldPersistTaps: PropTypes.string, 1090 | ListEmptyComponent: PropTypes.oneOfType([ 1091 | PropTypes.elementType, 1092 | PropTypes.element, 1093 | PropTypes.node, 1094 | ]), 1095 | ListFooterComponent: PropTypes.oneOfType([ 1096 | PropTypes.elementType, 1097 | PropTypes.element, 1098 | PropTypes.node, 1099 | ]), 1100 | ListFooterComponentStyle: PropTypes.oneOfType([ 1101 | PropTypes.object, 1102 | PropTypes.array, 1103 | ]), 1104 | ListHeaderComponent: PropTypes.oneOfType([ 1105 | PropTypes.elementType, 1106 | PropTypes.element, 1107 | PropTypes.node, 1108 | ]), 1109 | ListHeaderComponentStyle: PropTypes.oneOfType([ 1110 | PropTypes.object, 1111 | PropTypes.array, 1112 | ]), 1113 | onEndReached: PropTypes.func, 1114 | onEndReachedThreshold: PropTypes.number, 1115 | onLayout: PropTypes.func, 1116 | onRefresh: PropTypes.func, 1117 | onScroll: PropTypes.func, 1118 | onScrollEnd: PropTypes.func, 1119 | onViewableItemsChanged: PropTypes.func, 1120 | removeClippedSubviews: PropTypes.bool, 1121 | renderAccessory: PropTypes.func, 1122 | renderScrollViewWrapper: PropTypes.func, 1123 | renderEmpty: PropTypes.func, 1124 | renderFooter: PropTypes.func, 1125 | renderHeader: PropTypes.func, 1126 | renderItem: PropTypes.func.isRequired, 1127 | renderSectionHeader: PropTypes.func, 1128 | renderSectionFooter: PropTypes.func, 1129 | keyExtractor: PropTypes.func, 1130 | refreshing: PropTypes.bool, 1131 | scrollEventThrottle: PropTypes.number, 1132 | initialScrollIndex: PropTypes.number, 1133 | hideMarginalsOnEmpty: PropTypes.bool, 1134 | sectionFooterHeight: PropTypes.oneOfType([ 1135 | PropTypes.string, 1136 | PropTypes.number, 1137 | PropTypes.func, 1138 | ]), 1139 | sectionHeaderHeight: PropTypes.oneOfType([ 1140 | PropTypes.string, 1141 | PropTypes.number, 1142 | PropTypes.func, 1143 | ]), 1144 | sections: PropTypes.array, 1145 | stickySectionHeadersEnabled: PropTypes.bool, 1146 | nativeOffsetValues: PropTypes.shape({ 1147 | x: PropTypes.instanceOf(Animated.Value), 1148 | y: PropTypes.instanceOf(Animated.Value), 1149 | }), 1150 | ScrollViewComponent: PropTypes.oneOfType([ 1151 | PropTypes.func, 1152 | PropTypes.elementType, 1153 | ]), 1154 | }; 1155 | 1156 | BigList.defaultProps = { 1157 | // Data 1158 | data: [], 1159 | inverted: false, 1160 | horizontal: false, 1161 | sections: null, 1162 | refreshing: false, 1163 | batchSizeThreshold: 1, 1164 | numColumns: 1, 1165 | placeholder: Platform.select({ 1166 | web: false, 1167 | default: false /* TODO: default disabled until a solution for different screen sizes is found */, 1168 | }), 1169 | // Renders 1170 | renderItem: () => null, 1171 | renderHeader: () => null, 1172 | renderFooter: () => null, 1173 | renderSectionHeader: () => null, 1174 | renderSectionFooter: () => null, 1175 | hideMarginalsOnEmpty: false, 1176 | hideFooterOnEmpty: false, 1177 | hideHeaderOnEmpty: false, 1178 | controlItemRender: false, 1179 | // Height 1180 | itemHeight: 50, 1181 | headerHeight: 0, 1182 | footerHeight: 0, 1183 | sectionHeaderHeight: 0, 1184 | sectionFooterHeight: 0, 1185 | // Scroll 1186 | stickySectionHeadersEnabled: true, 1187 | removeClippedSubviews: false, 1188 | scrollEventThrottle: Platform.OS === "web" ? 5 : 16, 1189 | // Keyboard 1190 | keyboardShouldPersistTaps: "always", 1191 | keyboardDismissMode: "interactive", 1192 | // Insets 1193 | insetTop: 0, 1194 | insetBottom: 0, 1195 | contentInset: { top: 0, right: 0, left: 0, bottom: 0 }, 1196 | onEndReachedThreshold: 0, 1197 | nativeOffsetValues: undefined, 1198 | ScrollViewComponent: Animated.ScrollView, 1199 | }; 1200 | 1201 | export default BigList; 1202 | -------------------------------------------------------------------------------- /lib/BigListItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { View } from "react-native"; 4 | 5 | import { mergeViewStyle } from "./utils"; 6 | 7 | export const BigListItemType = { 8 | SPACER: "spacer", 9 | HEADER: "header", 10 | SECTION_HEADER: "section_header", 11 | ITEM: "item", 12 | SECTION_FOOTER: "section_footer", 13 | FOOTER: "footer", 14 | EMPTY: "empty", 15 | }; 16 | 17 | /** 18 | * List item. 19 | * @param {string} uniqueKey 20 | * @param {React.node} children 21 | * @param {array|object|null|undefined} style 22 | * @param {number} height 23 | * @param {number} width 24 | * @returns {JSX.Element} 25 | * @constructor 26 | */ 27 | const BigListItem = ({ 28 | uniqueKey, 29 | children, 30 | style, 31 | height, 32 | width = "100%", 33 | }) => { 34 | return ( 35 | 42 | {children} 43 | 44 | ); 45 | }; 46 | 47 | BigListItem.propTypes = { 48 | children: PropTypes.oneOfType([ 49 | PropTypes.arrayOf(PropTypes.node), 50 | PropTypes.node, 51 | ]), 52 | uniqueKey: PropTypes.string, 53 | height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 54 | width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 55 | style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), 56 | }; 57 | 58 | BigListItem.defaultProps = { 59 | width: "100%", 60 | }; 61 | 62 | export default memo(BigListItem); 63 | -------------------------------------------------------------------------------- /lib/BigListItemRecycler.js: -------------------------------------------------------------------------------- 1 | import { BigListItemType } from "./BigListItem"; 2 | 3 | class BigListItemRecycler { 4 | static lastKey = 0; 5 | /** 6 | * Constructor. 7 | * @param {object[]} items 8 | */ 9 | constructor(items) { 10 | this.items = {}; 11 | this.pendingItems = {}; 12 | items.forEach((item) => { 13 | const { type, section, index } = item; 14 | const [itemsForType] = this.itemsForType(type); 15 | itemsForType[`${type}:${section}:${index}`] = item; 16 | }); 17 | } 18 | 19 | /** 20 | * Items for type. 21 | * @param {any} type 22 | * @returns {(*|{}|*[])[]} 23 | */ 24 | itemsForType(type) { 25 | return [ 26 | this.items[type] || (this.items[type] = {}), 27 | this.pendingItems[type] || (this.pendingItems[type] = []), 28 | ]; 29 | } 30 | 31 | /** 32 | * Get item. 33 | * @param {any} type 34 | * @param {number} position 35 | * @param {number} height 36 | * @param {int} section 37 | * @param {int} index 38 | * @returns {{section: int, position: number, index: number, type: any, key: number, height: int}} 39 | */ 40 | get({ type, position, height, section = 0, index = 0 }) { 41 | const [items, pendingItems] = this.itemsForType(type); 42 | const itemKey = `${type}:${section}:${index}`; 43 | let item = items[itemKey]; 44 | if (item == null) { 45 | item = { type, key: -1, position, height, section, index }; 46 | pendingItems.push(item); 47 | } else { 48 | item.position = position; 49 | item.height = height; 50 | delete items[itemKey]; 51 | } 52 | return item; 53 | } 54 | 55 | /** 56 | * Fill. 57 | */ 58 | fill() { 59 | Object.values(BigListItemType).forEach((type) => { 60 | const [items, pendingItems] = this.itemsForType(type); 61 | let index = 0; 62 | Object.values(items).forEach(({ key }) => { 63 | const item = pendingItems[index]; 64 | if (item == null) { 65 | return false; 66 | } 67 | item.key = key; 68 | index++; 69 | }); 70 | 71 | for (; index < pendingItems.length; index++) { 72 | pendingItems[index].key = ++BigListItemRecycler.lastKey; 73 | } 74 | pendingItems.length = 0; 75 | }); 76 | } 77 | } 78 | export default BigListItemRecycler; 79 | -------------------------------------------------------------------------------- /lib/BigListPlaceholder.jsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Animated, Image } from "react-native"; 4 | 5 | import { createElement, mergeViewStyle } from "./utils"; 6 | 7 | const BigListPlaceholder = ({ 8 | component, 9 | image, 10 | style, 11 | height, 12 | width = "100%", 13 | }) => { 14 | const bgStyles = { 15 | position: "absolute", 16 | resizeMode: "repeat", 17 | overflow: "visible", 18 | backfaceVisibility: "visible", 19 | flex: 1, 20 | height: "100%", 21 | width: "100%", 22 | }; 23 | return ( 24 | 30 | {createElement(component) || ( 31 | 35 | )} 36 | 37 | ); 38 | }; 39 | 40 | BigListPlaceholder.propTypes = { 41 | height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 42 | width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 43 | style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), 44 | }; 45 | 46 | BigListPlaceholder.defaultProps = { 47 | width: "100%", 48 | }; 49 | 50 | export default memo(BigListPlaceholder); 51 | -------------------------------------------------------------------------------- /lib/BigListProcessor.js: -------------------------------------------------------------------------------- 1 | import { BigListItemType } from "./BigListItem"; 2 | import BigListItemRecycler from "./BigListItemRecycler"; 3 | import { isNumeric } from "./utils"; 4 | 5 | export default class BigListProcessor { 6 | /** 7 | * Constructor. 8 | * @param {ScrollView} scrollView 9 | * @param {array[]|object|null|undefined} sections 10 | * @param {number|function|null|undefined} headerHeight 11 | * @param {number|function|null|undefined} footerHeight 12 | * @param {number|function|null|undefined} sectionHeaderHeight 13 | * @param {number|function|null|undefined} itemHeight 14 | * @param {number|function|null|undefined} sectionFooterHeight 15 | * @param {number|function|null|undefined} insetTop 16 | * @param {number|function|null|undefined} insetBottom 17 | * @param {number|null|undefined} numColumns 18 | */ 19 | constructor({ 20 | scrollView, 21 | sections, 22 | headerHeight, 23 | footerHeight, 24 | sectionHeaderHeight, 25 | itemHeight, 26 | sectionFooterHeight, 27 | insetTop, 28 | insetBottom, 29 | numColumns, 30 | }) { 31 | this.headerHeight = headerHeight; 32 | this.footerHeight = footerHeight; 33 | this.sectionHeaderHeight = sectionHeaderHeight; 34 | this.itemHeight = itemHeight; 35 | this.sectionFooterHeight = sectionFooterHeight; 36 | this.sections = sections; 37 | this.insetTop = insetTop; 38 | this.insetBottom = insetBottom; 39 | this.uniform = isNumeric(itemHeight); 40 | this.scrollView = scrollView; 41 | this.numColumns = numColumns; 42 | } 43 | 44 | /** 45 | * Get item height. 46 | * @returns {number|*} 47 | */ 48 | getItemHeight(section, index) { 49 | const { itemHeight } = this; 50 | return isNumeric(itemHeight) 51 | ? Number(itemHeight) 52 | : itemHeight(section, index); 53 | } 54 | 55 | /** 56 | * Get header height. 57 | * @returns {number|*} 58 | */ 59 | getHeaderHeight() { 60 | const { headerHeight } = this; 61 | return isNumeric(headerHeight) ? Number(headerHeight) : headerHeight(); 62 | } 63 | 64 | /** 65 | * Get footer height. 66 | * @returns {number|*} 67 | */ 68 | getFooterHeight() { 69 | const { footerHeight } = this; 70 | return isNumeric(footerHeight) ? Number(footerHeight) : footerHeight(); 71 | } 72 | 73 | /** 74 | * Get section height. 75 | * @returns {number|*} 76 | */ 77 | getSectionHeaderHeight(section) { 78 | const { sectionHeaderHeight } = this; 79 | return isNumeric(sectionHeaderHeight) 80 | ? Number(sectionHeaderHeight) 81 | : sectionHeaderHeight(section); 82 | } 83 | 84 | /** 85 | * Get section footer height. 86 | * @returns {number|*} 87 | */ 88 | getSectionFooterHeight(section) { 89 | const { sectionFooterHeight } = this; 90 | return isNumeric(sectionFooterHeight) 91 | ? Number(sectionFooterHeight) 92 | : sectionFooterHeight(section); 93 | } 94 | 95 | /** 96 | * Process list items. 97 | * @param {number} top 98 | * @param {number} bottom 99 | * @param {array} prevItems 100 | * @returns {{items: [], height: *}} 101 | */ 102 | process(top, bottom, prevItems) { 103 | const { sections } = this; 104 | const items = []; 105 | const recycler = new BigListItemRecycler(prevItems); 106 | 107 | let position; 108 | let counter = -1; // Counter of items per row pushed 109 | let height = this.insetTop; 110 | let spacerHeight = height; 111 | 112 | /** 113 | * The width of the row is the entire line. 114 | * @param {object} item 115 | * @returns {boolean} 116 | */ 117 | const isFullRow = (item) => { 118 | // Only items can be rendered with column format, so all others are full row 119 | return item.type !== BigListItemType.ITEM; 120 | }; 121 | 122 | /** 123 | * Is visible below. 124 | * @param {object} item 125 | * @returns {boolean} 126 | */ 127 | const isVisibleBelow = (item) => { 128 | const { height: itemHeight } = item; 129 | counter = -1; 130 | if (height > bottom) { 131 | spacerHeight += itemHeight; 132 | return false; 133 | } else { 134 | return true; 135 | } 136 | }; 137 | 138 | /** 139 | * Is the item visible. 140 | * @param {object} item 141 | * @param {bool} force 142 | * @returns {boolean} 143 | */ 144 | const isVisible = (item, force = false) => { 145 | // Check section headers visibility below 146 | if (item.type === BigListItemType.SECTION_HEADER) { 147 | return isVisibleBelow(item); 148 | } 149 | // Dimensions 150 | const { height: itemHeight } = item; 151 | const fullRow = isFullRow(item); 152 | const prevHeight = height; 153 | // Increase or reset counter 154 | counter = fullRow ? -1 : counter + 1; 155 | if (fullRow || counter % this.numColumns === 0) { 156 | height += itemHeight; 157 | } 158 | // Check if is visible 159 | if (force || (height > top && prevHeight < bottom)) { 160 | return true; 161 | } else { 162 | if (fullRow || counter % this.numColumns === 0) { 163 | spacerHeight += itemHeight; 164 | } 165 | return false; 166 | } 167 | }; 168 | 169 | /** 170 | * Get recycled views and push items. 171 | * @param {object} itemsArray 172 | */ 173 | const push = (...itemsArray) => { 174 | itemsArray.forEach((item) => { 175 | items.push(recycler.get(item)); 176 | }); 177 | }; 178 | 179 | /** 180 | * Push spacer. 181 | * @param {object} item 182 | */ 183 | const pushSpacer = (item) => { 184 | if (spacerHeight > 0) { 185 | push({ 186 | type: BigListItemType.SPACER, 187 | position: item.position - spacerHeight, 188 | height: spacerHeight, 189 | section: item.section, 190 | index: item.index, 191 | }); 192 | spacerHeight = 0; 193 | } 194 | }; 195 | 196 | /** 197 | * Push the item when is visible. 198 | * @param {object} item 199 | * @param {bool} force 200 | */ 201 | const pushItem = (item, force = false) => { 202 | if (isVisible(item, force)) { 203 | pushSpacer(item); 204 | push(item); 205 | } 206 | }; 207 | 208 | /** 209 | * Calculate spacer height. 210 | */ 211 | const getSpacerHeight = () => { 212 | let itemsCounter = -1; 213 | return items.reduce((totalHeight, item, i) => { 214 | if (i !== items.length - 1) { 215 | const fullRow = isFullRow(item); 216 | itemsCounter = fullRow ? 0 : itemsCounter + 1; 217 | if (fullRow || itemsCounter % this.numColumns === 0) { 218 | return totalHeight + item.height; 219 | } 220 | } 221 | return totalHeight; 222 | }, 0); 223 | }; 224 | 225 | // Header 226 | const headerHeight = this.getHeaderHeight(); 227 | if (headerHeight > 0) { 228 | position = height; 229 | pushItem( 230 | { 231 | type: BigListItemType.HEADER, 232 | position: position, 233 | height: headerHeight, 234 | }, 235 | true, 236 | ); 237 | } 238 | // Sections 239 | for (let section = 0; section < sections.length; section++) { 240 | const rows = sections[section]; 241 | if (rows === 0) { 242 | continue; 243 | } 244 | // Section Header 245 | const sectionHeaderHeight = this.getSectionHeaderHeight(section); 246 | position = height; 247 | height += sectionHeaderHeight; 248 | if ( 249 | section > 1 && 250 | items.length > 0 && 251 | items[items.length - 1].type === BigListItemType.SECTION_HEADER 252 | ) { 253 | // Top Spacer 254 | const initialSpacerHeight = getSpacerHeight(); 255 | const prevSection = items[items.length - 1]; 256 | items.splice(0, items.length); 257 | push( 258 | { 259 | type: BigListItemType.HEADER, 260 | position: position, 261 | height: headerHeight, 262 | }, 263 | { 264 | type: BigListItemType.SPACER, 265 | position: 0, 266 | height: initialSpacerHeight - headerHeight, 267 | section: prevSection.section, 268 | index: 0, 269 | }, 270 | prevSection, 271 | ); 272 | } 273 | pushItem({ 274 | type: BigListItemType.SECTION_HEADER, 275 | position: position, 276 | height: sectionHeaderHeight, 277 | section: section, 278 | }); 279 | // Items 280 | let itemHeight = this.getItemHeight(section); 281 | for (let index = 0; index < rows; index++) { 282 | if (!this.uniform) { 283 | itemHeight = this.getItemHeight(section, index); 284 | } 285 | position = height; 286 | pushItem({ 287 | type: BigListItemType.ITEM, 288 | position: position, 289 | height: itemHeight, 290 | section: section, 291 | index: index, 292 | }); 293 | } 294 | // Section Footer 295 | const sectionFooterHeight = this.getSectionFooterHeight(section); 296 | if (sectionFooterHeight > 0) { 297 | position = height; 298 | pushItem({ 299 | type: BigListItemType.SECTION_FOOTER, 300 | position: position, 301 | height: sectionFooterHeight, 302 | section: section, 303 | }); 304 | } 305 | } 306 | // Footer 307 | const footerHeight = this.getFooterHeight(); 308 | if (footerHeight > 0) { 309 | position = height; 310 | pushItem( 311 | { 312 | type: BigListItemType.FOOTER, 313 | position: position, 314 | height: footerHeight, 315 | }, 316 | true, 317 | ); 318 | } 319 | // Bottom Spacer 320 | height += this.insetBottom; 321 | spacerHeight += this.insetBottom; 322 | if (spacerHeight > 0) { 323 | push({ 324 | type: BigListItemType.SPACER, 325 | position: height - spacerHeight, 326 | height: spacerHeight, 327 | section: sections.length, 328 | }); 329 | } 330 | recycler.fill(); 331 | return { 332 | height, 333 | items, 334 | }; 335 | } 336 | 337 | /** 338 | * Scroll to position. 339 | * @param {int} targetSection 340 | * @param {int} targetIndex 341 | * @param {boolean} animated 342 | */ 343 | scrollToPosition(targetSection, targetIndex, animated) { 344 | const { sections, insetTop } = this; 345 | 346 | // Header + inset 347 | let scrollTop = insetTop + this.getHeaderHeight(); 348 | let section = 0; 349 | let foundIndex = false; 350 | while (section <= targetSection) { 351 | const rows = Math.ceil(sections[section] / this.numColumns); 352 | if (rows === 0) { 353 | section += 1; 354 | continue; 355 | } 356 | // Section header 357 | scrollTop += this.getSectionHeaderHeight(section); 358 | 359 | // Items 360 | if (this.uniform) { 361 | const uniformHeight = this.getItemHeight(section); 362 | if (section === targetSection) { 363 | scrollTop += uniformHeight * Math.ceil(targetIndex / this.numColumns); 364 | foundIndex = true; 365 | } else { 366 | scrollTop += uniformHeight * rows; 367 | } 368 | } else { 369 | for (let index = 0; index < rows; index++) { 370 | if ( 371 | section < targetSection || 372 | (section === targetSection && index < targetIndex) 373 | ) { 374 | scrollTop += this.getItemHeight( 375 | section, 376 | Math.ceil(index / this.numColumns), 377 | ); 378 | } else if (section === targetSection && index === targetIndex) { 379 | foundIndex = true; 380 | break; 381 | } 382 | } 383 | } 384 | 385 | // Section footer 386 | if (!foundIndex) { 387 | scrollTop += this.getSectionFooterHeight(section); 388 | } 389 | section += 1; 390 | } 391 | this.scrollView.scrollTo({ 392 | x: 0, 393 | y: Math.max(0, scrollTop - this.getSectionHeaderHeight(targetSection)), 394 | animated, 395 | }); 396 | return true; 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /lib/BigListSection.jsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Animated } from "react-native"; 4 | 5 | import { mergeViewStyle } from "./utils"; 6 | 7 | /** 8 | * List section. 9 | * @param {object|array} style 10 | * @param {number} position 11 | * @param {number} height 12 | * @param {number} nextSectionPosition 13 | * @param {Animated.Value} scrollTopValue 14 | * @param {React.node} children 15 | * @returns {JSX.Element} 16 | * @constructor 17 | */ 18 | const BigListSection = ({ 19 | style, 20 | position, 21 | height, 22 | nextSectionPosition, 23 | scrollTopValue, 24 | children, 25 | }) => { 26 | const inputRange = [-1, 0]; 27 | const outputRange = [0, 0]; 28 | inputRange.push(position); 29 | outputRange.push(0); 30 | const collisionPoint = (nextSectionPosition || 0) - height; 31 | if (collisionPoint >= position) { 32 | inputRange.push(collisionPoint, collisionPoint + 1); 33 | outputRange.push(collisionPoint - position, collisionPoint - position); 34 | } else { 35 | inputRange.push(position + 1); 36 | outputRange.push(1); 37 | } 38 | const translateY = scrollTopValue.interpolate({ 39 | inputRange, 40 | outputRange, 41 | }); 42 | const child = React.Children.only(children); 43 | const fillChildren = 44 | React.isValidElement(child) && 45 | React.cloneElement( 46 | child, 47 | mergeViewStyle(style, { 48 | style: { flex: 1 }, 49 | }), 50 | ); 51 | const viewStyle = [ 52 | { 53 | elevation: 10, 54 | }, 55 | React.isValidElement(child) && child.props.style 56 | ? child.props.style 57 | : style, 58 | { 59 | zIndex: 10, 60 | height: height, 61 | width: "100%", 62 | transform: [{ translateY }], 63 | }, 64 | ]; 65 | return {fillChildren}; 66 | }; 67 | 68 | BigListSection.propTypes = { 69 | children: PropTypes.oneOfType([ 70 | PropTypes.arrayOf(PropTypes.node), 71 | PropTypes.node, 72 | ]), 73 | height: PropTypes.number, 74 | nextSectionPosition: PropTypes.number, 75 | position: PropTypes.number, 76 | scrollTopValue: PropTypes.instanceOf(Animated.Value), 77 | style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), 78 | }; 79 | 80 | export default memo(BigListSection); 81 | -------------------------------------------------------------------------------- /lib/assets/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcocesarato/react-native-big-list/a616eb833d1f3273264160bd2c626c2d669583ad/lib/assets/placeholder.png -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import React, { PureComponent } from "react"; 3 | import { 4 | Animated, 5 | FlatListProps, 6 | ListRenderItemInfo, 7 | ListViewProps, 8 | NativeScrollEvent, 9 | NativeSyntheticEvent, 10 | ScrollView, 11 | ScrollViewProps, 12 | ViewStyle, 13 | } from "react-native"; 14 | 15 | export type BigListRenderItemInfo = ListRenderItemInfo & { 16 | section?: number; 17 | key?: string; 18 | style?: ViewStyle | ViewStyle[]; 19 | }; 20 | 21 | export type BigListRenderItem = ( 22 | info: BigListRenderItemInfo, 23 | ) => React.ReactElement | null; 24 | 25 | export interface BigListProps 26 | extends ScrollViewProps, 27 | Pick< 28 | FlatListProps, 29 | | "ListEmptyComponent" 30 | | "ListFooterComponent" 31 | | "ListFooterComponentStyle" 32 | | "ListHeaderComponent" 33 | | "ListHeaderComponentStyle" 34 | | "getItemLayout" 35 | | "numColumns" 36 | | "keyExtractor" 37 | | "onEndReached" 38 | | "onEndReachedThreshold" 39 | | "onRefresh" 40 | | "onViewableItemsChanged" 41 | | "columnWrapperStyle" 42 | | "refreshing" 43 | | "initialScrollIndex" 44 | | "removeClippedSubviews" 45 | >, 46 | Pick< 47 | ListViewProps, 48 | "renderFooter" | "renderHeader" | "stickySectionHeadersEnabled" 49 | > { 50 | inverted?: boolean | null | undefined; 51 | actionSheetScrollRef?: any | null | undefined; 52 | batchSizeThreshold?: number | null | undefined; 53 | data?: ItemT[]; 54 | placeholder?: boolean; 55 | placeholderImage?: any; 56 | placeholderComponent?: React.ReactNode; 57 | footerHeight?: string | number | (() => number); 58 | headerHeight?: string | number | (() => number); 59 | hideMarginalsOnEmpty?: boolean | null | undefined; 60 | hideHeaderOnEmpty?: boolean | null | undefined; 61 | hideFooterOnEmpty?: boolean | null | undefined; 62 | insetBottom?: number; 63 | insetTop?: number; 64 | itemHeight: 65 | | string 66 | | number 67 | | ((section: number) => number) 68 | | ((section: number, index: number) => number); 69 | onScrollEnd?: (event: NativeSyntheticEvent) => void; 70 | renderAccessory?: (list: React.ReactNode) => React.ReactNode; 71 | renderScrollViewWrapper?: (element: React.ReactNode) => React.ReactNode; 72 | renderEmpty?: () => React.ReactNode | null | undefined; 73 | renderItem: BigListRenderItem | null | undefined; 74 | controlItemRender?: boolean; 75 | renderSectionHeader?: (section: number) => React.ReactNode | null | undefined; 76 | renderSectionFooter?: (section: number) => React.ReactNode | null | undefined; 77 | sectionFooterHeight?: string | number | ((section: number) => number); 78 | sectionHeaderHeight?: string | number | ((section: number) => number); 79 | sections?: ItemT[][] | null | undefined; 80 | stickySectionHeadersEnabled?: boolean; 81 | children?: null | undefined; 82 | nativeOffsetValues?: { x?: Animated.Value, y?: Animated.Value }; 83 | ScrollViewComponent?: React.ComponentType | React.Component; 84 | } 85 | export default class BigList extends PureComponent< 86 | BigListProps 87 | > { 88 | scrollTo({ x = 0, y = 0, animated = true }: { x?: number; y?: number; animated?: boolean }): void; 89 | scrollToTop({ animated = true }: { animated?: boolean }): void; 90 | scrollToEnd({ animated = true }: { animated?: boolean }): void; 91 | scrollToIndex({ index, section = 0, animated = true }: { index: number, section?: number, animated?: boolean }): void; 92 | scrollToItem({ item: ItemT, animated = false }: { item: ItemT, animated?: boolean }): void; 93 | scrollToOffset({ offset, animated = false }: { offset: number, animated?: boolean }): void; 94 | scrollToLocation({ section, index, animated = true }: { section: number, index: number, animated?: boolean }): void; 95 | scrollToSection({ section, animated = true }: { section: number, animated?: boolean }): void; 96 | flashScrollIndicators(): void; 97 | getNativeScrollRef(): ScrollView | null; 98 | getItemOffset({ index, section = 0 }: { index: number; section?: number }): number; 99 | getItem({index, section = 0 }: {index: number, section?: number }): ItemT; 100 | getItems(): ItemT[]; 101 | isVisible({ index, section = 0 }: { index: number, section?: number }): boolean; 102 | isEmpty(): boolean; 103 | } 104 | 105 | type BigListItemProps = { 106 | children?: React.ReactNode[] | React.ReactNode; 107 | height?: number | string; 108 | width?: number | string; 109 | style?: ViewStyle | ViewStyle[]; 110 | }; 111 | 112 | export class BigListItem extends PureComponent {} 113 | 114 | type BigListSectionProps = { 115 | children?: React.ReactNode[] | React.ReactNode; 116 | height?: number; 117 | nextSectionPosition?: number; 118 | position?: number; 119 | initialScrollIndex: string | number | Animated.Value; 120 | }; 121 | 122 | export class BigListSection extends PureComponent {} 123 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import { Animated } from "react-native"; 2 | 3 | import BigList from "./BigList"; 4 | 5 | export default Animated.createAnimatedComponent(BigList); 6 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | /** 4 | * Is numeric. 5 | * @param {any} num 6 | * @returns {boolean} 7 | */ 8 | export const isNumeric = (num) => { 9 | return !isNaN(parseFloat(num)) && isFinite(num); 10 | }; 11 | 12 | /** 13 | * Process block. 14 | * @param {number} containerHeight 15 | * @param {number} scrollTop 16 | * @param {number|null|undefined} batchSizeThreshold 17 | * @returns {{blockStart: number, batchSize: number, blockEnd: number}} 18 | */ 19 | export const processBlock = ({ 20 | containerHeight, 21 | scrollTop, 22 | batchSizeThreshold = 1, 23 | }) => { 24 | if (containerHeight === 0) { 25 | return { 26 | batchSize: 0, 27 | blockStart: 0, 28 | blockEnd: 0, 29 | }; 30 | } 31 | const batchSize = Math.ceil( 32 | containerHeight * Math.max(0.5, batchSizeThreshold), 33 | ); 34 | const blockNumber = Math.ceil(scrollTop / batchSize); 35 | const blockStart = batchSize * blockNumber; 36 | const blockEnd = blockStart + batchSize; 37 | return { batchSize, blockStart, blockEnd }; 38 | }; 39 | 40 | /** 41 | * Autobind context to class methods. 42 | * @param {object} self 43 | * @returns {{}} 44 | */ 45 | export const autobind = (self = {}) => { 46 | const exclude = [ 47 | "componentWillMount", 48 | /UNSAFE_.*/, 49 | "render", 50 | "getSnapshotBeforeUpdate", 51 | "componentDidMount", 52 | "componentWillReceiveProps", 53 | "shouldComponentUpdate", 54 | "componentWillUpdate", 55 | "componentDidUpdate", 56 | "componentWillUnmount", 57 | "componentDidCatch", 58 | "setState", 59 | "forceUpdate", 60 | ]; 61 | 62 | const filter = (key) => { 63 | const match = (pattern) => 64 | typeof pattern === "string" ? key === pattern : pattern.test(key); 65 | if (exclude) { 66 | return !exclude.some(match); 67 | } 68 | return true; 69 | }; 70 | 71 | const getAllProperties = (object) => { 72 | const properties = new Set(); 73 | do { 74 | for (const key of Object.getOwnPropertyNames(object).concat( 75 | Object.getOwnPropertySymbols(object), 76 | )) { 77 | properties.add([object, key]); 78 | } 79 | } while ( 80 | (object = Object.getPrototypeOf(object)) && 81 | object !== Object.prototype 82 | ); 83 | return properties; 84 | }; 85 | 86 | for (const [object, key] of getAllProperties(self.constructor.prototype)) { 87 | if (key === "constructor" || !filter(key)) { 88 | continue; 89 | } 90 | const descriptor = Object.getOwnPropertyDescriptor(object, key); 91 | if (descriptor && typeof descriptor.value === "function") { 92 | self[key] = self[key].bind(self); 93 | } 94 | } 95 | return self; 96 | }; 97 | 98 | /** 99 | * Merge styles 100 | * @param {array|object|null|undefined} style 101 | * @param {array|object} defaultStyle 102 | * @returns {Object} 103 | */ 104 | export const mergeViewStyle = (style, defaultStyle = {}) => { 105 | let mergedStyle = style; 106 | if (mergedStyle == null) { 107 | mergedStyle = defaultStyle; 108 | } else if (Array.isArray(style) && Array.isArray(defaultStyle)) { 109 | const mergedDefaultStyle = [...defaultStyle]; 110 | mergedDefaultStyle.concat(style); 111 | mergedStyle = mergedDefaultStyle; 112 | } else if (Array.isArray(defaultStyle)) { 113 | const mergedDefaultStyle = [...defaultStyle]; 114 | mergedDefaultStyle.push(style); 115 | mergedStyle = mergedDefaultStyle; 116 | } else if (Array.isArray(style)) { 117 | mergedStyle = [...style]; 118 | mergedStyle.unshift(defaultStyle); 119 | } else { 120 | mergedStyle = [defaultStyle, style]; 121 | } 122 | return mergedStyle; 123 | }; 124 | 125 | /** 126 | * Get element from component. 127 | * @param {React.node} Component 128 | * @param props 129 | * @returns {JSX.Element|[]|*} 130 | */ 131 | export const createElement = (Component, props = {}) => { 132 | return Component != null ? ( 133 | React.isValidElement(Component) ? ( 134 | React.cloneElement(Component, props) 135 | ) : ( 136 | 137 | ) 138 | ) : null; 139 | }; 140 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-big-list", 3 | "version": "1.6.1", 4 | "description": "A big and fast list implementation for react-native with a recycler API focused on performance and ram usage while processing thousand items on the list.", 5 | "keywords": [ 6 | "react-native-big-list", 7 | "react", 8 | "react-native", 9 | "javascript", 10 | "ui-lib", 11 | "rn", 12 | "big-list", 13 | "fast-list", 14 | "scroll-list", 15 | "large-list", 16 | "biglist", 17 | "fastlist", 18 | "scrolllist", 19 | "largelist", 20 | "fast", 21 | "scroll", 22 | "large", 23 | "bigdata", 24 | "big", 25 | "massive", 26 | "list", 27 | "performance" 28 | ], 29 | "main": "dist/commonjs/index.js", 30 | "module": "dist/module/index.js", 31 | "types": "lib/index.d.ts", 32 | "files": [ 33 | "lib", 34 | "dist" 35 | ], 36 | "author": "Marco Cesarato ", 37 | "bugs": { 38 | "url": "https://github.com/marcocesarato/react-native-big-list/issues" 39 | }, 40 | "homepage": "https://marcocesarato.github.io/react-native-big-list-docs/", 41 | "license": "Apache-2.0", 42 | "scripts": { 43 | "prepare": "bob build && node scripts/dist.js", 44 | "lint": "eslint --ignore-path .gitignore \"./lib/*.{js,jsx}\"", 45 | "prettify": "prettier --write \"./**/*.{ts,tsx,js,jsx,json,md}\"", 46 | "format": "yarpm run prettify && yarpm run lint --fix", 47 | "release": "standard-version" 48 | }, 49 | "devDependencies": { 50 | "@react-native-community/eslint-config": "^3.2.0", 51 | "eslint": "^8.36.0", 52 | "eslint-config-standard": "^17.0.0", 53 | "eslint-plugin-import": "^2.27.5", 54 | "eslint-plugin-jest": "^27.2.1", 55 | "eslint-plugin-node": "^11.1.0", 56 | "eslint-plugin-prettier": "^4.2.1", 57 | "eslint-plugin-promise": "^6.1.1", 58 | "eslint-plugin-react": "^7.32.2", 59 | "eslint-plugin-react-native": "^4.0.0", 60 | "eslint-plugin-simple-import-sort": "^10.0.0", 61 | "glob": "^9.3.2", 62 | "husky": "^8.0.3", 63 | "lint-staged": "^13.2.0", 64 | "prettier": "^2.8.7", 65 | "react-native-builder-bob": "^0.20.4", 66 | "standard-version": "^9.5.0", 67 | "typescript": "^4.5.2", 68 | "yarpm": "^1.2.0" 69 | }, 70 | "husky": { 71 | "hooks": { 72 | "pre-commit": "lint-staged" 73 | } 74 | }, 75 | "lint-staged": { 76 | "**/*.{ts,tsx,js,jsx}": [ 77 | "eslint --ignore-path .gitignore . --fix" 78 | ], 79 | "**/*.{ts,tsx,js,jsx,json}": [ 80 | "prettier --write ." 81 | ] 82 | }, 83 | "peerDependencies": { 84 | "@types/react": "*", 85 | "@types/react-native": "*", 86 | "react": "*", 87 | "react-native": "*" 88 | }, 89 | "dependencies": { 90 | "prop-types": "^15.8.1" 91 | }, 92 | "bit": { 93 | "env": {}, 94 | "packageManager": "npm" 95 | }, 96 | "react-native-builder-bob": { 97 | "source": "lib", 98 | "output": "dist", 99 | "targets": [ 100 | "commonjs", 101 | "module", 102 | "typescript" 103 | ] 104 | }, 105 | "eslintIgnore": [ 106 | "node_modules/", 107 | "dist/" 108 | ] 109 | } 110 | -------------------------------------------------------------------------------- /scripts/dist.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const glob = require("glob"); 3 | const prettier = require("prettier"); 4 | 5 | function clean(str) { 6 | return prettier.format(str.replace(/\*\/(\r?\n)+/g, "*/\n"), { 7 | parser: "babel", 8 | }); 9 | } 10 | 11 | console.log("Cleaning distribution..."); 12 | 13 | glob(process.cwd() + "/dist/**/*.{js,jsx,ts,tsx}", {}, (error, files) => { 14 | if (error) console.log(error); 15 | files.forEach((file) => { 16 | const contents = fs.readFileSync(file).toString(); 17 | fs.writeFile(file, clean(contents), (e) => { 18 | if (e) console.log(e); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "allowUnusedLabels": false, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "jsx": "react", 8 | "lib": ["esnext"], 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noFallthroughCasesInSwitch": true, 12 | "noImplicitReturns": true, 13 | "noImplicitUseStrict": false, 14 | "noStrictGenericChecks": false, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "resolveJsonModule": true, 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "target": "esnext" 21 | } 22 | } 23 | --------------------------------------------------------------------------------