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

Add Context API #246

Merged
merged 5 commits into from
Jan 21, 2020
Merged

Add Context API #246

merged 5 commits into from
Jan 21, 2020

Conversation

psewell
Copy link
Contributor

@psewell psewell commented Jan 9, 2020

Adds createContext, provide, and consume APIs to Roact.
Components that consume Contexts automatically update when the Context updates, wherever they are in the tree.

Closes #4 .

  • Adds createContext function, which is used to make a new Context
  • createContext returns a Provider and Consumer.
local ThemeContext = Roact.createContext("default value")

local function UsingValue()
	return Roact.createElement(ThemeContext.Consumer, {
		render = function(theme)
			return Roact.createElement("TextLabel", {
				Text = theme,
			})
		end,
	})
end

local function Root()
	return Roact.createElement(ThemeContext.Provider, {
		value = "overridden value",
	}, {
		Inner = Roact.createElement(UsingValue),
	})
end

Checklist before submitting:

  • Added entry to CHANGELOG.md
  • Added/updated relevant tests
  • Added/updated documentation

Added createContext, provide, and consume APIs to Roact.
@coveralls
Copy link

coveralls commented Jan 9, 2020

Coverage Status

Coverage increased (+0.5%) to 94.162% when pulling dbe4263 on psewell:master into 68e0932 on Roblox:master.

@ZoteTheMighty ZoteTheMighty mentioned this pull request Jan 10, 2020
2 tasks
Removes provide and consume from the new Roact context API, so that createContext returns a Provider and Consumer.
Copy link
Contributor

@LPGhatguy LPGhatguy left a comment

Choose a reason for hiding this comment

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

Looking good!

Left some comments.

We'll also want to port the code to use the new context API defined in #247 once that lands and make sure everything still works.

end

function Provider:didUpdate()
self.updateValue(self.props.value)
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's check that prevProps.value ~= self.props.value before firing this method. This will let us avoid firing updates when we don't need to.

end

function Provider:render()
return oneChild(self.props[Children])
Copy link
Contributor

Choose a reason for hiding this comment

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

We should use a fragment here instead! We added them in 1.0 to improve cases like this, and will let providers have multiple children instead of just one.

local PureComponent = require(script.Parent.PureComponent)

local function createProvider(context)
local Provider = PureComponent:extend("Provider")
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use a regular component here. Because this component is always going to have children, the shouldUpdate check provided by PureComponent is almost always going to fail.

In general, we should prefer function components or Component by default, and fall back to PureComponent with profiling if we have performance issues.

end

function Consumer:render()
assert(type(self.props.render) == "function", "Consumer expects a `render` function")
Copy link
Contributor

Choose a reason for hiding this comment

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

We should be able to use Roact's validateProps feature here instead of asserting in render!

end

function Consumer:willUnmount()
if self.disconnect then
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we be a little bit more explicit with this check, as in

Suggested change
if self.disconnect then
if self.disconnect ~= nil then

It's unlikely that disconnect will be false, but we've been trying to encourage that explicitness when the exact type involved is unclear.

end

function Context:__tostring()
return tostring(self.defaultValue)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is kind of a strange __tostring!

Can we return a fixed string here like RoactContext?

local didRender = false
local context = createContext("Test")

local Listener = PureComponent:extend("Listener")
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of a PureComponent, this should just be a function component for simplicity.


expect(function()
local tree = noopReconciler.mountVirtualTree(element, nil, "Provide Tree")
noopReconciler.unmountVirtualTree(tree)
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need to call unmountVirtualTree. We should limit the scope of what's inside this expect block so that we don't erroneously pass this test when something else throws.

noopReconciler.unmountVirtualTree(tree)

expect(foundValue).to.equal("Test")
end)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be more robust to use Roact's internal createSpy method to create a spy function to use for the render function. This lets us assert how many times it was called, as well as all of the arguments passed.

Notably, this lets us catch errors like:

  1. We invoke the function twice and the first one is incorrect
  2. We pass extra arguments to the function

expect(foundValue).to.equal("ThirdTest")

noopReconciler.unmountVirtualTree(tree)
end)
Copy link
Contributor

Choose a reason for hiding this comment

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

This test can also be rewritten in terms of Roact's spies, which might be more readable.

-Use fragment instead of oneChild
-Use spies for testing
-Use Component over PureComponent
-Check for duplicates in didUpdate
-nil check disconnect
Patrick Sewell added 2 commits January 16, 2020 11:47
Merge with current Roblox master
Updates the createContext API to internally use the new internal Context API.
@ZoteTheMighty ZoteTheMighty merged commit d9b7f96 into Roblox:master Jan 21, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

New Context API
4 participants