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

Add getDerivedStateFromProps #57

Merged
merged 3 commits into from
Apr 5, 2018
Merged
Show file tree
Hide file tree
Changes from 2 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
57 changes: 43 additions & 14 deletions lib/Component.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ setState cannot be used currently, are you calling setState from any of:
* the render function
* the shouldUpdate function]]

local function mergeState(currentState, partialState)
local newState = {}

for key, value in pairs(currentState) do
newState[key] = value
end

for key, value in pairs(partialState) do
if value == Core.None then
newState[key] = nil
else
newState[key] = value
end
end

return newState
end

--[[
Create a new Roact stateful component class.

Expand Down Expand Up @@ -76,6 +94,14 @@ function Component:extend(name)
self.state = {}
end

if class.getDerivedStateFromProps then
local partialState = class.getDerivedStateFromProps(props, self.state)

if partialState then
self.state = mergeState(self.state, partialState)
end
end

-- Now that state has definitely been set, we can now allow it to be changed.
self._canSetState = true

Expand Down Expand Up @@ -133,20 +159,7 @@ function Component:setState(partialState)
partialState = partialState(self.state, self.props)
end

local newState = {}

for key, value in pairs(self.state) do
newState[key] = value
end

for key, value in pairs(partialState) do
if value == Core.None then
newState[key] = nil
else
newState[key] = value
end
end

local newState = mergeState(self.state, partialState)
self:_update(self.props, newState)
end

Expand Down Expand Up @@ -174,6 +187,22 @@ end
function Component:_forceUpdate(newProps, newState)
self._canSetState = false

-- Compute new derived state.
-- Get the class - getDerivedStateFromProps is static.
local class = getmetatable(self)

-- Only update if newProps are given!
if newProps then
if class.getDerivedStateFromProps then
local derivedState = class.getDerivedStateFromProps(newProps, newState or self.state)

-- getDerivedStateFromProps can return nil if no changes are necessary.
if derivedState ~= nil then
newState = mergeState(newState or self.state, derivedState)
end
end
end

if self.willUpdate then
self:willUpdate(newProps or self.props, newState or self.state)
end
Expand Down
70 changes: 70 additions & 0 deletions lib/Component.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,76 @@ return function()
Reconciler.teardown(instance)
end)

describe("getDerivedStateFromProps", function()
it("should be called when a component is initialized", function()
local TestComponent = Component:extend("TestComponent")
local getStateCallback

function TestComponent.getDerivedStateFromProps(newProps, oldState)
return {
visible = newProps.visible
}
end

function TestComponent:init(props)
self.state = {
visible = false
}

getStateCallback = function()
return self.state
end
end

function TestComponent:render() end

local handle = Reconciler.reify(Core.createElement(TestComponent, {
visible = true
}))

local state = getStateCallback()
expect(state.visible).to.equal(true)

Reconciler.teardown(handle)
end)

it("should be called on property updates", function()
local TestComponent = Component:extend("TestComponent")
local getStateCallback

function TestComponent.getDerivedStateFromProps(newProps, oldState)
return {
visible = newProps.visible
}
end

function TestComponent:init(props)
self.state = {
visible = false
}

getStateCallback = function()
return self.state
end
end

function TestComponent:render() end

local handle = Reconciler.reify(Core.createElement(TestComponent, {
visible = true
}))
Copy link
Contributor

Choose a reason for hiding this comment

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

We can merge these two tests just by adding the two lines of assertion here I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed now!


Reconciler.reconcile(handle, Core.createElement(TestComponent, {
visible = 123,
}))

local state = getStateCallback()
expect(state.visible).to.equal(123)

Reconciler.teardown(handle)
end)
end)

describe("setState", function()
it("should throw when called in init", function()
local InitComponent = Component:extend("InitComponent")
Expand Down