Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Introduce StoreUpdateGate, for temporarily blocking store updates for its descendants #7

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
107 changes: 107 additions & 0 deletions lib/GatedSignal.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
--[[
A limited, simple implementation of a GatedSignal with the addition of a
gated condition.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ouch, that comment got butchered by find-and-replace.


If the signal has 'isBlocking' set, changes will be witheld until
'isBlocking' is reset to false..

Handlers are fired in order, and (dis)connections are properly handled when
executing an event.
]]

local function immutableAppend(list, ...)
local new = {}
local len = #list

for key = 1, len do
new[key] = list[key]
end

for i = 1, select("#", ...) do
new[len + i] = select(i, ...)
end

return new
end

local function immutableRemoveValue(list, removeValue)
local new = {}

for i = 1, #list do
if list[i] ~= removeValue then
table.insert(new, list[i])
end
end

return new
end

local GatedSignal = {}

GatedSignal.__index = GatedSignal

function GatedSignal.new()
local self = {
_listeners = {},
_isBlocking = false,
_shouldFireWhenUnblocked = false,
}

setmetatable(self, GatedSignal)

return self
end

function GatedSignal:connect(callback)
local listener = {
callback = callback,
disconnected = false,
}

self._listeners = immutableAppend(self._listeners, listener)

local function disconnect()
listener.disconnected = true
self._listeners = immutableRemoveValue(self._listeners, listener)
end

return {
disconnect = disconnect
}
end

function GatedSignal:fire(...)
if self._isBlocking then
self._shouldFireWhenUnblocked = true
return
end

for _, listener in ipairs(self._listeners) do
if not listener.disconnected then
listener.callback(...)
end
end
end

function GatedSignal:block()
if self._isBlocking then
return
end

self._isBlocking = true
end

function GatedSignal:unblock(...)
if not self._isBlocking then
return
end

self._isBlocking = false

if self._shouldFireWhenUnblocked then
self._shouldFireWhenUnblocked = false
self:fire(...)
end
end

return GatedSignal
76 changes: 76 additions & 0 deletions lib/StoreUpdateGate.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
local Roact = require(script.Parent.Parent.Roact)

local storeKey = require(script.Parent.storeKey)
local GatedSignal = require(script.Parent.GatedSignal)

local StoreUpdateGate = Roact.Component:extend("StoreUpdateGate")

function StoreUpdateGate:init()
local realStore = self._context[storeKey]

if not realStore then
error("StoreUpdateGate must be placed below a StoreProvider in the tree!")
end

-- The 'mock store' only exposes a subset of the real store's methods!
local mockStore = {}
mockStore.changed = GatedSignal.new()

mockStore.getState = function()
if self.props.shouldBlockUpdates then
return self.stateAtLastBlock
else
return realStore:getState()
end
end

mockStore.dispatch = function(self, action)
return realStore:dispatch(action)
end

self.changedConnection = realStore.changed:connect(function(...)
mockStore.changed:fire(...)
end)

self.mockStore = mockStore
self._context[storeKey] = mockStore

self.stateAtLastBlock = nil

if self.props.shouldBlockUpdates then
self:blockChanges()
end
end

function StoreUpdateGate:blockChanges()
self.stateAtLastBlock = self.mockStore:getState()
self.mockStore:block()
end

function StoreUpdateGate:unblockChanges()
local oldState = self.stateAtLastBlock
local newState = self.mockStore:getState()

self.stateAtLastBlock = nil
self.mockStore:unblock(oldState, newState)
end

function StoreUpdateGate:didUpdate(oldProps)
if self.props.shouldBlockUpdates ~= oldProps.shouldBlockUpdates then
if self.props.shouldBlockUpdates then
self:blockChanges()
else
self:unblockChanges()
end
end
end

function StoreUpdateGate:willUnmount()
self.changedConnection:disconnect()
end

function StoreUpdateGate:render()
return Roact.oneChild(self.props[Roact.Children])
end

return StoreUpdateGate
6 changes: 6 additions & 0 deletions lib/StoreUpdateGate.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
return function()
local Roact = require(script.Parent.Parent.Roact)
local StoreProvider = require(script.Parent.StoreProvider)

local StoreUpdateGate = require(script.Parent.StoreUpdateGate)
end