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

Commit

Permalink
Fix inconsistent behavior between setState in init and directly assig…
Browse files Browse the repository at this point in the history
…ning to state (#232)
  • Loading branch information
ZoteTheMighty authored Aug 14, 2019
1 parent 37eeb19 commit 986c1d5
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 2 deletions.
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

0 comments on commit 986c1d5

Please sign in to comment.