Skip to content
This repository has been archived by the owner on Dec 13, 2023. It is now read-only.

Pass props and state through render #242

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Roact Changelog

## Unreleased Changes
* `render` now passes the incoming props and state ([#199](https://github.com/Roblox/roact/issues/199))

## [1.2.0](https://github.com/Roblox/roact/releases/tag/v1.2.0) (September 6th, 2019)
* Fixed a bug where derived state was lost when assigning directly to state in init ([#232](https://github.com/Roblox/roact/pull/232/))
Expand Down
18 changes: 14 additions & 4 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,20 +400,30 @@ end

### render
```
render() -> Element | nil
render(props, state) -> Element | nil
```

`render` describes what a component should display at the current instant in time.

```lua
function MyComponent:render(props, state)
-- props == self.props
-- state == self.state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to add a little bit more explanation of what we're trying to convey here. Maybe a little blurb that says that props and state can be accessed either as arguments or as properties on self?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do that, I was just copying Preact's documentation.

return Roact.createElement("TextLabel", {
Text = "Hello, " .. props.name .. "!"
})
end
```

!!! info
Roact assumes that `render` act likes a pure function: the result of `render` must depend only on `props` and `state`, and it must not have side-effects.

```lua
function MyComponent:render()
function MyComponent:render(props, state)
-- This is okay:
return Roact.createElement("TextLabel", {
Text = self.props.text,
Position = self.state.position
Text = props.text,
Position = state.position
})

-- Ack! Depending on values outside props/state is not allowed!
Expand Down
6 changes: 2 additions & 4 deletions src/Component.lua
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,6 @@ end
Returns a snapshot of this component given the current props and state. Must
be overridden by consumers of Roact and should be a pure function with
regards to props and state.

TODO (#199): Accept props and state as arguments.
]]
function Component:render()
local internalData = self[InternalData]
Expand Down Expand Up @@ -287,7 +285,7 @@ function Component:__mount(reconciler, virtualNode)
virtualNode.context = instance._context

internalData.lifecyclePhase = ComponentLifecyclePhase.Render
local renderResult = instance:render()
local renderResult = instance:render(props, instance.state)

internalData.lifecyclePhase = ComponentLifecyclePhase.ReconcileChildren
reconciler.updateVirtualNodeWithRenderResult(virtualNode, hostParent, renderResult)
Expand Down Expand Up @@ -450,7 +448,7 @@ function Component:__resolveUpdate(incomingProps, incomingState)
self.props = incomingProps
self.state = incomingState

local renderResult = virtualNode.instance:render()
local renderResult = virtualNode.instance:render(incomingProps, incomingState)

internalData.lifecyclePhase = ComponentLifecyclePhase.ReconcileChildren
reconciler.updateVirtualNodeWithRenderResult(virtualNode, virtualNode.hostParent, renderResult)
Expand Down
15 changes: 9 additions & 6 deletions src/Component.spec/render.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ return function()

expect(renderSpy.callCount).to.equal(1)

local renderArguments = renderSpy:captureValues("self")
local renderArguments = renderSpy:captureValues("self", "props", "state")

expect(Type.of(renderArguments.self)).to.equal(Type.StatefulComponentInstance)
assertDeepEqual(capturedProps, {})
Expand All @@ -57,7 +57,10 @@ return function()

local capturedProps
local capturedState
local renderSpy = createSpy(function(self)
local renderSpy = createSpy(function(self, props, state)
expect(props).to.equal(self.props)
expect(state).to.equal(self.state)

capturedProps = self.props
capturedState = self.state
end)
Expand All @@ -74,7 +77,7 @@ return function()

expect(renderSpy.callCount).to.equal(1)

local firstRenderArguments = renderSpy:captureValues("self")
local firstRenderArguments = renderSpy:captureValues("self", "props", "state")
local firstProps = capturedProps
local firstState = capturedState

Expand All @@ -91,7 +94,7 @@ return function()

expect(renderSpy.callCount).to.equal(2)

local secondRenderArguments = renderSpy:captureValues("self")
local secondRenderArguments = renderSpy:captureValues("self", "props", "state")
local secondProps = capturedProps
local secondState = capturedState

Expand Down Expand Up @@ -127,7 +130,7 @@ return function()

expect(renderSpy.callCount).to.equal(1)

local firstRenderArguments = renderSpy:captureValues("self")
local firstRenderArguments = renderSpy:captureValues("self", "props", "state")
local firstProps = capturedProps
local firstState = capturedState

Expand All @@ -137,7 +140,7 @@ return function()

expect(renderSpy.callCount).to.equal(2)

local renderArguments = renderSpy:captureValues("self")
local renderArguments = renderSpy:captureValues("self", "props", "state")

expect(Type.of(renderArguments.self)).to.equal(Type.StatefulComponentInstance)
expect(capturedProps).to.equal(firstProps)
Expand Down