From 8fb93947c1fd1ff21795200e66fcc96f98098016 Mon Sep 17 00:00:00 2001 From: Eric Nakagawa Date: Tue, 4 Oct 2016 11:56:01 -0700 Subject: [PATCH 01/12] Tutorial ported from @spicyj's internal wiki to top-level navigation option --- docs/_config.yml | 6 + docs/_data/nav_docs.yml | 86 +++++- docs/_data/nav_tutorial.yml | 52 ++++ docs/_includes/nav_tutorial.html | 24 ++ docs/_layouts/default.html | 1 + docs/_layouts/tutorial.html | 29 ++ docs/_plugins/sidebar_item.rb | 4 + docs/img/tutorial/devtools.png | Bin 0 -> 51403 bytes docs/tutorial/tutorial.md | 460 +++++++++++++++++++++++++++++++ 9 files changed, 655 insertions(+), 7 deletions(-) create mode 100644 docs/_data/nav_tutorial.yml create mode 100644 docs/_includes/nav_tutorial.html create mode 100644 docs/_layouts/tutorial.html create mode 100644 docs/img/tutorial/devtools.png create mode 100644 docs/tutorial/tutorial.md diff --git a/docs/_config.yml b/docs/_config.yml index 3fb15b18fa804..f11a1a2d601d8 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -20,6 +20,12 @@ defaults: type: pages values: sectionid: blog +- scope: + path: tutorial + type: pages + values: + layout: tutorial + sectionid: tutorial - scope: path: docs type: pages diff --git a/docs/_data/nav_docs.yml b/docs/_data/nav_docs.yml index 58ea582851042..7a0ebe2286207 100644 --- a/docs/_data/nav_docs.yml +++ b/docs/_data/nav_docs.yml @@ -1,9 +1,81 @@ -- title: Tutorial -- title: Basic Guides +- title: Quick Start items: - - id: installation - title: Installation - - id: hello-world - title: Hello World -- title: Advanced Guides + - id: getting-started + title: Getting Started + - id: thinking-in-react + title: Thinking in React +- title: Community Resources + items: + - id: conferences + title: Conferences + - id: videos + title: Videos + - id: complementary-tools + title: Complementary Tools + href: https://github.com/facebook/react/wiki/Complementary-Tools + - id: examples + title: Examples + href: https://github.com/facebook/react/wiki/Examples +- title: Guides + items: + - id: why-react + title: Why React? + - id: displaying-data + title: Displaying Data + subitems: + - id: jsx-in-depth + title: JSX in Depth + - id: jsx-spread + title: JSX Spread Attributes + - id: jsx-gotchas + title: JSX Gotchas + - id: interactivity-and-dynamic-uis + title: Interactivity and Dynamic UIs + - id: multiple-components + title: Multiple Components + - id: reusable-components + title: Reusable Components + - id: transferring-props + title: Transferring Props + - id: forms + title: Forms + - id: working-with-the-browser + title: Working With the Browser + subitems: + - id: more-about-refs + title: Refs to Components + - id: tooling-integration + title: Tooling Integration + subitems: + - id: language-tooling + title: Language Tooling + - id: package-management + title: Package Management + - id: environments + title: Server-side Environments + - id: addons + title: Add-Ons + subitems: + - id: animation + title: Animation + - id: two-way-binding-helpers + title: Two-Way Binding Helpers + - id: test-utils + title: Test Utilities + - id: clone-with-props + title: Cloning Elements + - id: create-fragment + title: Keyed Fragments + - id: update + title: Immutability Helpers + - id: pure-render-mixin + title: PureRenderMixin + - id: perf + title: Performance Tools + - id: shallow-compare + title: Shallow Compare + - id: advanced-performance + title: Advanced Performance + - id: context + title: Context - title: Reference diff --git a/docs/_data/nav_tutorial.yml b/docs/_data/nav_tutorial.yml new file mode 100644 index 0000000000000..8ddfc28def946 --- /dev/null +++ b/docs/_data/nav_tutorial.yml @@ -0,0 +1,52 @@ +- title: Tutorial + items: + - id: tutorial + title: Overview + subitems: + - id: what-were-building + title: What We're Building + href: /react/tutorial/tutorial.html#what-were-building + - id: what-is-react + title: What is React? + href: /react/tutorial/tutorial.html#what-is-react + - id: getting-started + title: Getting Started + href: /react/tutorial/tutorial.html#getting-started + - id: passing-data-through-props + title: Passing Data Through Props + href: /react/tutorial/tutorial.html#passing-data-through-props + - id: an-interactive-component + title: An Interactive Component + href: /react/tutorial/tutorial.html#an-interactive-component + - id: developer-tools + title: Developer Tools + href: /react/tutorial/tutorial.html#developer-tools + - id: lifting-state-up + title: Lifting State Up + href: /react/tutorial/tutorial.html#lifting-state-up + subitems: + - id: functional-components + title: Functional Components + href: /react/tutorial/tutorial.html#functional-components + - id: taking-turns + title: Taking Turns + href: /react/tutorial/tutorial.html#taking-turns + - id: declaring-a-winner + title: Declaring a Winner + href: /react/tutorial/tutorial.html#declaring-a-winner + - id: storing-a-history + title: Storing A History + href: /react/tutorial/tutorial.html#storing-a-history + subitems: + - id: showing-the-moves + title: Showing the Moves + href: /react/tutorial/tutorial.html#showing-the-moves + - id: keys + title: Keys + href: /react/tutorial/tutorial.html#keys + - id: implementing-time-travel + title: Implementing Time Travel + href: /react/tutorial/tutorial.html#implementing-time-travel + - id: wrapping-up + title: Wrapping Up + href: /react/tutorial/tutorial.html#wrapping-up \ No newline at end of file diff --git a/docs/_includes/nav_tutorial.html b/docs/_includes/nav_tutorial.html new file mode 100644 index 0000000000000..2a720cbdd0bd7 --- /dev/null +++ b/docs/_includes/nav_tutorial.html @@ -0,0 +1,24 @@ + diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 15d60f99fd39b..6847fb90bf1f2 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -54,6 +54,7 @@
  • Download
  • Community
  • Blog
  • +
  • Tutorial
  • diff --git a/docs/_layouts/tutorial.html b/docs/_layouts/tutorial.html new file mode 100644 index 0000000000000..c6bd4832dfe7e --- /dev/null +++ b/docs/_layouts/tutorial.html @@ -0,0 +1,29 @@ +--- +layout: default +sectionid: tutorial +--- + +
    + {% include nav_tutorial.html %} + +
    + Edit on GitHub +

    + {{ page.title }} +

    +
    {{ page.description }}
    + + {{ content }} + +
    + {% if page.prev %} + ← Prev + {% endif %} + {% if page.next %} + Next → + {% endif %} +
    +
    +
    diff --git a/docs/_plugins/sidebar_item.rb b/docs/_plugins/sidebar_item.rb index d899af7ab18cf..699787620390e 100644 --- a/docs/_plugins/sidebar_item.rb +++ b/docs/_plugins/sidebar_item.rb @@ -12,6 +12,10 @@ def community_sidebar_link(item) return sidebar_helper(item, 'community') end + def tutorial_sidebar_link(item) + return sidebar_helper(item, 'tutorial') + end + def sidebar_helper(item, group) pageID = @context.registers[:page]["id"] itemID = item["id"] diff --git a/docs/img/tutorial/devtools.png b/docs/img/tutorial/devtools.png new file mode 100644 index 0000000000000000000000000000000000000000..7dcf3c6a9dbee0f94759f152a2703ff34f1e7afd GIT binary patch literal 51403 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 F{s(wPl(hf= literal 0 HcmV?d00001 diff --git a/docs/tutorial/tutorial.md b/docs/tutorial/tutorial.md new file mode 100644 index 0000000000000..51a0b4fe8de72 --- /dev/null +++ b/docs/tutorial/tutorial.md @@ -0,0 +1,460 @@ +--- +id: tutorial +title: "Tutorial: Thinking In React" +layout: tutorial +sectionid: tutorial +permalink: /tutorial/tutorial.html +--- + +## What We're Building + +Today, we're going to build an interactive tic-tac-toe game. We'll assume some familiarity with HTML and JavaScript but you should be able to follow along even if you haven't used them before. + +If you like, you can check out the final result here: FinalR Result. Try playing the game. You can also click on a link in the move list to go "back in time" and see what the board looked like just after that move was made. + +## What is React? + +React is a declarative, efficient, and flexible JavaScript library for building user interfaces. + +React has a few different kinds of components, but we'll start with React.Component subclasses: + +``` +class ShoppingList extends React.Component { + render() { + return ( +
    +

    Shopping List for {this.props.name}

    +
      +
    • Instagram
    • +
    • WhatsApp
    • +
    • Oculus
    • +
    +
    + ); + } +} + +// Example usage: +``` + +We'll get to the funny XML-like tags in a second. Your components tell React what you want to render – then React will efficiently update and render just the right components when your data changes. + +Here, ShoppingList is a **React component class**, or **React component type**. A component takes in parameters, called `props`, and returns a hierarchy of views to display via the `render` method. + +The `render` method return a *description* of what you want to render, and then React takes that description and renders it to the screen. In particular, `render` returns a **React element**, which is a lightweight description of what to render. Most React developers use a special syntax called JSX which makes it easier to write these structures. The `
    ` syntax is transformed at build time to `React.createElement('div')`. The example above is equivalent to + +``` + return React.createElement('div', {className: 'shopping-list'}, + React.createElement('h1', ...), + React.createElement('ul', ...) + ); +``` + +You can put any JavaScript expression within braces inside JSX. Each React element is a real JavaScript object that you can store in a variable or pass around your program. + +The `ShoppingList` component only renders built-in DOM components, but you can compose custom React components just as easily, by writing ``. Each component is encapsulated so it can operate independently, which allows you to build complex UIs out of simple components. + +## Getting Started + +Start with this example: Starter Code. + +It contains the shell of what we're building today. We've provided the styles so you only need to worry about the JavaScript. + +In particular, we have three components: + +* Square +* Board +* Game + +The Square component renders a single `
    `, the Board renders 9 squares, and the Game component renders a board with some placeholders that we'll fill in later. None of the components are interactive at this point. + +(The end of the JS file also defines a helper function `calculateWinner` that we'll use later.) + +## Passing Data Through Props + +Just to get our feet wet, let's try passing some data from the Board component to the Square component. In Board's `_renderSquare` method, change the code to return `` then change Square's render method to show that value by replacing `{/* TODO */}` with `{this.props.value}`. + +You should see a number in each square in the rendered output. + +##An Interactive Component + +Let's make the Square component fill in an "X" when you click it. Try changing the opening tag to + +``` + +``` + +Whenever `this.setState` is called, an update to the component is scheduled, causing React to merge in the passed state update and rerender the component along with its descendants. When the component rerenders, `this.state.value` will be `'X'` so you'll see an X in the grid. + +If you click on any square, an X should show up in it. + +## Developer Tools + +The React Devtools extension for [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/) lets you inspect a React component tree in your browser devtools. + +![React Devtools](/react/img/tutorial/devtools.png) + +It lets you inspect the props and state of any of the components in your tree. + +It doesn't work great on Codepen because of the multiple frames, but if you log in to Codepen and confirm your email (for spam prevention), you can go to Change View > Debug to open your code in a new tab, then the devtools will work. It's fine if you don't want to do this now, but it's good to know that it exists. + +## Lifting State Up + +We now have the basic building blocks for a tic-tac-toe game. But right now, the state is encapsulated in each Square component. To make a fully-working game, we now need to check if one player has won the game, and alternate placing X and O in the squares. To check if someone has won, we'll need to have the value of all 9 squares in one place, rather than split up across the Square components. + +You might think that Board should just inquire what the current state of each Square is. Although it is technically possible to do this in React, it is discouraged because it tends to make code difficult to understand, more brittle, and harder to refactor. + +Instead, the best solution here is to store this state in the Board component instead of in each Square – and the Board component can tell each Square what to display, like how we made each square display its index earlier. + +**When you want to aggregate data from multiple children or to have two child components communicate with each other, move the state upwards so that it lives in the parent component. The parent can then pass the state back down to the children via props, so that the child components are always in sync with each other and with the parent.** + +Pulling state upwards like this is common when refactoring React components, so let's take this opportunity to try it out. Add an initial state for Board containing an array with 9 nulls, corresponding to the 9 squares: + +``` +class Board extends React.Component { + constructor() { + super(); + this.state = { + squares: Array(9).fill(null), + }; + } +``` + +We'll fill it in later so that a board looks something like + +``` +[ + 'O', null, 'X', + 'X', 'X', 'O', + 'O', null, null, +] +``` + +Pass the value of each square down: + +``` +_renderSquare(i) { + return ; +} +``` + +And change Square to use `this.props.value` again. Now we need to change what happens when a square is clicked. The Board component now stores which squares are filled, which means we need some way for Square to update the state of Board. Since component state is considered private, we can't update Board's state directly from Square. The usual pattern here is pass down a function from Board to Square that gets called when the square is clicked. Change `_renderSquare` again so that it reads: + +``` + return this._handleClick(i)} />; +``` + +Now we're passing down two props from Board to Square: `value` and `onClick`. The latter is a function that Square can call. So let's do that by changing `render` in Square to have: + +``` + + ); +} +``` + +You'll need to change `this.props` to `props` both times it appears. Many components in your apps will be able to written as functional components: these components tend to be easier to write and React will optimize them more in the future. + +## Taking Turns + +An obvious defect in our game is that only X can play. Let's fix that. Add an `xIsNext: true` key to the initial state of Board, then update `_handleClick` to use that state: + +``` + _handleClick(i) { + const squares = this.state.squares.slice(); + const xIsNext = this.state.xIsNext; + squares[i] = xIsNext ? 'X' : 'O'; + this.setState({ + squares: squares, + xIsNext: !xIsNext, + }); + } +``` + +Now X and O take turns. Next, change the "status" text in Board's `render` so that it also updates when `xIsNext` changes. + +## Declaring a Winner + +Let's show when the game is won. A `calculateWinner(squares)` helper function that takes the list of 9 values has been provided for you at the bottom of the file. You can call it in Board's `render` function to check if anyone has won the game and make the status text show "Winner: [X/O]" when someone wins: + +``` + render() { + const winner = calculateWinner(this.state.squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + } + ... + } +``` + +You might also want to change `_handleClick` to return early and ignore the click if someone has already won the game or if a square is already filled: + +``` +_handleClick(i) { + const squares = this.state.squares.slice(); + if (calculateWinner(squares) || squares[i]) { + return; + } + ... +} +``` + +Congratulations! You now have a working tic-tac-toe game. And now you know the basics of React. So *you're* probably the real winner here. + + +## Storing a History + +Let's make it possible to revisit old states of the board so we can see what it looked like after any of the previous moves. We're already creating a new `squares` array each time a move is made, which means we can easily store all of the past board states simultaneously. In addition to each array of squares, let's store whose turn it was, what time the move was made, and information about the move: who made the move and its location. + +Let's plan to store an object like this in state: + +``` +history = [ + { + squares: [null x 9], + xIsNext: true, + move: null, // Initial board has no corresponding move + when: /* timestamp */, + }, + { + squares: [... x 9], + xIsNext: false, + move: {player: 'X', location: 4}, + when: /* timestamp */, + }, + ... +] +``` + +We'll want the top-level Game component to be responsible for displaying the list of moves. So just as we pulled the state up before from Square into Board, let's now pull it up again from Board into Game – so that we have all the information we need at the top level. + +Set up the initial state for Game: + +``` +class Game extends React.Component { + constructor() { + super(); + this.state = { + history: [{ + squares: Array(9).fill(null), + xIsNext: true, + move: null, + when: Date.now(), + }], + }; + } +``` + +then change Board so that it takes `squares` via props and has its own `onClick` prop specified by `Game`, like the transformation we made for Square and Board earlier. You can pass the location of each square into the click handler so that we still know which square was clicked: + +``` +return this.props.onClick(i)} />; +``` + +Game's `render` should look at the most recent history entry and can take over calculating the game status: + +``` +const history = this.state.history; +const current = history[history.length - 1]; +const winner = calculateWinner(current.squares); + +let status; +if (winner) { + status = 'Winner: ' + winner; +} else { + status = 'Next player: ' + (current.xIsNext ? 'X' : 'O'); +} +... +
    + this._handleClick(i)} + /> +
    +
    +
    {status}
    +
      {/* TODO */}
    +
    +``` + +Its `_handleClick` can push a new entry onto the stack by concatenating the new history entry to make a new history array: + +``` +_handleClick(i) { + var history = this.state.history; + var current = history[history.length - 1]; + const squares = current.squares.slice(); + if (calculateWinner(squares) || squares[i]) { + return; + } + const xIsNext = current.xIsNext; + squares[i] = xIsNext ? 'X' : 'O'; + this.setState({ + history: history.concat([{ + squares: squares, + xIsNext: !xIsNext, + move: {player: squares[i], location: i}, + when: Date.now(), + }]), + }); +} +``` + +This was a significant amount of refactoring, but the game should again be playable. At this point, Board only needs `_renderStep` and `render`; the state initialization and click handler should both live in Game. + +## Showing the Moves + +Let's show the moves that have been made in the game so far. We learned earlier that React elements are first-class JS objects and we can store them or pass them around. To render multiple items in React, we pass an array of React elements. The most common way to build that array is to map over your array of data. Let's do that in the `render` method of Game: + +``` + const moves = history.map((step, i) => { + const desc = step.move ? + step.move.player + ' played at ' + step.move.location : + 'Game start'; + return ( +
  • + this._jumpTo(i)}>{desc} +
  • + ); + }); + ... +
      {moves}
    +``` + +For each step in the history, we create a list item `
  • ` with a link `` inside it that goes nowhere (`href="#"`) but has a click handler which we'll implement shortly. With this code, you should see a list of the moves that have been made in the game, along with a warning that says + +(IMPORTANT) Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of "Game". + +Let's talk about what that warning means. + +## Keys + +When you render a list of items, React always stores some info about each item in the list. If you render a component that has state, that state needs to be stored – and regardless of how you implement your components, React stores a reference to the backing native views. + +When you update that list, React needs to determine what has changed. You could've added, removed, rearranged, or updated items in the list. + +Imagine transitioning from + +``` +
  • Alexa: 7 tasks left
  • +
  • Ben: 5 tasks left
  • +``` + +to + +``` +
  • Ben: 9 tasks left
  • +
  • Claudia: 8 tasks left
  • +
  • Alexa: 5 tasks left
  • +``` + +To a human eye, it looks likely that Alexa and Ben swapped places and Claudia was added – but React is just a computer program and doesn't know what you intended it to do. As a result, React asks you to specify a //key// property on each element in a list, a string to differentiate each component from its siblings. In this case, `alexa`, `ben`, `claudia` might be sensible keys; if the items correspond to objects in a database, the database ID is usually a good choice: + +``` +
  • {user.name}: {user.taskCount} tasks left
  • +``` + +`key` is a special property that's reserved by React (along with `ref`, a more advanced feature). When an element is created, React pulls off the `key` property and stores the key directly on the returned element. Even though it may look like it is part of props, it cannot be referenced with `this.props.key`. React uses the key automatically while deciding which children to update; there is no way for a component to inquire about its own key. + +When a list is rerendered, React takes each element in the new version and looks for one with a matching key in the previous list. When a key is added to the set, a component is created; when a key is removed, a component is destroyed. Keys tell React about the identity of each component, so that it can maintain the state across rerenders. If you change the key of a component, it will be completely destroyed and recreated with a new state. + +**It's strongly recommended that you assign proper keys whenever you build dynamic lists.** If you don't have an appropriate key handy, you may want to consider restructuring your data so that you do. + +If you don't specify any key, React will warn you and fall back to using the array index as a key – which is not the correct choice if you ever reorder elements in the list or add/remove items anywhere but the bottom of the list. Explicitly passing `key={i}` silences the warning but has the same problem so isn't recommended in most cases. + +Component keys don't need to be globally unique, only unique relative to the immediate siblings. + +## Implementing Time Travel + +For our move list, we already have a unique ID for each step: the time that it happened. Add the key as `
  • ` and the key warning should disappear. + +Clicking any of the move links throws an error because `_jumpTo` is undefined. Let's add a new key to Game's state to indicate which step we're currently viewing. First, add `stepNumber: 0` to the initial state, then have `_jumpTo` update that state: + +``` + _jumpTo(i) { + this.setState({ + stepNumber: i, + }); + } +``` + +Then update `stepNumber` when a new move is made by adding `stepNumber: history.length` to the state update in `_handleClick`. Now you can modify `render` to read from that step in the history: + +``` +const current = history[this.state.stepNumber]; +``` + +If you click any move link now, the board should immediately update to show what the game looked like at that time. You may also want to update `_handleClick` to be aware of `stepNumber` when reading the current board state so that you can go back in time then click in the board to create a new entry. (Hint: It's easiest to `.slice()` off the extra elements from `history` at the very top of `_handleClick`.) + +## Wrapping Up + +Now, you've made a tic-tac-toe game that: + +* lets you play tic-tac-toe, +* indicates when one player has won the game, +* stores the entire history of moves during the game, +* allows players to jump back in time to see older versions of the game board. + +Nice work! We hope you now feel like you have a decent grasp on how React works. + +If you have extra time or want to practice your new skills, here are some ideas for improvements you could make, listed in order of increasing difficulty: + +1. Display the move locations in the format "(1, 3)" instead of "6". +2. Bold the currently-selected item in the move list. +3. Rewrite Board to use two loops to make the squares instead of hardcoding them. +4. Add a toggle button that lets you sort the moves in either ascending or descending order. +5. When someone wins, highlight the three squares that caused the win. From a2360a9c8f21bde5751688e1676e6a6a9814bb6e Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 4 Oct 2016 20:02:36 +0100 Subject: [PATCH 02/12] Fix the conflict --- docs/_layouts/default.html | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 67081b18c31d9..ec28765ce5127 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -74,13 +74,9 @@
  • From 3f24afcc987adeb9d5f1e1549d408783fe13b4c2 Mon Sep 17 00:00:00 2001 From: Eric Nakagawa Date: Tue, 4 Oct 2016 13:02:33 -0700 Subject: [PATCH 03/12] fixed coloring for tutorial --- docs/tutorial/tutorial.md | 134 +++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/docs/tutorial/tutorial.md b/docs/tutorial/tutorial.md index 51a0b4fe8de72..0f1aa15c04ca2 100644 --- a/docs/tutorial/tutorial.md +++ b/docs/tutorial/tutorial.md @@ -18,7 +18,7 @@ React is a declarative, efficient, and flexible JavaScript library for building React has a few different kinds of components, but we'll start with React.Component subclasses: -``` +```javascript class ShoppingList extends React.Component { render() { return ( @@ -43,11 +43,11 @@ Here, ShoppingList is a **React component class**, or **React component type**. The `render` method return a *description* of what you want to render, and then React takes that description and renders it to the screen. In particular, `render` returns a **React element**, which is a lightweight description of what to render. Most React developers use a special syntax called JSX which makes it easier to write these structures. The `
    ` syntax is transformed at build time to `React.createElement('div')`. The example above is equivalent to -``` - return React.createElement('div', {className: 'shopping-list'}, - React.createElement('h1', ...), - React.createElement('ul', ...) - ); +```javascript +return React.createElement('div', {className: 'shopping-list'}, + React.createElement('h1', ...), + React.createElement('ul', ...) +); ``` You can put any JavaScript expression within braces inside JSX. Each React element is a real JavaScript object that you can store in a variable or pass around your program. @@ -80,7 +80,7 @@ You should see a number in each square in the rendered output. Let's make the Square component fill in an "X" when you click it. Try changing the opening tag to -``` +```html @@ -134,7 +134,7 @@ Instead, the best solution here is to store this state in the Board component in Pulling state upwards like this is common when refactoring React components, so let's take this opportunity to try it out. Add an initial state for Board containing an array with 9 nulls, corresponding to the 9 squares: -``` +```javascript class Board extends React.Component { constructor() { super(); @@ -146,7 +146,7 @@ class Board extends React.Component { We'll fill it in later so that a board looks something like -``` +```javascript [ 'O', null, 'X', 'X', 'X', 'O', @@ -156,7 +156,7 @@ We'll fill it in later so that a board looks something like Pass the value of each square down: -``` +```javascript _renderSquare(i) { return ; } @@ -164,19 +164,19 @@ _renderSquare(i) { And change Square to use `this.props.value` again. Now we need to change what happens when a square is clicked. The Board component now stores which squares are filled, which means we need some way for Square to update the state of Board. Since component state is considered private, we can't update Board's state directly from Square. The usual pattern here is pass down a function from Board to Square that gets called when the square is clicked. Change `_renderSquare` again so that it reads: -``` - return this._handleClick(i)} />; +```javascript +return this._handleClick(i)} />; ``` Now we're passing down two props from Board to Square: `value` and `onClick`. The latter is a function that Square can call. So let's do that by changing `render` in Square to have: -``` +```javascript