Hello, before we begin this tutorial, I assume you have some knowledge of luau and roact itself. If you dont know what these are, you need to learn these before proceeding this tutorial!
Our gui should look like a hint when we finish : )
Why roactrodux
rodux was really useful for me to manage states, and rodux does not handle the state changing for you, you can write the dispatch code however you like, and getting the state is effortless! and then roactrodux came in, I use it nowadays because its really useful for me to pass states into my gui components, but there was no tutorials on this amazing module so lets begin shall we?
So lets start, if you want to install roact, roactrodux, and rodux, the easiest way to install it is wally or rbxms if you dont use an external code editor, if you want to know how to use wally, just go to this tutorial for the sake of this one.
Your wally.toml packages:
roact = "roblox/roact@1.4.2"
roactRodux = "roblox/roact-rodux@0.3.0"
rodux = "roblox/rodux@3.0.0"
Or alternatively go to these installation guides:
https://roblox.github.io/roact/guide/installation/
https://roblox.github.io/rodux/introduction/installation/
https://roblox.github.io/roact-rodux/guide/installation/
ok enough with the installations, lets start.
so
we create a folder in StarterPlayer > StarterPlayerScripts
name it any name you want
ill name it “coolgui” for this tutorial
now we create a main localscript (known as main.client.lua in an external ide) inside the folder
lets require the needed dependancies
local roact = require(path.to.roact)
local roactRodux = require(path.to.roactrodux)
next, we forgot something important, we need the component and the store
add two folders inside a gui folder, name one “components” and the other “stores”
now we create a modulescript in “components” and call it “coolguicomponent” or anything u like
in an external ide, name it “coolguicomponent.lua”
clear the modulescript
so we require roact only because thats what the component only needs
local roact = require(path.to.roact)
Now this is obviously a component, lets make a component
local roact = require(path.to.roact)
local component = roact.Component:extend("coolcomponent")
return component
we should also return the component because the main script will require the component script for it.
lets add in our “common” stuff we do in roact components, so we add :render()
local roact = require(path.to.roact)
local component = roact.Component:extend("coolcomponent")
function component:render()
end
return component
we need to return an element for render, lets do it.
local roact = require(path.to.roact)
local component = roact.Component:extend("coolcomponent")
function component:render()
return roact.createElement("TextLabel", {
Text = self.props.text,
Size = UDim2.fromScale(1, 0.1)
})
end
return component
we ended writing our component script.
Why do we do self.props.text instead of self.state.text, isnt rodux a state management module?
because roactrodux simply passes the state as props and does not set the state. rodux is still a state management module.
our rodux state will contain a value called “text” containing our text value. thats why i called it self.props.text
Ok, now we move to the stores folder.
create a modulescript called “mycoolstore” and in an external ide, “mycoolstore.lua”
clear it, require rodux only because thats what we need for the store.
local rodux = require(path.to.rodux)
this one is simpler than the component modulescript, we basically return a new rodux store.
local rodux = require(path.to.rodux)
return rodux.Store.new(function(state, action)
end)
what is state and action we set as the arguments of the function we are gonna pass into rodux.store.new?
state, is our current state of the current rodux store.
action, is the dispatch argument we are gonna fetch when we :dispatch() our store which is what we are gonna do later.
the function is called whenever :dispatch() is called.
lets make a default state incase there is none.
local rodux = require(path.to.rodux)
return rodux.Store.new(function(state, action)
state = state or {
text = "Hello"
}
return state
end)
why do we return the state
returning the state changes the state to the returned state
now we are gonna check something called action.type, which is the type of the dispatch
local rodux = require(path.to.rodux)
return rodux.Store.new(function(state, action)
state = state or {
text = "Hello"
}
if action.type == "changeText" then
state.text = action.text
end
return state
end)
uhh this is not like the code the rodux docs uses. explain?
i return the state at the end of the code, so i dont need to return it in our current if condition block, instead i directly change the state instead of returning it directly to make life easier.
okay, we basically finished our rodux store code! change the function to anything you like as long as you return le state
lets open the main script again
local roact = require(path.to.roact)
local roactRodux = require(path.to.roactrodux)
really empty. lets require our store and component we made!
local roact = require(path.to.roact)
local roactRodux = require(path.to.roactrodux)
local stores = script.Parent:WaitForChild("stores")
local components = script.Parent:WaitForChild("components")
local myStore = require(stores.mycoolstore)
local myComponent = require(components.coolguicomponent)
now, before we create our roact tree, we gotta make roactrodux connect our component
roactrodux.connect returns a function after .connect called,
lets do this.
local roact = require(path.to.roact)
local roactRodux = require(path.to.roactrodux)
local stores = script.Parent:WaitForChild("stores")
local components = script.Parent:WaitForChild("components")
local myStore = require(stores.mycoolstore)
local myComponent = require(components.coolguicomponent)
myComponent = roactRodux.connect(
function(state, props)
return state
end,
function(dispatch)
return {}
end
)(myComponent)
i have millions of questions, whats that first argument of roactrodux.connect, and why is there a second?
the first argument of roactrodux.connect is for mapping our rodux state to props, sometimes its nessecary to actually map it, but our state has the exact same name as expected props so we dont need to map anything, hence why i directly returned state.
its run every time the store dispatches.
the second argument is the same except its runned only once, we use it for event connections. which we arent using yet, so we leave the table returned empty
why do we call the function with our component as an arg?
because .connect returns a function so we are gonna use it!
now the tree comes in
local roact = require(path.to.roact)
local roactRodux = require(path.to.roactrodux)
local stores = script.Parent:WaitForChild("stores")
local components = script.Parent:WaitForChild("components")
local myStore = require(stores.mycoolstore)
local myComponent = require(components.coolguicomponent)
myComponent = roactRodux.connect(
function(state, props)
return state
end,
function(dispatch)
return {}
end
)(myComponent)
local tree = roact.createElement("ScreenGui", {
ResetOnSpawn = false
}, {
textlabel = roact.createElement(myComponent)
})
local roactroduxTree = roact.createElement(roactRodux.StoreProvider, {
store = myStore
}, {
coolgui = roact.createElement(tree)
})
roact.mount(roactroduxTree, game.Players.LocalPlayer.PlayerGui)
congrats! we finished our gui, lets try dispatching our store to change my text on the hint
local roact = require(path.to.roact)
local roactRodux = require(path.to.roactrodux)
local stores = script.Parent:WaitForChild("stores")
local components = script.Parent:WaitForChild("components")
local myStore = require(stores.mycoolstore)
local myComponent = require(components.coolguicomponent)
myComponent = roactRodux.connect(
function(state, props)
return state
end,
function(dispatch)
return {}
end
)(myComponent)
local tree = roact.createElement("ScreenGui", {
ResetOnSpawn = false
}, {
textlabel = roact.createElement(myComponent)
})
local roactroduxTree = roact.createElement(roactRodux.StoreProvider, {
store = myStore
}, {
coolgui = roact.createElement(tree)
})
roact.mount(roactroduxTree, game.Players.LocalPlayer.PlayerGui)
task.wait(5)
myStore:dispatch({
type = "changeText",
text = "Changed"
})
now when you play, you should see a gui, but after 5 seconds, the text will be changed to “Changed”, Enjoy.
The .connect second argument should be very easy to find out how it works, but if you dont know, hit up a reply or dm and ill explain it too
Enjoy.