Skip to content

Commit ed7eb70

Browse files
Connect to store before children connect to the store (#31)
Connecting to the store in didMount means that child components connect to the store before their parent component. This causes them to render before their parent component which can lead to issues if the state they need depends on their props and is no longer valid due to the store changing. The specific problem I have is that when a Player leaves, the in game playerlist has errors because the children are updated before their parent. The child component tries to access information that no longer exists in the store, but the parent will stop rendering this child when it updates due to the player leaving anyway. I think we should be able to fix this by changing it so that connecting to the store in RoactRodux is done in init, not didMount since init is called first for parents and then for children whereas didMount is called for children first. We could also fix this more explicitly by connecting to the store by priority (deeper in the tree = less priority). This might be a possible future fix to the problem. I added configuration for this change by copying the GlobalConfig pattern from Roact. Let me know if I should do something a bit more lightweight than this instead.
1 parent 923b17b commit ed7eb70

File tree

7 files changed

+137
-17
lines changed

7 files changed

+137
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# RoactRodux Changelog
22

33
## Current master
4-
* No changes
4+
* Change order of connection to store so that Parent components are connected to the store before their children.
55

66
## 1.0.0 (TODO: Date)
77
* Initial release

src/TempConfig.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
return {
2+
newConnectionOrder = true,
3+
}

src/connect.lua

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ local getStore = require(script.Parent.getStore)
33
local shallowEqual = require(script.Parent.shallowEqual)
44
local join = require(script.Parent.join)
55

6+
local TempConfig = require(script.Parent.TempConfig)
7+
68
--[[
79
Formats a multi-line message with printf-style placeholders.
810
]]
@@ -85,6 +87,23 @@ local function connect(mapStateToPropsOrThunk, mapDispatchToProps)
8587
end
8688
end
8789

90+
function Connection:createStoreConnection()
91+
self.storeChangedConnection = self.store.changed:connect(function(storeState)
92+
self:setState(function(prevState, props)
93+
local mappedStoreState = prevState.mapStateToProps(storeState, props)
94+
95+
-- We run this check here so that we only check shallow
96+
-- equality with the result of mapStateToProps, and not the
97+
-- other props that could be passed through the connector.
98+
if shallowEqual(mappedStoreState, prevState.mappedStoreState) then
99+
return nil
100+
end
101+
102+
return prevState.stateUpdater(props, prevState, mappedStoreState)
103+
end)
104+
end)
105+
end
106+
88107
function Connection:init()
89108
self.store = getStore(self)
90109

@@ -155,23 +174,16 @@ local function connect(mapStateToPropsOrThunk, mapDispatchToProps)
155174
for key, value in pairs(extraState) do
156175
self.state[key] = value
157176
end
177+
178+
if TempConfig.newConnectionOrder then
179+
self:createStoreConnection()
180+
end
158181
end
159182

160183
function Connection:didMount()
161-
self.storeChangedConnection = self.store.changed:connect(function(storeState)
162-
self:setState(function(prevState, props)
163-
local mappedStoreState = prevState.mapStateToProps(storeState, props)
164-
165-
-- We run this check here so that we only check shallow
166-
-- equality with the result of mapStateToProps, and not the
167-
-- other props that could be passed through the connector.
168-
if shallowEqual(mappedStoreState, prevState.mappedStoreState) then
169-
return nil
170-
end
171-
172-
return prevState.stateUpdater(props, prevState, mappedStoreState)
173-
end)
174-
end)
184+
if not TempConfig.newConnectionOrder then
185+
self:createStoreConnection()
186+
end
175187
end
176188

177189
function Connection:willUnmount()

src/connect.spec.lua

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ return function()
66
local Roact = require(script.Parent.Parent.Roact)
77
local Rodux = require(script.Parent.Parent.Rodux)
88

9+
local TempConfig = require(script.Parent.TempConfig)
10+
911
local function noop()
1012
return nil
1113
end
@@ -248,4 +250,104 @@ return function()
248250

249251
Roact.unmount(handle)
250252
end)
253+
254+
it("should render parent elements before children", function()
255+
local oldNewConnectionOrder = TempConfig.newConnectionOrder
256+
TempConfig.newConnectionOrder = true
257+
258+
local function mapStateToProps(state)
259+
return {
260+
count = state.count,
261+
}
262+
end
263+
264+
local childWasRenderedFirst = false
265+
266+
local function ChildComponent(props)
267+
if props.count > props.parentCount then
268+
childWasRenderedFirst = true
269+
end
270+
end
271+
272+
local ConnectedChildComponent = connect(mapStateToProps)(ChildComponent)
273+
274+
local function ParentComponent(props)
275+
return Roact.createElement(ConnectedChildComponent, {
276+
parentCount = props.count,
277+
})
278+
end
279+
280+
local ConnectedParentComponent = connect(mapStateToProps)(ParentComponent)
281+
282+
local store = Rodux.Store.new(reducer)
283+
local tree = Roact.createElement(StoreProvider, {
284+
store = store,
285+
}, {
286+
parent = Roact.createElement(ConnectedParentComponent),
287+
})
288+
289+
local handle = Roact.mount(tree)
290+
291+
store:dispatch({ type = "increment" })
292+
store:flush()
293+
294+
store:dispatch({ type = "increment" })
295+
store:flush()
296+
297+
Roact.unmount(handle)
298+
299+
expect(childWasRenderedFirst).to.equal(false)
300+
301+
TempConfig.newConnectionOrder = oldNewConnectionOrder
302+
end)
303+
304+
it("should render child elements before children when TempConfig.newConnectionOrder is false", function()
305+
local oldNewConnectionOrder = TempConfig.newConnectionOrder
306+
TempConfig.newConnectionOrder = false
307+
308+
local function mapStateToProps(state)
309+
return {
310+
count = state.count,
311+
}
312+
end
313+
314+
local childWasRenderedFirst = false
315+
316+
local function ChildComponent(props)
317+
if props.count > props.parentCount then
318+
childWasRenderedFirst = true
319+
end
320+
end
321+
322+
local ConnectedChildComponent = connect(mapStateToProps)(ChildComponent)
323+
324+
local function ParentComponent(props)
325+
return Roact.createElement(ConnectedChildComponent, {
326+
parentCount = props.count,
327+
})
328+
end
329+
330+
local ConnectedParentComponent = connect(mapStateToProps)(ParentComponent)
331+
332+
local store = Rodux.Store.new(reducer)
333+
local tree = Roact.createElement(StoreProvider, {
334+
store = store,
335+
}, {
336+
parent = Roact.createElement(ConnectedParentComponent),
337+
})
338+
339+
local handle = Roact.mount(tree)
340+
341+
store:dispatch({ type = "increment" })
342+
store:flush()
343+
344+
store:dispatch({ type = "increment" })
345+
store:flush()
346+
347+
Roact.unmount(handle)
348+
349+
expect(childWasRenderedFirst).to.equal(true)
350+
351+
TempConfig.newConnectionOrder = oldNewConnectionOrder
352+
end)
251353
end

src/init.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
local StoreProvider = require(script.StoreProvider)
22
local connect = require(script.connect)
33
local getStore = require(script.getStore)
4+
local TempConfig = require(script.TempConfig)
45

56
return {
67
StoreProvider = StoreProvider,
78
connect = connect,
89
UNSTABLE_getStore = getStore,
910
UNSTABLE_connect2 = connect,
11+
12+
TEMP_CONFIG = TempConfig,
1013
}

test-place.project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
},
1616

1717
"Rodux": {
18-
"$path": "modules/rodux/lib"
18+
"$path": "modules/rodux/src"
1919
},
2020

2121
"TestEZ": {

test/lemur.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
-- If you add any dependencies, add them to this table so they'll be loaded!
66
local LOAD_MODULES = {
77
{"src", "RoactRodux"},
8-
{"modules/rodux/lib", "Rodux"},
8+
{"modules/rodux/src", "Rodux"},
99
{"modules/roact/src", "Roact"},
1010
{"modules/testez/lib", "TestEZ"},
1111
}

0 commit comments

Comments
 (0)