├── react.png ├── mockData.js └── React Native Workshop.md /react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harisaurus/react-native-workshop/HEAD/react.png -------------------------------------------------------------------------------- /mockData.js: -------------------------------------------------------------------------------- 1 | export const mockData = [ 2 | { 3 | id: 456435643, 4 | time: "8:45 AM", 5 | data: [ 6 | { 7 | id: 1, 8 | title: "Registration & breakfast", 9 | description: "Register and grab some breakfast and coffee.", 10 | time: "8:45 AM", 11 | speaker: null 12 | } 13 | ] 14 | }, 15 | { 16 | id: 1234234235, 17 | time: "9:30 AM", 18 | data: [ 19 | { 20 | id: 2, 21 | title: "Keynote", 22 | description: 23 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius nisl sapien, a ullamcorper felis aliquam nec. Aliquam erat volutpat. Donec eget risus pretium orci fermentum aliquam et pellentesque sem. Maecenas luctus dictum odio, imperdiet pharetra felis accumsan quis. Integer lobortis augue felis. Ut non dui gravida, luctus lectus non, semper lectus.", 24 | time: "9:30 AM", 25 | speaker: { 26 | name: "Haris Mahmood", 27 | role: "Chief Millenial Officer", 28 | avatar: "https://api.adorable.io/avatars/285/harismahmood.png" 29 | } 30 | } 31 | ] 32 | }, 33 | { 34 | id: 56756352, 35 | time: "10:15 AM", 36 | data: [ 37 | { 38 | id: 3, 39 | title: "Introduction to React Native", 40 | description: 41 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius nisl sapien, a ullamcorper felis aliquam nec. Aliquam erat volutpat. Donec eget risus pretium orci fermentum aliquam et pellentesque sem. Maecenas luctus dictum odio, imperdiet pharetra felis accumsan quis. Integer lobortis augue felis. Ut non dui gravida, luctus lectus non, semper lectus.", 42 | time: "10:15 AM", 43 | speaker: { 44 | name: "Ryan Baldwin", 45 | role: "Baker of Bytes", 46 | avatar: "https://api.adorable.io/avatars/285/ryan.png" 47 | } 48 | }, 49 | { 50 | id: 4, 51 | title: "React Native at Scale", 52 | description: 53 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius nisl sapien, a ullamcorper felis aliquam nec. Aliquam erat volutpat. Donec eget risus pretium orci fermentum aliquam et pellentesque sem. Maecenas luctus dictum odio, imperdiet pharetra felis accumsan quis. Integer lobortis augue felis. Ut non dui gravida, luctus lectus non, semper lectus.", 54 | time: "10:15 AM", 55 | speaker: { 56 | name: "Sergey Gavrilyuk", 57 | role: "Hardcore Canadian", 58 | avatar: "https://api.adorable.io/avatars/285/sergey.png" 59 | } 60 | }, 61 | { 62 | id: 5, 63 | title: "Working With Data Using Apollo and GraphQL in React Native", 64 | description: 65 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius nisl sapien, a ullamcorper felis aliquam nec. Aliquam erat volutpat. Donec eget risus pretium orci fermentum aliquam et pellentesque sem. Maecenas luctus dictum odio, imperdiet pharetra felis accumsan quis. Integer lobortis augue felis. Ut non dui gravida, luctus lectus non, semper lectus.", 66 | time: "10:15 AM", 67 | speaker: { 68 | name: "Corey Pollock", 69 | role: "Poor man's Steve Jobs", 70 | avatar: "https://api.adorable.io/avatars/285/corey.png" 71 | } 72 | } 73 | ] 74 | }, 75 | { 76 | id: 3456466, 77 | time: "11:00 AM", 78 | data: [ 79 | { 80 | id: 6, 81 | title: "Styling with the StyleSheet API", 82 | description: 83 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius nisl sapien, a ullamcorper felis aliquam nec. Aliquam erat volutpat. Donec eget risus pretium orci fermentum aliquam et pellentesque sem. Maecenas luctus dictum odio, imperdiet pharetra felis accumsan quis. Integer lobortis augue felis. Ut non dui gravida, luctus lectus non, semper lectus.", 84 | time: "11:00 AM", 85 | speaker: { 86 | name: "Jacob Abraham", 87 | role: "Founder & CEO of Jacob By Jacob", 88 | avatar: "https://api.adorable.io/avatars/285/jacob.png" 89 | } 90 | }, 91 | { 92 | id: 7, 93 | title: "Creating Themes with Restyle", 94 | description: 95 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius nisl sapien, a ullamcorper felis aliquam nec. Aliquam erat volutpat. Donec eget risus pretium orci fermentum aliquam et pellentesque sem. Maecenas luctus dictum odio, imperdiet pharetra felis accumsan quis. Integer lobortis augue felis. Ut non dui gravida, luctus lectus non, semper lectus.", 96 | time: "11:00 AM", 97 | speaker: { 98 | name: "Matt Legaspi", 99 | role: "Figma World Champion", 100 | avatar: "https://api.adorable.io/avatars/285/matt.png" 101 | } 102 | }, 103 | { 104 | id: 8, 105 | title: "Making Apps Accessible in React Native", 106 | description: 107 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius nisl sapien, a ullamcorper felis aliquam nec. Aliquam erat volutpat. Donec eget risus pretium orci fermentum aliquam et pellentesque sem. Maecenas luctus dictum odio, imperdiet pharetra felis accumsan quis. Integer lobortis augue felis. Ut non dui gravida, luctus lectus non, semper lectus.", 108 | time: "11:00 AM", 109 | speaker: { 110 | name: "Mo Hashi", 111 | role: "Director of Business", 112 | avatar: "https://api.adorable.io/avatars/285/mo.png" 113 | } 114 | } 115 | ] 116 | }, 117 | { 118 | id: 456435643, 119 | time: "12:00 PM", 120 | data: [ 121 | { 122 | id: 9, 123 | title: "Lunch break", 124 | description: 125 | "Break time! Grab some lunch and coffee and mingle with fellow humans.", 126 | time: "12:00 PM", 127 | speaker: null 128 | } 129 | ] 130 | }, 131 | { 132 | id: 456435643, 133 | time: "2:00 PM", 134 | data: [ 135 | { 136 | id: 10, 137 | title: "RN Workshop", 138 | description: 139 | "Introductory React Native workshop for those looking to get started.", 140 | time: "2:00 PM - 5:00 PM", 141 | speaker: { 142 | name: "Lloyd Wiredu", 143 | role: "React Native Instagram Influencer", 144 | avatar: "https://api.adorable.io/avatars/285/lloyd.png" 145 | } 146 | } 147 | ] 148 | } 149 | ]; 150 | -------------------------------------------------------------------------------- /React Native Workshop.md: -------------------------------------------------------------------------------- 1 | # React Native Workshop 2 | 3 | # Welcome! 4 | 5 | Hello! And welcome to this React Native workshop! We'll spend the next 2.5 hours taking a look at what React Native is, quickly looking at the tools we'll be using, then diving right into building a project. This is an introductory workshop aimed at helping you familiarize yourselves with React Native. More advanced workshops are in the works and will be offered in the upcoming months. 6 | 7 | Please do ask questions if you have any, but do keep in mind that if its not directly relevant to what we're working on, we may have to hold off chatting about it until the end of the workshop to make sure we get through all the content. 8 | 9 | # What is React? 10 | 11 | React is a JavaScript library for building user interfaces. It allows you to build encapsulated components that manage their own state, then compose them to make complex UIs. React by default renders client-side, but can also render on the server using Node, and power mobile apps using React Native. 12 | 13 | # Quick intro to React Native 14 | 15 | React Native is a library that allows you to create native apps for Android and iOS using React JS. You can use React Native in an existing Android or iOS project, or build an entire app from the ground up using React Native. Today we'll be doing the latter! 16 | 17 | The important thing to remember here is that the app we build will be a truly native app. Other libraries have offered the "use web tech to build apps" ability before, but they embed a webview (think iframe) into a native shell, and render your app within. This leads to poor performance and not a true native experience. 18 | 19 | # Tooling 20 | 21 | ## JavaScript + React 22 | 23 | We'll be writing lots of regular and React-style JavaScript today! 24 | 25 | ## React Native 26 | 27 | Obviously 28 | 29 | ## Expo 30 | 31 | Expo is a framework, or a set of tools and services, that allow you to build, deploy and quickly iterate on React Native apps. The developer experience is super nice. We'll get into this soon. 32 | 33 | # Requirements + Pre-requisites 34 | 35 | ## Xcode 36 | 37 | Please be on the latest version of Xcode. (Xcode 11.X at the time of writing) 38 | 39 | ## Node 40 | 41 | Verify and/or install the latest stable version of Node.js 42 | 43 | 44 | ``` Bash 45 | brew install node 46 | 47 | # or 48 | brew update 49 | brew upgrade node 50 | ``` 51 | 52 | ## Expo CLI 53 | 54 | Next, install the Expo CLI globally by running the following command. You can also run the same command to update your version of the `expo-cli`. 55 | 56 | ``` Bash 57 | npm install -g expo-cli 58 | ``` 59 | 60 | ## Expo Go app 61 | 62 | Install the "Expo Go" app on your iOS or Android device and create an account. Log into your account in your terminal window by running 63 | 64 | 65 | ``` Bash 66 | expo login 67 | ``` 68 | 69 | # JavaScript in 5ish minutes 70 | 71 | Since not everyone here is familiar with JS, lets take a few minutes to go through some fundamental concepts. These will be sufficient to get started, and we'll add more as necessary throughout the workshop. 72 | 73 | ## Variables 74 | 75 | Variables are declared in two ways: either use `const` (for constants), or `let` (for variables that can change over time). 76 | 77 | You can use single, double, and back-ticks to create string literals, only back-ticks can interpolate variables though. 78 | 79 | ``` JavaScript 80 | const name = "Haris"; 81 | 82 | console.log("Hello " + name); 83 | console.log('Hello ' + name); 84 | console.log(`Hello ${name}`); 85 | ``` 86 | 87 | Use square brackets `[]` to create arrays. 88 | 89 | 90 | ``` JavaScript 91 | const exampleArray = [7, 14, 21]; 92 | ``` 93 | 94 | Use curly brackets `{}` to create objects (string indexed dictionaries). 95 | 96 | ``` JavaScript 97 | const exampleObject = { 98 | name: "Haris", 99 | role: "Senior Engineer", 100 | employer: "Shopify", 101 | coworkers: ["Ryan", "Sergey", "Alex"], 102 | somethingElse: { 103 | nested: true 104 | } 105 | } 106 | ``` 107 | 108 | ## Functions 109 | 110 | There are two main ways to create a function: the `function` keyword or the arrow syntax `=>`. Arrow functions can directly return a value from the expression without needing the explicit `return` keyword. 111 | 112 | ``` JavaScript 113 | // with function keyword 114 | function example() { 115 | return "This is fun" 116 | } 117 | 118 | // with arrow syntax 119 | const example = () => { 120 | return "This is fun" 121 | } 122 | 123 | // can be simplified and rewritten as 124 | const examples = () => "This is fun" 125 | 126 | // returned objects must be in `()` 127 | const example = () => { 128 | return ( 129 | {name: "Haris"} 130 | ) 131 | } 132 | 133 | // can be simplified and rewritten as 134 | const example = () => ({name: "Haris"}) 135 | 136 | // with function keyword and 1 param 137 | function double(num) { 138 | return num * 2; 139 | } 140 | 141 | // with arrow syntax 142 | const double = (num) => num * 2; 143 | 144 | // simplify since we only have 1 param 145 | const double = num => num * 2; 146 | ``` 147 | 148 | # Create a New React Native App 149 | 150 | Lets get started by creating a new React Native app using Expo! 151 | 152 | Run `expo init` to create a new project. Expo provides a number of template options to get started with. You'll also notice the templates grouped into to workflow types: Managed and Bare. 153 | 154 | ## Workflow types 155 | 156 | With the managed workflow you only write JavaScript / TypeScript, and Expo tools + services take care of the rest for you. This is similar to "Rails" and "Create React App" for React Native. 157 | 158 | In the bare workflow you have full control over every aspect of the native project, and Expo tools can't help quite as much. 159 | 160 | You can begin a project using the managed workflow, but "eject" into a bare workflow at anytime. However, you *cannot* go in reverse. 161 | 162 | We'll stick with the "blank" template in the managed workflow for this workshop. Continue the setup by entering a project name and slug, and say "yes" to install dependencies using Yarn. 163 | 164 | Note: The Arrive team opted to not use Expo at all. We wanted an absolute bare bones experience to really understand every aspect as we were vetting whether React Native was a viable choice. We were also using a number of native modules that Expo would not support, so we knew off the bat that a managed workflow would be impossible to use without sacrificing features. 165 | 166 | # Running a React Native app 167 | 168 | Navigate into the project directory and start expo. 169 | 170 | ``` Bash 171 | cd [project-name] 172 | expo start 173 | ``` 174 | 175 | This will open the Metro Bundler screen in a new tab in your browser. 176 | 177 | Click the 'Run on iOS simulator' or hit the `i` key in your terminal to boot up an iOS simulator, install the expo client on it and start your app. This will trigger Metro Bundler to build your app and serve it. 178 | 179 | Congratulations on getting your first React Native mobile app up and running! 180 | 181 | ## Expo client app 182 | 183 | You can also follow the instructions in the terminal for getting the app running on your mobile device using the previously installed Expo client. Let's go through this together now to make sure we can all test on our personal devices. 184 | 185 | if you haven't already, log into your Expo account in the terminal using: 186 | 187 | 188 | ``` Bash 189 | expo login 190 | ``` 191 | 192 | App QR codes and links can be sent to anyone to test your app. As long as Expo is running on your machine, anyone can scan the code / add the link to their Expo client and test the app. 193 | 194 | # Basic components 195 | 196 | ## View 197 | 198 | The `View` component is equivalent to `uiview` in iOS, `view` in Android, and a `div` in web. Its one of the simplest and most used components in React Native. It is used to create layout, but can also be used to create basic shapes like squares, rectangles and circles. `View` components are not "visible" as they do not have visual representations. You will often wrap other elements inside `View` components to implement layout and styling. 199 | 200 | In web, we also have other semantic elements, such as `header`, `section`, `footer`, etc. These are all replaced with `View` in React Native. 201 | 202 | [https://facebook.github.io/react-native/docs/view](https://facebook.github.io/react-native/docs/view) 203 | 204 | ## Text 205 | 206 | The `Text` component handles *all* textual elements in React Native. All text must be wrapped in a `Text` component. 207 | 208 | [https://facebook.github.io/react-native/docs/text](https://facebook.github.io/react-native/docs/text) 209 | 210 | ## Image 211 | 212 | The `Image` component allows you to render an image. You tell the component to render a specific image by passing in an image object to the `source` prop. 213 | 214 | [https://facebook.github.io/react-native/docs/image](https://facebook.github.io/react-native/docs/image) 215 | 216 | ## TouchableOpacity 217 | 218 | A wrapper for making views respond properly to touches. On press down, the opacity of the wrapped view is decreased, dimming it. It also takes a value for the `onPress` prop. 219 | 220 | [https://facebook.github.io/react-native/docs/touchableopacity](https://facebook.github.io/react-native/docs/touchableopacity) 221 | 222 | ## ScrollView 223 | 224 | Similar to the `View` component, except that it implements scrolling when its content is too long. The `View` component cannot be scrolled. Scrolling is vertical by default, but can be switched to horizontal by passing in the `horizontal` prop. ScrollViews are not super performant when content is too long and complex. 225 | 226 | [https://facebook.github.io/react-native/docs/scrollview](https://facebook.github.io/react-native/docs/scrollview) 227 | 228 | # Styling 229 | 230 | To style React Native apps, we use css-in-js, essentially writing CSS in a JS format. We'll play around with a bunch of styling over the course of this workshop. Styles are passed to a component through the `style` prop. There are two main ways styles can be applied: inline and by using the `StyleSheet` api. 231 | 232 | ## Inline styles 233 | 234 | Inline styles are applied passing in an object of key-value pairs to the `style` prop directly. 235 | 236 | 237 | ``` JavaScript 238 | This text will be red 239 | ``` 240 | 241 | ## StyleSheet 242 | 243 | Applying all your styles inline can get really messy very quickly. So I recommend you use the `StyleSheet` api provided by React Native. See the following example to understand how its used. 244 | 245 | ``` JavaScript 246 | This text will be red 247 | 248 | const styles = StyleSheet.create({ 249 | example: { 250 | color: 'red' 251 | } 252 | }) 253 | ``` 254 | 255 | The `styles` object will continue to grow as new keys are added to represent various 'classes'. 256 | 257 | [https://facebook.github.io/react-native/docs/stylesheet](https://facebook.github.io/react-native/docs/stylesheet) 258 | 259 | ## Style arrays 260 | 261 | Similar to how you can apply multiple classes to elements in HTML, you can pass in an array of styles into the style prop. 262 | 263 | ``` JavaScript 264 | This text will be red 265 | 266 | const styles = StyleSheet.create({ 267 | one: { 268 | color: 'blue' 269 | }, 270 | two: { 271 | color: 'red' 272 | } 273 | }) 274 | ``` 275 | 276 | ## Layout with Flexbox 277 | 278 | Layout is achieved through Flexbox. CSS Grids, tables and floats are not available. 279 | 280 | Some fundamentals: 281 | 282 | - Flexbox requires there to be a container element (the flex container) and elements inside (the flex children) 283 | - Styles applied to the container tell it how its content will flow. We aren't worried about individually placing elements, rather how all the content will behave/flow inside the flex container. For example, left to right, or top to bottom, spaced out evenly, or all bunched up together. 284 | - The direction of flow for a flex container is defined using the `flexDirection` property. This also defines your primary axis. Your cross axis becomes your secondary axis. The default value for `flexDirection` is `column`, which causes your content to flow from top to bottom. Note: the default value on web is `row`. 285 | - `justifyContent` controls how items are distributed on the primary axis. 286 | - `alignItems` controls how items are distributed on the cross/secondary axis. 287 | 288 | For those new to Flexbox, I recommend you watch a video or two to familiarize yourselves with some of the more complex aspects of Flexbox. There are quite a number of properties and they can get confusing pretty quick. 289 | 290 | # Conference app: Home Screen 291 | 292 | Alright! Enough basics! Let's build our first screen: the Home Screen. 293 | 294 | First, create a new folder in the root directory and name it `screens`. This is where our HomeScreen and other screen files will go. It keeps things neat and tidy. 295 | 296 | Create a new file in the `screens` directory and call it `HomeScreen.js`. 297 | 298 | Setup this file by creating a new functional component named `HomeScreen` , setting up initial imports, and add some temporary text to test things out. 299 | 300 | 301 | ``` JavaScript 302 | import React from "react"; 303 | import {Text, View} from "react-native"; 304 | 305 | const HomeScreen = () => { 306 | return ( 307 | 308 | This is the Home Screen! 309 | 310 | ); 311 | }; 312 | 313 | export default HomeScreen; 314 | ``` 315 | 316 | This is great, but you'll notice that our app doesn't render this component we created. To do so, we need to import this component into our `App.js` file, and `return` it in the `App()` function's `render`. 317 | 318 | ``` JavaScript 319 | import React from "react"; 320 | import { StyleSheet, Text, View } from "react-native"; 321 | import HomeScreen from "./screens/HomeScreen"; 322 | 323 | export default function App() { 324 | return ( 325 | 326 | 327 | 328 | ); 329 | } 330 | 331 | const styles = StyleSheet.create({ 332 | container: { 333 | flex: 1, 334 | backgroundColor: "#fff", 335 | alignItems: "center", 336 | justifyContent: "center" 337 | } 338 | }); 339 | ``` 340 | 341 | Woop woop! 342 | 343 | Alright, let's head back to the Home screen. This screen will consist of the app's title, an image (branding perhaps), some additional text, and a button. Import the necessary components from the React Native library, and add them to the screen. 344 | 345 | 346 | ``` JavaScript 347 | import React from "react"; 348 | import { Text, View, TouchableOpacity, Image } from "react-native"; 349 | 350 | const HomeScreen = () => { 351 | return ( 352 | 353 | 354 | RNCONF 355 | The best React Native conference, powered by Shopify 356 | 357 | 358 | See schedule 359 | 360 | 361 | ); 362 | }; 363 | 364 | export default HomeScreen; 365 | ``` 366 | 367 | Now let's get the image in place. Copy the provided `react.png` file into the `assets` folder. To use this image, first import it (you can give it any name you like). 368 | 369 | ``` JavaScript 370 | import Icon from "../assets/react.png"; 371 | ``` 372 | 373 | Then pass it into the `source` prop for the `Image` component. 374 | 375 | ``` JavaScript 376 | 377 | ``` 378 | 379 | Great! We've now got all our content in place for this screen, but its looking pretty rough. Let's fix it up a bit with some styles. Import `StyleSheet` from `react-native`, create a styles object using the `StyleSheet` API and write out the various styles we'll need for the text elements. Then pass in the appropriate styles to various components using the `style` prop. 380 | 381 | Check out `flatuicolors` for help with picking colors! [https://flatuicolors.com/](https://flatuicolors.com/) 382 | 383 | ``` JavaScript 384 | const HomeScreen = () => { 385 | return ( 386 | 387 | 388 | RNCONF 389 | 390 | The best React Native conference, powered by Shopify 391 | 392 | 393 | 394 | See schedule 395 | 396 | 397 | ); 398 | }; 399 | 400 | const styles = StyleSheet.create({ 401 | container: { 402 | flex: 1, 403 | backgroundColor: "#fff", 404 | alignItems: "center", 405 | justifyContent: "center", 406 | paddingHorizontal: 24 407 | }, 408 | image: { 409 | width: 70, 410 | height: 70, 411 | marginBottom: 8 412 | }, 413 | appName: { 414 | fontSize: 60, 415 | fontWeight: "700", 416 | color: "#222f3e" 417 | }, 418 | description: { 419 | paddingHorizontal: 48, 420 | textAlign: "center", 421 | marginBottom: 48, 422 | color: "#576574" 423 | }, 424 | button: { 425 | backgroundColor: "#5f27cd", 426 | paddingHorizontal: 16, 427 | paddingVertical: 8, 428 | borderRadius: 4 429 | }, 430 | buttonText: { 431 | color: "white" 432 | } 433 | }); 434 | ``` 435 | 436 | We won't cover it in this workshop, but if you're interested in setting up a custom font: [https://docs.expo.io/versions/latest/guides/using-custom-fonts/](https://docs.expo.io/versions/latest/guides/using-custom-fonts/) 437 | 438 | Excellent! We've pretty much completed our first screen! Let's move on to the second. 439 | 440 | # Conference app: Detail Screen 441 | 442 | The details screen will show details for a specific talk during the conference. It will show the talk's title, time, description, and show the name, photo and job title of the speaker. 443 | 444 | Let's create a new file in the `screens` folder and call it `DetailsScreen.js`. 445 | 446 | Then setup the functional component and the default export, along with some imports we know we're going to use (View and Text components) 447 | 448 | 449 | ``` JavaScript 450 | import React from "react"; 451 | import { View, Text } from "react-native"; 452 | 453 | const DetailsScreen = () => { 454 | return ( 455 | 456 | Details Screen 457 | 458 | ); 459 | }; 460 | 461 | export default DetailsScreen; 462 | ``` 463 | 464 | To preview this screen, import it in `App.js` and render it instead of the `HomeScreen`. 465 | 466 | Use this moment to also simplify the container styles as we no longer need most of them at the App level, since we're applying styles to individual screens directly. 467 | 468 | ``` JavaScript 469 | import React from "react"; 470 | import { StyleSheet, View } from "react-native"; 471 | import HomeScreen from "./screens/HomeScreen"; 472 | import DetailsScreen from "./screens/DetailsScreen"; 473 | 474 | export default function App() { 475 | return ( 476 | 477 | 478 | 479 | ); 480 | } 481 | 482 | const styles = StyleSheet.create({ 483 | container: { 484 | flex: 1 485 | } 486 | }); 487 | ``` 488 | 489 | Back in the `Details` screen, add the necessary structure and content. Add the various `Text` components needed for the title, date & time, description, speaker name, job title, and an `Image` for the speaker's headshot. You should have something that looks similar to this. 490 | 491 | 492 | ``` JavaScript 493 | import React from "react"; 494 | import { View, Text, Image } from "react-native"; 495 | 496 | const DetailsScreen = () => { 497 | return ( 498 | 499 | 500 | Talk title 501 | Date & time 502 | Talk description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius 503 | nisl sapien, a ullamcorper felis aliquam nec. Aliquam erat volutpat. 504 | Donec eget risus pretium orci fermentum aliquam et pellentesque sem. 505 | Maecenas luctus dictum odio, imperdiet pharetra felis accumsan quis. 506 | Integer lobortis augue felis. Ut non dui gravida, luctus lectus non, 507 | semper lectus. 508 | 509 | 510 | 511 | 512 | Speaker name 513 | Job title 514 | 515 | 516 | 517 | ); 518 | }; 519 | 520 | export default DetailsScreen; 521 | ``` 522 | 523 | Excellent, now let'ss work in some styles. 524 | 525 | 526 | ``` JavaScript 527 | const DetailsScreen = () => { 528 | return ( 529 | 530 | 531 | Talk title 532 | Date & time 533 | 534 | Talk description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius 535 | nisl sapien, a ullamcorper felis aliquam nec. Aliquam erat volutpat. 536 | Donec eget risus pretium orci fermentum aliquam et pellentesque sem. 537 | Maecenas luctus dictum odio, imperdiet pharetra felis accumsan quis. 538 | Integer lobortis augue felis. Ut non dui gravida, luctus lectus non, 539 | semper lectus. 540 | 541 | 542 | 543 | 547 | 548 | Speaker name 549 | Job title 550 | 551 | 552 | 553 | ); 554 | }; 555 | 556 | const styles = StyleSheet.create({ 557 | container: { 558 | flex: 1 559 | }, 560 | talkDetails: { 561 | paddingVertical: 48, 562 | paddingHorizontal: 24, 563 | borderBottomWidth: StyleSheet.hairlineWidth, 564 | borderBottomColor: "#ccc" 565 | }, 566 | title: { 567 | fontSize: 36, 568 | fontWeight: "700" 569 | }, 570 | time: { 571 | fontWeight: "700", 572 | marginTop: 16 573 | }, 574 | description: { 575 | marginTop: 8 576 | }, 577 | speaker: { 578 | padding: 16, 579 | borderRadius: 8, 580 | shadowOffset: { width: 0, height: 15 }, 581 | shadowOpacity: 0.25, 582 | shadowRadius: 28, 583 | elevation: 3, 584 | shadowColor: "#000", 585 | flexDirection: "row", 586 | backgroundColor: "#fff", 587 | marginTop: 24, 588 | marginHorizontal: 24, 589 | alignItems: "center" 590 | }, 591 | avatar: { 592 | height: 80, 593 | width: 80, 594 | borderRadius: 80, 595 | marginRight: 16 596 | }, 597 | speakerName: { 598 | fontSize: 16, 599 | fontWeight: "700", 600 | marginBottom: 4 601 | }, 602 | speakerRole: { 603 | fontSize: 11 604 | } 605 | }); 606 | ``` 607 | 608 | Dimensions are normalized to logical units. So a thickness of `1` can look different (will look like 3 units wide on a super retina display), so we can use `StyleSheet.hairlineWidth` instead. 609 | 610 | And we're done! On to the next screen! 611 | 612 | # Conference app: Schedule Screen 613 | 614 | The schedule screen will show a list of all the talks during the conference. It'll list the talk's name, and when it starts. 615 | 616 | ## New components: `FlatList` and `SectionList` 617 | 618 | Before diving in, let's cover two new components React Native provides: `FlatList` and `SectionList`. These two components are built on top of `ScrollView`, so they have all the same benefits. They also have further optimizations to handle super long lists. They do this primarily by avoiding rendering things that are out of view. 619 | 620 | Consider a grocery list app. If you wanted to show a list of all items without any grouping, you'd utilize a `FlatList`. Now let's say you wanted to show that same list but organized in different categories, or sections: Fruits, Vegetables, Cleaning, Misc. Here, you'd use a `SectionList` instead. 621 | 622 | Back to the conference app: We could render a list that shows all the talk names and times in one giant list, but it'd make more sense to organize it by time. So, we'll be using a `SectionList` with the talk times representing the section headings/titles. 623 | 624 | ## Building the `SectionList` 625 | 626 | Create a new file in the `screens` directory and call it `ScheduleScreen.js` and add some basic setup. Adjust the `App.js` screen to render the `ScheduleScreen` component instead. (Similar to what we did for the Details Screen work). 627 | 628 | 629 | ``` JavaScript 630 | // ScheduleScreen.js 631 | 632 | import React from "react"; 633 | import { View, Text } from "react-native"; 634 | 635 | const ScheduleScreen = () => { 636 | return ( 637 | 638 | Schedule Screen 639 | 640 | ); 641 | }; 642 | 643 | export default ScheduleScreen; 644 | 645 | // App.js 646 | 647 | import React from "react"; 648 | import { StyleSheet, View } from "react-native"; 649 | import HomeScreen from "./screens/HomeScreen"; 650 | import DetailsScreen from "./screens/DetailsScreen"; 651 | import ScheduleScreen from "./screens/ScheduleScreen"; 652 | 653 | export default function App() { 654 | return ( 655 | 656 | 657 | 658 | ); 659 | } 660 | 661 | const styles = StyleSheet.create({ 662 | container: { 663 | flex: 1 664 | } 665 | }); 666 | ``` 667 | 668 | Import the `SectionList` component from `react-native` and render one in the `ScheduleScreen` component. 669 | 670 | 671 | ``` JavaScript 672 | import React from "react"; 673 | import { View, Text, SectionList } from "react-native"; 674 | 675 | const ScheduleScreen = () => { 676 | return ( 677 | 678 | Schedule Screen 679 | 680 | 681 | ); 682 | }; 683 | 684 | export default ScheduleScreen; 685 | ``` 686 | 687 | ## Required props: `sections` and `renderItem` 688 | 689 | A `SectionList` has two required props: `sections` and `renderItem`. `sections` expects an array of data, broken up by sections. `renderItem` expects a function that will render **every** item in **every** section. 690 | 691 | Our mock data is available in the provided `mockdata.js` file. Add this file to the root of your project directory. We can now import the `mockData` array from `mockData.js` and pass it to the `SectionList`'s `sections` prop. 692 | 693 | 694 | ``` JavaScript 695 | import React from "react"; 696 | import { View, Text, SectionList } from "react-native"; 697 | import { mockData } from "../mockData"; 698 | 699 | const ScheduleScreen = () => { 700 | return ( 701 | 702 | Schedule Screen 703 | 704 | 705 | ); 706 | }; 707 | 708 | export default ScheduleScreen; 709 | ``` 710 | 711 | Now create a function responsible for rendering each item in each section. This function will receive a bunch of data from the `SectionList`. Include that as an expected parameter. 712 | 713 | 714 | ``` JavaScript 715 | const singleItem = data => { 716 | return ( 717 | 718 | Single Item 719 | 720 | ); 721 | }; 722 | 723 | const ScheduleScreen = () => { 724 | return ( 725 | 726 | Schedule Screen 727 | 728 | 729 | ); 730 | }; 731 | ``` 732 | 733 | Excellent! Now use the received `data` to pull out actual information rather than rendering "Single Item" every time. 734 | 735 | 736 | ``` JavaScript 737 | const singleItem = data => { 738 | const item = data.item; 739 | 740 | return ( 741 | 742 | {item.title} 743 | 744 | ); 745 | }; 746 | ``` 747 | 748 | ## Optional props 749 | 750 | A `SectionList` has a number of optional props. 751 | 752 | ### keyExtractor 753 | 754 | `keyExtractor` expects a function, which is used to extract a unique key for a given item. The Key is used for caching and to keep track of re-ordering. 755 | 756 | ``` JavaScript 757 | .. 758 | 759 | const keyExtractor = item => item.id; 760 | 761 | const ScheduleScreen = () => { 762 | return ( 763 | 764 | Schedule Screen 765 | 770 | 771 | ); 772 | }; 773 | ``` 774 | 775 | ### ItemSeparatorComponent 776 | 777 | Rendered between each item, but not at the top or bottom. Import `StyleSheet` from `react-native`, create a new styles object, add some divider styles, and create a divider component. 778 | 779 | ``` JavaScript 780 | .. 781 | import { View, Text, SectionList, StyleSheet } from "react-native"; 782 | .. 783 | 784 | const divider = () => { 785 | return ; 786 | }; 787 | 788 | const ScheduleScreen = () => { 789 | return ( 790 | 791 | Schedule Screen 792 | 798 | 799 | ); 800 | }; 801 | 802 | const styles = StyleSheet.create({ 803 | divider: { 804 | width: "100%", 805 | height: 2, 806 | backgroundColor: "#eee" 807 | } 808 | }); 809 | ``` 810 | 811 | ### renderSectionHeader 812 | 813 | Rendered at the top of each section. These stick to the top of the `ScrollView` by default on iOS. Explore the `stickySectionHeadersEnabled` prop to change this behavior. 814 | 815 | Add some styles to separate these headers out from the other items. 816 | 817 | ``` JavaScript 818 | .. 819 | 820 | const sectionHeader = data => { 821 | const section = data.section; 822 | return ( 823 | 824 | {section.time} 825 | 826 | ); 827 | }; 828 | 829 | const ScheduleScreen = () => { 830 | return ( 831 | 832 | Schedule Screen 833 | 840 | 841 | ); 842 | }; 843 | 844 | const styles = StyleSheet.create({ 845 | divider: { .. }, 846 | sectionHeader: { 847 | backgroundColor: "#5f27cd", 848 | paddingHorizontal: 16, 849 | paddingVertical: 12 850 | }, 851 | sectionHeaderText: { 852 | fontWeight: "700", 853 | color: "white" 854 | }, 855 | }); 856 | 857 | .. 858 | ``` 859 | 860 | ### ListHeaderComponent 861 | 862 | Rendered at the very beginning of the list. Add some styles here too. 863 | 864 | ``` JavaScript 865 | .. 866 | 867 | const listHeader = () => { 868 | return ( 869 | 870 | Schedule 871 | 872 | ); 873 | }; 874 | 875 | 876 | const ScheduleScreen = () => { 877 | return ( 878 | 879 | Schedule Screen 880 | 888 | 889 | ); 890 | }; 891 | 892 | const styles = StyleSheet.create({ 893 | divider: { .. }, 894 | sectionHeader: { .. }, 895 | sectionHeaderText: { .. }, 896 | listHeader: { 897 | backgroundColor: "#ccc", 898 | paddingVertical: 32, 899 | paddingHorizontal: 20, 900 | justifyContent: "center", 901 | alignItems: "center" 902 | }, 903 | listHeaderText: { 904 | fontSize: 24, 905 | fontWeight: "600" 906 | } 907 | }); 908 | 909 | .. 910 | ``` 911 | 912 | Lastly, add styles for the individual talk items, add container styles, and remove the temporary `Text` component we originally started with. 913 | 914 | ``` JavaScript 915 | const singleItem = data => { 916 | 917 | const item = data.item; 918 | return ( 919 | 920 | {item.title} 921 | 922 | ); 923 | }; 924 | 925 | .. 926 | 927 | const ScheduleScreen = () => { 928 | return ( 929 | 930 | 938 | 939 | ); 940 | }; 941 | 942 | .. 943 | 944 | const styles = StyleSheet.create({ 945 | divider: { .. }, 946 | sectionHeader: { .. }, 947 | sectionHeaderText: { .. }, 948 | listHeader: { .. }, 949 | listHeaderText: { .. }, 950 | singleItem: { 951 | paddingHorizontal: 16, 952 | paddingVertical: 20 953 | }, 954 | container: { 955 | flex: 1, 956 | backgroundColor: "#fff" 957 | }, 958 | }); 959 | ``` 960 | 961 | ### Final code 962 | 963 | Your final code for `ScheduleScreen` should something like this: 964 | 965 | ``` JavaScript 966 | import React from "react"; 967 | import { View, Text, SectionList, StyleSheet } from "react-native"; 968 | import { mockData } from "../mockData"; 969 | 970 | const singleItem = data => { 971 | const item = data.item; 972 | 973 | return ( 974 | 975 | {item.title} 976 | 977 | ); 978 | }; 979 | 980 | const keyExtractor = item => item.id; 981 | 982 | const divider = () => { 983 | return ; 984 | }; 985 | 986 | const sectionHeader = data => { 987 | const section = data.section; 988 | return ( 989 | 990 | {section.time} 991 | 992 | ); 993 | }; 994 | 995 | const listHeader = () => { 996 | return ( 997 | 998 | Schedule 999 | 1000 | ); 1001 | }; 1002 | 1003 | const ScheduleScreen = () => { 1004 | return ( 1005 | 1006 | 1014 | 1015 | ); 1016 | }; 1017 | 1018 | const styles = StyleSheet.create({ 1019 | container: { 1020 | flex: 1, 1021 | backgroundColor: "#fff" 1022 | }, 1023 | divider: { 1024 | width: "100%", 1025 | height: 2, 1026 | backgroundColor: "#eee" 1027 | }, 1028 | sectionHeader: { 1029 | backgroundColor: "#5f27cd", 1030 | paddingHorizontal: 16, 1031 | paddingVertical: 12 1032 | }, 1033 | sectionHeaderText: { 1034 | fontWeight: "700", 1035 | color: "white" 1036 | }, 1037 | listHeader: { 1038 | backgroundColor: "#ccc", 1039 | paddingVertical: 32, 1040 | paddingHorizontal: 20, 1041 | justifyContent: "center", 1042 | alignItems: "center" 1043 | }, 1044 | listHeaderText: { 1045 | fontSize: 24, 1046 | fontWeight: "600" 1047 | }, 1048 | singleItem: { 1049 | paddingHorizontal: 16, 1050 | paddingVertical: 20 1051 | } 1052 | }); 1053 | 1054 | export default ScheduleScreen; 1055 | ``` 1056 | 1057 | # Conference app: Navigation 1058 | 1059 | Navigating on the web consists of a concept we can call the "history stack". When you navigate to a new place it pushes an item to the stack. Pressing the back button pops an item from the stack. This is how navigation history is maintained. A web browser tab will consist of only one stack. 1060 | 1061 | Mobile navigation is a little different. An app can have multiple navigation groups and types. We'll chat more about this shortly. 1062 | 1063 | The most commonly used library for navigation is `react-navigation`. 1064 | 1065 | To install `react-navigation` into our project, run the following command in terminal: 1066 | 1067 | ``` Bash 1068 | yarn add @react-navigation/native 1069 | ``` 1070 | 1071 | The `react-navigation` library depends on a number of additional libraries. Install expo-supported versions of these libraries by running the following. 1072 | 1073 | ``` Bash 1074 | expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view 1075 | ``` 1076 | 1077 | ## Navigator types 1078 | 1079 | ### Stack Navigator 1080 | 1081 | Provides a way for your app to transition between screens where each new screen is placed on top of the stack. 1082 | 1083 | ### Drawer Navigator 1084 | 1085 | Creates a drawer that you can toggle in and out. 1086 | 1087 | ### Bottom Tab Navigator 1088 | 1089 | Creates a series of tabs to allow navigating between various screens. 1090 | 1091 | The stack navigator is the most common way to navigate between screens. Let's add the necessary libraries to create a stack navigator. 1092 | 1093 | ``` Bash 1094 | yarn add @react-navigation/stack 1095 | ``` 1096 | 1097 | Before moving forward, restart Expo to pickup these newly available libraries with `control + c` and `expo start`. 1098 | 1099 | Back in `App.js` , import `NavigationContainer` from `@react-navigation/native` and `createStackNavigator` from `@react-navigation/stack`. 1100 | 1101 | ``` JavaScript 1102 | import { NavigationContainer } from '@react-navigation/native'; 1103 | import { createStackNavigator } from '@react-navigation/stack'; 1104 | ``` 1105 | 1106 | `NavigationContainer` is a component which manages our navigation tree and contains the navigation state. It must wrap all navigators strucutre. Generally, we'll render this component at the root of our app. 1107 | 1108 | ## Creating a navigator 1109 | Create a stack navigator using `createStackNavigator`. `createStackNavigator` is a function that returns an object containing `Screen` and `Navigator`. Both are used for configuring the navigator as seen below. Add it right under the imports from the previous step. 1110 | 1111 | ``` JavaScript 1112 | const Stack = createStackNavigator(); 1113 | ``` 1114 | Next, remove everything in the return block of `App.js` and replace it with the following. 1115 | ``` JavaScript 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | ``` 1122 | The casing of the route name doesn't matter -- you can use lowercase home or capitalized Home, it's up to you. We prefer capitalizing our route names. 1123 | 1124 | Run this code. You'll now see a screen with a navigation bar and a content area that displays our `HomeScreen` component! The styles you see for the navigation bar are the default configuration for a stack navigator, we'll learn how to configure and adjust those later. 1125 | 1126 | Let's add additional routes for our `ScheduleScreen` and `DetailsScreen`. 1127 | 1128 | ``` JavaScript 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | ``` 1137 | 1138 | Running this code shows no changes. You might be asking yourself: "How does the navigator know to start from the `HomeScreen`?". Well, the first item provided in the route configuration object is always set as the starting screen unless you explicitly state the initial route in the optional options object. Play around with this to validate. 1139 | 1140 | ``` JavaScript 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | 1147 | 1148 | ``` 1149 | 1150 | ## Moving between screens 1151 | 1152 | The next obvious question is: "How do I go from one route to another?". 1153 | 1154 | The steps we took previously now provides a `navigation` property to every route component; in our case that's `HomeScreen`, `ScheduleScreen` and `DetailsScreen`. 1155 | 1156 | The `HomeScreen` component currently does not use any data from passed in props. We now want to utilize the `navigation` prop. First, lets expect `props` to be passed in to the `HomeScreen` component. Use `console.log()` to verify what `props` is. 1157 | 1158 | ``` JavaScript 1159 | const HomeScreen = (props) => { 1160 | .. 1161 | ``` 1162 | 1163 | `navigate` is an available function on the `props.navigation` object. We can call this with the name of the route that we'd like to move the user to. 1164 | 1165 | Update the `TouchableOpacity` button to include the `onPress` prop and pass in an anonymous function which navigates the user to the "Schedule" screen. 1166 | 1167 | ``` JavaScript 1168 | // Step 1: add `onPress` prop 1169 | 1170 | 1174 | 1175 | // Step 2: pass in anonymous function to onPress prop 1176 | 1177 | {}} 1180 | > 1181 | 1182 | // Step 3: use navigate function 1183 | 1184 | { 1187 | props.navigation.navigate("Schedule"); 1188 | }} 1189 | > 1190 | ``` 1191 | 1192 | ## Navigating to the Details Screen 1193 | 1194 | Navigating from the `ScheduleScreen` to the `DetailsScreen` will require some adjustments. First, update the `ScheduleScreen` component to accomodate props. 1195 | 1196 | ``` JavaScript 1197 | const ScheduleScreen = props => { 1198 | ``` 1199 | 1200 | Next, update the `singleItem` component to use `TouchableOpacity` since we want to navigate on press. Remember to import `TouchableOpacity` from `react-native`. 1201 | 1202 | 1203 | ``` JavaScript 1204 | const singleItem = data => { 1205 | const item = data.item; 1206 | 1207 | return ( 1208 | {}}> 1209 | 1210 | {item.title} 1211 | 1212 | 1213 | ); 1214 | }; 1215 | ``` 1216 | 1217 | The `onPress` prop's function will need to call the `navigate` function on the `navigation` object, but there is a problem with our current setup. The `navigation` object is only available in the `ScheduleScreen` component's props, but we need access to it in the `singleItem` component's scope. 1218 | 1219 | The simplest solution here is to move `singleItem` into the `ScheduleScreen` function before the `return`. `singleItem` now has access to `ScheduleScreen`'s scope. There is a small performance penalty for doing it this way, but its negligible for our needs and time constraint. 1220 | 1221 | Update the `onPress` callback now to navigate to the "Details" screen. 1222 | 1223 | 1224 | ``` JavaScript 1225 | const ScheduleScreen = props => { 1226 | const singleItem = data => { 1227 | const item = data.item; 1228 | 1229 | return ( 1230 | props.navigation.navigate("Details")}> 1231 | 1232 | {item.title} 1233 | 1234 | 1235 | ); 1236 | }; 1237 | 1238 | return ( .. ) 1239 | } 1240 | ``` 1241 | 1242 | Navigation between all three screens is complete! 1243 | 1244 | # Dynamic Details Screen 1245 | 1246 | Even though navigation between the `ScheduleScreen` and the `DetailsScreen` exists, one major problem persists. The `DetailsScreen` shows hard-coded dummy data. To address this, first head to the `singleItem` function in `ScheduleScreen.js`. 1247 | 1248 | The `TouchableOpacity` `onPress` prop allows us to navigate to the "Details" screen. The `navigate` function accepts a second, optional, parameter to pass data from one screen to another. Pass in the `item` as `talkData` in this optional parameter. 1249 | 1250 | ``` JavaScript 1251 | props.navigation.navigate("Details", { talkData: item })} 1253 | > 1254 | ``` 1255 | 1256 | Now in `DetailsScreen.js`, first update `DetailsScreen` to expect `({ route })`. Optional data passed into `navigate` is available under `route.params`. Pull this object out into a variable for easy reference. 1257 | 1258 | ``` JavaScript 1259 | const DetailScreen = ({ route }) => { 1260 | const talkData = route.params.talkData; 1261 | 1262 | .. 1263 | ``` 1264 | 1265 | Update the title, time, and description values with values from `talkData`. 1266 | 1267 | ``` JavaScript 1268 | {talkData.title} 1269 | {talkData.time} 1270 | {talkData.description} 1271 | ``` 1272 | 1273 | The speaker section is a little trickier since not every talk has a speaker (breakfast and lunch breaks). The `speaker` value in those scenarios is `null`, which we can check for and render speaker data conditionally. 1274 | 1275 | ``` JavaScript 1276 | {talkData.speaker && ( 1277 | 1278 | 1282 | 1283 | Speaker name 1284 | Job title 1285 | 1286 | 1287 | )} 1288 | ``` 1289 | 1290 | And finally, update the speaker values. The final code for the `DetailsScreen` should look like the following: 1291 | 1292 | ``` JavaScript 1293 | const DetailsScreen = ({ route }) => { 1294 | const talkData = route.params.talkData; 1295 | 1296 | return ( 1297 | 1298 | 1299 | {talkData.title} 1300 | {talkData.time} 1301 | {talkData.description} 1302 | 1303 | {talkData.speaker && ( 1304 | 1305 | 1309 | 1310 | {talkData.speaker.name} 1311 | {talkData.speaker.role} 1312 | 1313 | 1314 | )} 1315 | 1316 | ); 1317 | }; 1318 | ``` 1319 | 1320 | # Navigation polish 1321 | 1322 | The Stack Navigator creates the header on every screen in the stack. A navigation options object exists for every screen in the stack where modifications and overrides can be provided on a screen-by-screen basis. 1323 | 1324 | The `title` key enables changing the header title. 1325 | 1326 | ``` JavaScript 1327 | 1334 | ``` 1335 | 1336 | ## Home Screen 1337 | 1338 | The "Home" screen doesn't really need the header, so let's completely hide it by using the `headerShown` flag. 1339 | 1340 | ``` JavaScript 1341 | 1349 | ``` 1350 | 1351 | ## Schedule Screen 1352 | 1353 | The "Schedule" screen shows the back button to navigate back to the Home screen. This isn't necessary. Hide the left header controls passing the `headerLeft` key a function that returns `null`. This doesn't completely fix the issue since you can still swipe left or tap the android back button to navigate back. The `gestureEnabled` key takes care of this. 1354 | 1355 | ``` JavaScript 1356 | null, 1361 | gestureEnabled: false, 1362 | }} 1363 | /> 1364 | ``` 1365 | 1366 | # All done! 1367 | 1368 | And that's a wrap! You now have a fully functioning React Native app with navigation and a super cool SectionList. What we've covered today are important fundamentals that you'll need to continue your React Native adventure. 1369 | 1370 | I aim to create and offer future workshops that cover intermediate and advanced concepts (data handling, state management, etc). If you're interested, do let me know and keep an eye on the #react-native Slack channel. 1371 | 1372 | I'll be sending a tiny survey out after this to collect your feedback. It helps refine these workshops to make them more efficient and useful for you all. 1373 | 1374 | Once again, thanks! <3 1375 | --------------------------------------------------------------------------------