You can just think of context as a way to avoid passing props too many times, such that multiple components can read and update the state, which will cause everyone that is consuming this context to reload to the new state.
A context would just have a state holding some data, and you just export out this data so your components can read them, and you export out the functions to update the data, so you can control what the context data changes to. A simple example is a page context. To follow along with this example, create three files. The first file is a file called PageContext.luau
which is our context file
-- At PageContext.luau file
-- !! Make sure to require relevant react packages. I am omitting from this file since our paths can differ a lot
-- Pages we want to navigate our context to
export type Pages = "Home" | "Shop" | "Game" | "Settings"
export type PageContext = {
-- This is the active page our context holds
page: Pages,
-- This is a function to change our page to the page we pass in the parameter
changePage: (page: Pages) -> (),
}
-- We can either set the default value to nil, or we can provide a default value to the context
-- When you provide default value, it means two things
-- The first time the context runs, this default value is used
-- Whenever you try to access the context outside of its provider, you can use this value
-- Although IDEALLY, you don't want to access the value outside the providers
-- So in react.js it is a common practice to set the default value to undefined or nil
-- If you don't want to set it to undefined, thats okay too, just pass some default value instead
local page_context: react.Context<PageContext?> = createContext(nil :: PageContext?)
local function provider(props: react.ElementProps<any>): react.ReactNode
-- Page is the current context state, set_page is the useState function to change the state
-- Note that set_page can only recieve Pages as the parameter
-- We are setting the default value of the page to `Home`
local page, set_page = useState("Home" :: Pages)
-- Attach our state and the handler as a value
-- We will then pass this to the context.value
local value: PageContext = {
page = page,
changePage = set_page,
}
-- Note that e is just local e = react.createElement;
-- We are basically wrapping all the children components inside the Provider
-- This way all the children components can access the provider's values.
-- By default, react always passes a prop called `children`. Which is what we are using here
return e(page_context.Provider, {
value = value,
}, props.children)
end
local function usePage(): Pages
local context = useContext(page_context)
if context == nil then
return error(`Attempted to access Page context from outside the provider`)
end
return context.page
end
local function useChangePage(): (Pages) -> ()
local context = useContext(page_context)
if context == nil then
return error(`Attempted to access Page context from outside the provider`)
end
return context.changePage
end
return {
provider = provider,
usePage = usePage,
useChangePage = useChangePage,
}
Now, we need to wrap our App into the providers. It is also a common practice to have all the context wrapped in at the top of our app. Here is the App.luau
file which is our main client file essentially. For our demo purpose, this file is going to be very simple
-- At App.luau file
-- !! Make sure to require relevant react packages. I am omitting from this file since our paths can differ a lot
-- I am also importing Page Context module in this file as well
local function App()
-- Consume the page here so we can conditionally render our pages
-- Annnoying luau bug here as well since Pages datatype gets coerced into strings
-- Which is why we must manually assign the pageContext.Pages type for now.... sigh...
local active_page: pageContext.Pages = pageContext.usePage()
-- Now, we can conditionally render each of our pages based on what the active page is
return e("ScreenGui", {}, {
Home = active_page == "Home" and e(HomePage),
Settings = active_page == "Settings" and e(SettingsPage),
Game = active_page == "Game" and e(GamePage),
Shop = active_page == "Shop" and e(ShopPage),
})
end
-- This is the function that you will mount on the react-roblox
-- I will not be going over the implementation detail of that since that is covered in
-- tutorial in this forum to begin with
local function main(): react.ReactNode
return e(pageContext.provider, {}, App)
end
You can also require this Page Context file anywhere else as well. And it works great because module scripts are cached. So lets say inside the Settings.luau
file we want a button that is going to get us back to home page. Well, we can just require the Page context file, and when a button is clicked, use the changePage
function and pass in Home
. A simple example can look like this
-- At SettingsPage.luau file
-- !! Make sure to require relevant react packages. I am omitting from this file since our paths can differ a lot
-- I am also importing Page Context module in this file as well
export type SettingsPageProps = {}
local function SettingsPage(props: SettingsPageProps): react.ReactNode
local changePage = pageContext.useChangePage()
-- Settings papge is rendered in the App file earlier when current page is set to "settings"
-- So when this page is being rendered in our UI, we know that the current page is settings
return e("Frame", {
Size = UDim2.fromScale(1, 1),
}, {
-- We have a go back home button here
HomeButton = e("TextButton", {
Size = UDim2.new(0, 200, 0, 50),
Text = "Home",
-- When a button is activated, we simply change the page context's state to Home
-- Then the page context will force rerender on all of the components that are
-- consuming this Context. Then the app component will render Home page instead
[react.Event.Activated] = function()
changePage("Home")
end,
}),
})
end
Context are also commonly used in roblox to listen for remote events or Signal changes through server/client. So when multiple components needs to listen to remote event changes, you would listen to the remote events inside the context. However, if only a single component or a child of a parent component needs to consume a state, then it isn’t necessary to use context. So make sure you aren’t overusing context either.