From 794455fba6c7ad137ce468f300b5383a54f49dd5 Mon Sep 17 00:00:00 2001 From: Paul Doyle Date: Wed, 2 Jan 2019 09:30:00 -0800 Subject: [PATCH 1/7] Add 'createFragment' to public interface. whoops! --- lib/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/init.lua b/lib/init.lua index 869577fb..aa505563 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -15,6 +15,7 @@ local reconcilerCompat = createReconcilerCompat(robloxReconciler) local Roact = strict { Component = require(script.Component), createElement = require(script.createElement), + createFragment = require(script.createFragment), oneChild = require(script.oneChild), PureComponent = require(script.PureComponent), None = require(script.None), From 1bfe3942f8324466fe5ff69cc0d99301c9428fa9 Mon Sep 17 00:00:00 2001 From: Paul Doyle Date: Wed, 2 Jan 2019 15:43:10 -0800 Subject: [PATCH 2/7] Add fragments to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39a39b6b..f02b5aed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * Ref switching now occurs in one pass, which should fix edge cases where the result of a ref is `nil`, especially in property changed events ([#98](https://github.com/Roblox/roact/pull/98)) * `setState` can now be called inside `init` and `willUpdate`. Instead of triggering a new render, it will affect the currently scheduled one. ([#139](https://github.com/Roblox/roact/pull/139)) * Roll back changes that allowed `setState` to be called inside `willUpdate`, which created state update scenarios with difficult-to-determine behavior. ([#157](https://github.com/Roblox/roact/pull/157)) +* Add fragment support via `Roact.createFragment` ([#172](https://github.com/Roblox/roact/pull/172)) ## 1.0.0 Prerelease 2 (March 22, 2018) * Removed `is*Element` methods, this is unlikely to affect anyone ([#50](https://github.com/Roblox/roact/pull/50)) From 41b5f6b961c96fa2d3b1294197fe6f393e500784 Mon Sep 17 00:00:00 2001 From: Paul Doyle Date: Wed, 2 Jan 2019 15:43:20 -0800 Subject: [PATCH 3/7] Add to api-documentation --- docs/api-reference.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api-reference.md b/docs/api-reference.md index f2b30ad6..a9f2ecd5 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -14,6 +14,13 @@ The `children` argument is shorthand for adding a `Roact.Children` key to `props !!! caution Once `props` or `children` are passed into the `createElement`, make sure not to modify them! +### Roact.createFragment +``` +Roact.createFragment(elements) -> RoactFragment +``` + +Creates a new Roact fragment, which can contain multiple elements. Fragments allow a component to group elements together by returning them to external groups instead of needing create nested groupings. + ### Roact.mount ``` Roact.mount(element, [parent, [key]]) -> ComponentInstanceHandle From e4876b36463427557fa47178f79ee644e691d0ea Mon Sep 17 00:00:00 2001 From: Paul Doyle Date: Thu, 3 Jan 2019 10:48:36 -0800 Subject: [PATCH 4/7] Update docs page, flesh out api section --- docs/advanced/fragments.md | 36 ++++++++++++++++++++++++++++++++++++ docs/api-reference.md | 9 +++++++-- mkdocs.yml | 1 + 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 docs/advanced/fragments.md diff --git a/docs/advanced/fragments.md b/docs/advanced/fragments.md new file mode 100644 index 00000000..26e92868 --- /dev/null +++ b/docs/advanced/fragments.md @@ -0,0 +1,36 @@ +!!! info + This section is a work in progress. + +Typically, Roact components will render a single element by returning the result of a call to `createElement`. Suppose we'd instead like to create a Roact component that renders a collection of elements. That sort of component could be used to inject items into a list or frame without additional nesting. + +For example, let's to define a list component like this: +```lua +local function ListComponent(props) + return Roact.createElement("Frame", { + -- Props for frame... + }, { + Layout = Roact.createElement("UIListLayout", { + -- Props for UIListLayout... + }) + ListItems = Roact.createElement(ListItems) + }) +end +``` + +The `ListItems` piece of our children will be defined by its own component that renders the contents of the list. To do this, we need to tell Roact explicitly that we'd like to render a collection of elements as children within the same parent. + +That's where `Roact.createFragment` comes in: +```lua +local function ListItems(props) + return Roact.createFragment({ + Item1 = Roact.createElement("TextLabel", { + -- Props for item... + }), + Item2 = Roact.createElement("TextLabel", { + -- Props for item... + }) + }) +end +``` + +This will work as expected when used in combination with the above `ListComponent`. \ No newline at end of file diff --git a/docs/api-reference.md b/docs/api-reference.md index a9f2ecd5..6503af90 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -12,14 +12,19 @@ The `children` argument is shorthand for adding a `Roact.Children` key to `props `component` can be a string, a function, or a table created by `Component:extend`. !!! caution - Once `props` or `children` are passed into the `createElement`, make sure not to modify them! + Make sure not to modify `props` or `children` after they're passed into `createElement`! ### Roact.createFragment ``` Roact.createFragment(elements) -> RoactFragment ``` -Creates a new Roact fragment, which can contain multiple elements. Fragments allow a component to group elements together by returning them to external groups instead of needing create nested groupings. +Creates a new Roact fragment with the provided table of elements. A fragment represents a collection of elements that will be parented together. + +The `elements` argument should look exactly like the table of children that you might pass as the third argument of `createElement`. + +!!! caution + Make sure not to modify `children` after they're passed into `createFragment`! ### Roact.mount ``` diff --git a/mkdocs.yml b/mkdocs.yml index f3614e03..b9ac44bf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,6 +19,7 @@ pages: - State and Lifecycle: guide/state-and-lifecycle.md - Events: guide/events.md - Advanced Concepts: + - Fragments: advanced/fragments.md - Portals: advanced/portals.md - Refs: advanced/refs.md - Context: advanced/context.md From d43d6fd6354b96c154a92b053faa77e03560ed5e Mon Sep 17 00:00:00 2001 From: Paul Doyle Date: Thu, 3 Jan 2019 09:53:12 -0800 Subject: [PATCH 5/7] Introduce more detailed example --- docs/advanced/fragments.md | 55 ++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/docs/advanced/fragments.md b/docs/advanced/fragments.md index 26e92868..d0932a27 100644 --- a/docs/advanced/fragments.md +++ b/docs/advanced/fragments.md @@ -1,36 +1,69 @@ !!! info This section is a work in progress. -Typically, Roact components will render a single element by returning the result of a call to `createElement`. Suppose we'd instead like to create a Roact component that renders a collection of elements. That sort of component could be used to inject items into a list or frame without additional nesting. +Fragments are a tool for organizing components without unnecessary nesting. -For example, let's to define a list component like this: +Typically, Roact components will render a single element by returning the result of a call to `createElement`. + +For example, suppose for a multiplayer game we define a list component like this: ```lua -local function ListComponent(props) +local function TeamList(props) return Roact.createElement("Frame", { - -- Props for frame... + -- Props for Frame... }, { Layout = Roact.createElement("UIListLayout", { -- Props for UIListLayout... }) - ListItems = Roact.createElement(ListItems) + ListItems = Roact.createElement(TeamLabels) }) end ``` -The `ListItems` piece of our children will be defined by its own component that renders the contents of the list. To do this, we need to tell Roact explicitly that we'd like to render a collection of elements as children within the same parent. - -That's where `Roact.createFragment` comes in: +And a separate component to render a collection of `TextLabel`s that represent teams: ```lua +local function TeamLabels(props) + return Roact.createElement("Frame", { + -- Props for Frame... + }, { + RedTeam = Roact.createElement("TextLabel", { + -- Props for item... + }), + BlueTeam = Roact.createElement("TextLabel", { + -- Props for item... + }) + }) +end +``` + +Unfortunately, the `TeamLabels` piece of our children will be defined by its own component that renders the contents of the list. The resulting Roblox hierarchy won't actually apply the `UIListLayout` to the list of items, because it's grouped incorrectly: +``` +Frame: + UIListLayout + Frame: + TextLabel + TextLabel +``` + +Suppose we'd instead like to create a Roact component that renders a collection of elements. That sort of component could be used to inject items into a list or frame without additional nesting. That's where `Roact.createFragment` comes in: +```lua hl_lines="2" local function ListItems(props) return Roact.createFragment({ - Item1 = Roact.createElement("TextLabel", { + RedTeam = Roact.createElement("TextLabel", { -- Props for item... }), - Item2 = Roact.createElement("TextLabel", { + BlueTeam = Roact.createElement("TextLabel", { -- Props for item... }) }) end ``` -This will work as expected when used in combination with the above `ListComponent`. \ No newline at end of file +When used in combination with the above `TeamList` component, this will generate the desired Roblox hierarchy: +``` +Frame: + UIListLayout + TextLabel + TextLabel +``` + +We are also free to create alternate views that use the same `TeamLabels` component with different Layouts or groupings. \ No newline at end of file From ddb630c72cab24164c28331e8802bf0804e3ee37 Mon Sep 17 00:00:00 2001 From: Paul Doyle Date: Thu, 3 Jan 2019 10:10:42 -0800 Subject: [PATCH 6/7] Some rewording --- docs/advanced/fragments.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/advanced/fragments.md b/docs/advanced/fragments.md index d0932a27..7eeaa086 100644 --- a/docs/advanced/fragments.md +++ b/docs/advanced/fragments.md @@ -1,9 +1,7 @@ !!! info This section is a work in progress. -Fragments are a tool for organizing components without unnecessary nesting. - -Typically, Roact components will render a single element by returning the result of a call to `createElement`. +Fragments are a tool for avoiding unnecessary nesting when organizing components. Typically, Roact components will render a single element by returning the result of a call to `createElement`. For example, suppose for a multiplayer game we define a list component like this: ```lua @@ -44,7 +42,7 @@ Frame: TextLabel ``` -Suppose we'd instead like to create a Roact component that renders a collection of elements. That sort of component could be used to inject items into a list or frame without additional nesting. That's where `Roact.createFragment` comes in: +Suppose we'd instead like to create a Roact component that renders a collection of elements. That sort of component could be used to inject items into a list or frame without additional nesting. That's where fragments come in: ```lua hl_lines="2" local function ListItems(props) return Roact.createFragment({ @@ -58,7 +56,7 @@ local function ListItems(props) end ``` -When used in combination with the above `TeamList` component, this will generate the desired Roblox hierarchy: +We call `Roact.createFragment` and provide it a table of elements. When used in combination with the above `TeamList` component, this will generate the desired Roblox hierarchy: ``` Frame: UIListLayout From 0484a58d9343848a64afa0cad3004189a219fe4b Mon Sep 17 00:00:00 2001 From: Paul Doyle Date: Mon, 21 Jan 2019 14:08:28 -0800 Subject: [PATCH 7/7] Some minor revisions, rework fragments page --- docs/advanced/fragments.md | 19 ++++++++++--------- docs/api-reference.md | 6 ++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/advanced/fragments.md b/docs/advanced/fragments.md index 7eeaa086..c482574b 100644 --- a/docs/advanced/fragments.md +++ b/docs/advanced/fragments.md @@ -1,9 +1,8 @@ -!!! info - This section is a work in progress. +Fragments are a tool for avoiding unnecessary nesting when organizing components by allowing components to render collections of elements without wrapping them in a single containing element. -Fragments are a tool for avoiding unnecessary nesting when organizing components. Typically, Roact components will render a single element by returning the result of a call to `createElement`. +## Without Fragments -For example, suppose for a multiplayer game we define a list component like this: +Typically, Roact components will render a single element via `createElement`. For example, suppose we define a component like this: ```lua local function TeamList(props) return Roact.createElement("Frame", { @@ -17,7 +16,7 @@ local function TeamList(props) end ``` -And a separate component to render a collection of `TextLabel`s that represent teams: +Suppose we also want to use a separate component to render a collection of `TextLabel`s: ```lua local function TeamLabels(props) return Roact.createElement("Frame", { @@ -33,7 +32,7 @@ local function TeamLabels(props) end ``` -Unfortunately, the `TeamLabels` piece of our children will be defined by its own component that renders the contents of the list. The resulting Roblox hierarchy won't actually apply the `UIListLayout` to the list of items, because it's grouped incorrectly: +Unfortunately, the `TeamLabels` component can't return two different labels without wrapping them in a containing frame. The resulting Roblox hierarchy from these `TeamList` component won't actually apply the `UIListLayout` to the list of items, because it's grouped incorrectly: ``` Frame: UIListLayout @@ -42,9 +41,11 @@ Frame: TextLabel ``` -Suppose we'd instead like to create a Roact component that renders a collection of elements. That sort of component could be used to inject items into a list or frame without additional nesting. That's where fragments come in: +## With Fragments + +In order to separate our list contents from our list container, we need to be able to return a group of elements from our render method rather than a single one. Fragments make this possible: ```lua hl_lines="2" -local function ListItems(props) +local function TeamLabels(props) return Roact.createFragment({ RedTeam = Roact.createElement("TextLabel", { -- Props for item... @@ -56,7 +57,7 @@ local function ListItems(props) end ``` -We call `Roact.createFragment` and provide it a table of elements. When used in combination with the above `TeamList` component, this will generate the desired Roblox hierarchy: +We provide `Roact.createFragment` with a table of elements. These elements will result in multiple children of this component's parent. When used in combination with the above `TeamList` component, it will generate the desired Roblox hierarchy: ``` Frame: UIListLayout diff --git a/docs/api-reference.md b/docs/api-reference.md index 6503af90..1338c36c 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -19,12 +19,10 @@ The `children` argument is shorthand for adding a `Roact.Children` key to `props Roact.createFragment(elements) -> RoactFragment ``` -Creates a new Roact fragment with the provided table of elements. A fragment represents a collection of elements that will be parented together. - -The `elements` argument should look exactly like the table of children that you might pass as the third argument of `createElement`. +Creates a new Roact fragment with the provided table of elements. Fragments allow grouping of elements without the need for intermediate containing objects like `Frame`s. !!! caution - Make sure not to modify `children` after they're passed into `createFragment`! + Make sure not to modify `elements` after they're passed into `createFragment`! ### Roact.mount ```