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

Fix inconsistent behavior between setState in init and directly assigning to state #232

Merged
merged 4 commits into from
Aug 14, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
* Fixed a bug where derived state was lost when assigning directly to state in init ([#232](https://github.com/Roblox/roact/pull/232/))
* Improved the error message when an invalid changed hook name is used. ([#216](https://github.com/Roblox/roact/pull/216))
* Fixed a bug where fragments could not be used as children of an element or another fragment. ([#214](https://github.com/Roblox/roact/pull/214))

Expand Down
1 change: 1 addition & 0 deletions src/Component.lua
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ function Component:__mount(reconciler, virtualNode)

if instance.init ~= nil then
instance:init(instance.props)
assign(instance.state, instance:__getDerivedState(instance.props, instance.state))
end

-- It's possible for init() to redefine _context!
Expand Down
52 changes: 50 additions & 2 deletions src/Component.spec/getDerivedStateFromProps.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ return function()
someState = 2,
})

expect(getDerivedSpy.callCount).to.equal(3)
-- getDerivedStateFromProps will be called:
-- * Once on empty props
-- * Once during the self:setState in init
-- * Once more, defensively, on the resulting state AFTER init
-- * On updating with new state via updateVirtualNode
expect(getDerivedSpy.callCount).to.equal(4)

local values = getDerivedSpy:captureValues("props", "state")

Expand Down Expand Up @@ -123,7 +128,11 @@ return function()

noopReconciler.mountVirtualNode(element, hostParent, hostKey)

expect(getDerivedSpy.callCount).to.equal(2)
-- getDerivedStateFromProps will be called:
-- * Once on empty props
-- * Once during the self:setState in init
-- * Once more, defensively, on the resulting state AFTER init
expect(getDerivedSpy.callCount).to.equal(3)

local values = getDerivedSpy:captureValues("props", "state")

Expand Down Expand Up @@ -228,4 +237,43 @@ return function()
-- getDerivedStateFromProps is always called on initial state
expect(stateDerivedSpy.callCount).to.equal(3)
end)

it("should have derived state after assigning to state in init", function()
local getStateCallback
local getDerivedSpy = createSpy(function()
return {
derived = true,
}
end)
local WithDerivedState = Component:extend("WithDerivedState")

WithDerivedState.getDerivedStateFromProps = getDerivedSpy.value

function WithDerivedState:init()
self.state = {
init = true,
}

getStateCallback = function()
return self.state
end
end

function WithDerivedState:render()
return nil
end

local hostParent = nil
local hostKey = "WithDerivedState"
local element = createElement(WithDerivedState)

noopReconciler.mountVirtualNode(element, hostParent, hostKey)

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

assertDeepEqual(getStateCallback(), {
init = true,
derived = true,
})
end)
end