I'm using React.js with TypeScript. Is there any way to create React components that inherit from other components but have some additional props/states?
What I'm trying to achieve is something like this:
interface BaseStates {
a: number;
}
class GenericBase<S extends BaseStates> extends React.Component<void, S> {
protected getBaseInitialState(): BaseStates {
return { a: 3 };
}
}
class Base extends GenericBase<BaseStates> {
getInitialState(): BaseStates {
return super.getBaseInitialState();
}
}
interface DerivedStates extends BaseStates {
b: number;
}
class Derived extends GenericBase<DerivedStates> {
getInitialState(): DerivedStates {
var initialStates = super.getBaseInitialState() as DerivedStates; // unsafe??
initialStates.b = 4;
return initialStates
}
}
However, this will fail if I call this.setState in Derived, I get a TypeScript error (parameter of type DerivedStates is not assignable to type S). I suppose this is not a TypeScript-specific thing, but a general limitation of mixing inheritance with generics (?). Is there any type-safe workaround for this?
UPDATE
The solution I settled on (based on the answer of David Sherret):
interface BaseStates {
a: number;
}
class GenericBase<S extends BaseStates> extends React.Component<void, S> {
constructor() {
super();
this.state = this.getInitialState();
}
getInitialState(): S {
return { a: 3 } as S;
}
update() {
this.setState({ a: 7 } as S);
}
}
interface DerivedStates extends BaseStates {
b: number;
}
class Derived extends GenericBase<DerivedStates> {
getInitialState(): DerivedStates {
var initialStates = super.getInitialState();
initialStates.b = 4;
return initialStates;
}
update() {
this.setState({ a: 7, b: 4 });
}
}
You can set only a few properties of the state at once in Derived by using a type assertion:
this.setState({ b: 4 } as DerivedStates); // do this
this.setState({ a: 7 } as DerivedStates); // or this
this.setState({ a: 7, b: 4 }); // or this
By the way, no need to have different names for getInitialState... you could just do:
class GenericBase<S extends BaseStates> extends React.Component<void, S> {
constructor() {
super();
this.state = this.getInitialState();
}
protected getInitialState() {
return { a: 3 } as BaseStates as S;
}
}
class Derived extends GenericBase<DerivedStates> {
getInitialState() {
var initialStates = super.getInitialState();
initialStates.b = 4;
return initialStates;
}
}
import { Component } from 'react'
abstract class TestComponent<P = {}, S = {}, SS = any> extends Component<P, S, SS> {
abstract test(): string
}
type Props = {
first: string,
last: string,
}
type State = {
fullName: string,
}
class MyTest extends TestComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
fullName: `${props.first} ${props.last}`
}
}
test() {
const { fullName } = this.state
return fullName
}
}
Related
I have this class that exposes remote configs to others. I thought by creating a class, I would just mock it when testing others that use it but so far, firebase is blocking me. Not sure what I am doing wrong exactly.
class AppRemoteConfig #Inject constructor() {
private var remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig
private fun setListeningInterval(): Long {
if (BuildConfig.DEBUG){
return 0;
}
return 86400;
}
init {
val configSettings = remoteConfigSettings {
minimumFetchIntervalInSeconds = setListeningInterval()
}
remoteConfig.setConfigSettingsAsync(configSettings)
remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)
remoteConfig.fetchAndActivate()
.addOnCompleteListener(OnCompleteListener{
if (it.isSuccessful) {
remoteConfig.activate()
}
})
}
fun getString(key: String): String {
return this.remoteConfig.getString(key)
}
}
Now a class uses it this way:
class GetRData #Inject constructor(
private val _remoteConfig: AppRemoteConfig
) {
operator fun invoke(key): String {
try {
return _remoteConfig.getString(key)
} catch(ex: Exception){
return ""
}
return ""
}
}
Now I want to test GetRData class but I get the error: Default FirebaseApp is not initialized in this process null. Make sure to call FirebaseApp.initializeApp(Context) first.
here is what I have tried:
class GetRDataTest {
private var appRemoteConfig = mockk<AppRemoteConfig>(relaxed = true)
private lateinit var getRData : GetRData
#Before
fun setUp(){
getRData = GetRData(appRemoteConfig)
}
#Test
fun `Should get string value`() {
every { appRemoteConfig.getString("status") } returns "red"
val result = getRData.invoke("status")
verify { appRemoteConfig.getString("status") }
Truth.assertThat(result).isEqualTo("red")
}
}
I have a problem with the following code snippet:
(Some functions' bodies are ommited for clear view)
fun collectLinks(page: Page): List<String> {
return LinksCrawler().run {
page.accept(this)
this.links
}
}
class LinksCrawler {
private var _links = mutableListOf<String>()
val links
get() = _links.toList()
fun visit(page: Page) { (...) }
fun visit(container: Container) = { (...) }
private fun visit(elements: List<HtmlElement>){ (...) }
}
When I invoke collectLinks() I get
Visitor$LinksCrawler: method 'void ()' not found
(where Visitor is my filename)
As far as I believe, problem would be caused by scope function .run(), maybe that it has no initialisation code that would do sth with LinksCrawler, but correct me if I am wrong.
I do it in .kts file, if it has any meaning. In overall, it is supposed to be an example for a Visitor design pattern. Full file code below:
import Visitor.HtmlElement.Image as Image
import Visitor.HtmlElement.Link as Link
import Visitor.HtmlElement.Table as Table
import Visitor.HtmlElement.Container as Container
main()
// ---------------
fun main() {
val page = Page(Container(Image(), Link(), Image()),
Table(),
Link(),
Container(Table(), Link()),
Container(Image(), Container(Image(), Link())))
println(collectLinks(page))
}
fun collectLinks(page: Page): List<String> {
return LinksCrawler().run {
page.accept(this)
this.links
}
}
class LinksCrawler {
private var _links = mutableListOf<String>()
val links
get() = _links.toList()
fun visit(page: Page) {
visit(page.elements)
}
fun visit(container: Container) = visit(container.elements)
private fun visit(elements: List<HtmlElement>){
for (e in elements) {
when (e) {
is Container -> e.accept(this)
is Link -> _links.add(e.href)
is Image -> _links.add(e.src)
else -> {}
}
}
}
}
fun Container.accept(feature: LinksCrawler) {
feature.visit(this)
}
fun Page.accept(feature: LinksCrawler) = feature.visit(this)
class Page(val elements: MutableList<HtmlElement> = mutableListOf()) {
constructor(vararg elements: HtmlElement) : this(mutableListOf()) {
for (s in elements) {
this.elements.add(s)
}
}
}
sealed class HtmlElement {
class Container(val elements: MutableList<HtmlElement> = mutableListOf()) : HtmlElement() {
constructor(vararg units: HtmlElement) : this(mutableListOf()) {
for (u in units) {
this.elements.add(u)
}
}
}
class Image : HtmlElement() {
val src: String
get() = "http://image"
}
class Link : HtmlElement() {
val href : String
get() = "http://link"
}
class Table : HtmlElement()
}
I have two data classes which are very similar to each other.
I want to write a parent class for both of them so they can inherit the common functionality.
My problem is that some methods I want to inherit are needed to be inside companion object.
A) data class Link
import org.json.JSONArray
import org.json.JSONObject
data class Link(
val name: String,
val url: String
) {
var selected: Boolean = false
fun toggle() { selected = selected.not() }
companion object {
fun fromJson(obj: JSONObject): Link = with(obj) {
Link(getString("name"), getString("url"))
}
fun fromJson(arr: JSONArray): List<Link> = with(arr) {
List(length()) {
fromJson(getJSONObject(it))
}
}
fun toJson(list: List<Link>): JSONArray = JSONArray().apply {
list.forEach {
put(it.toJson())
}
}
}
fun toJson(): JSONObject = JSONObject().apply {
put("name", name)
put("url", url)
}
}
B) data class DownloadStatus
import org.json.JSONArray
import org.json.JSONObject
data class DownloadStatus(
val name: String,
val url: String,
val path: String,
var progress: Int = 0
) {
var selected: Boolean = false
fun toggle() { selected = selected.not() }
companion object {
fun fromJson(obj: JSONObject): DownloadStatus = with(obj) {
DownloadStatus(getString("name"), getString("url"), getString("path"), getInt("progress"))
}
fun fromJson(arr: JSONArray): List<DownloadStatus> = with(arr) {
List(length()) {
fromJson(getJSONObject(it))
}
}
fun toJson(list: List<DownloadStatus>): JSONArray = JSONArray().apply {
list.forEach {
put(it.toJson())
}
}
}
fun toJson(): JSONObject = JSONObject().apply {
put("name", name)
put("url", url)
put("path", path)
put("progress", progress)
}
}
abstract class Parent
abstract class Parent {
var selected: Boolean = false
fun toggle() { selected = selected.not() }
companion object {
}
abstract fun toJson(): JSONObject
}
I am stuck here. How to put the methods in companion object of the parent class?
I have this:
val navigateToMainFragmentEvent: StateFlow<State<Event<Boolean>>>
if (navigateToMainFragmentEvent.collectAsState().value is State.TriggerState) {
(viewModel.navigateToMainFragmentEvent.collectAsState().value
as State.TriggerState).data.getContentIfNotHandled()
?.let {
if (it) {
Timber.tag("Nurs").d("collect as state ")
navController.popBackStack()
navController.navigate(MAIN_SCRENN)
}
}
}
is it possible to shorten with generics the if statement?
val state = navigateToMainFragmentEvent.value
if (state is State.TriggerState) {
state.data.getContentIfNotHandled()?.let {
// do sth
}
}
As an advice: You can define ifNotHandled method with a lambda argument in your Event class to more shortening:
fun ifNotHandled(callback: () -> T) {
if (!hasBeenHandled) {
hasBeenHandled = true
callback.invoke(content)
}
}
val state = navigateToMainFragmentEvent.value
if (state is State.TriggerState) {
state.data.ifNotHandled {
// do sth
}
}
My application needs to permit additions to the listview. I've figured out how I can dynamically add to a listview by using observableArrayList. If I click on the button, an item gets added to the list and displayed.
Now I'm struggling to add a click handler (I want to handle the event that happens when someone clicks on any item within the list view). Where do I do this?
Here is my code.
package someapp
import javafx.collections.FXCollections
import javafx.geometry.Pos
import javafx.scene.layout.VBox
import javafx.scene.text.FontWeight
import tornadofx.*
class MyApp : App(HelloWorld::class) {
}
class HelloWorld : View() {
val leftSide: LeftSide by inject()
override val root = borderpane {
left = leftSide.root
}
}
class LeftSide: View() {
var requestView: RequestView by singleAssign()
override val root = VBox()
init {
with(root) {
requestView = RequestView()
this += requestView
this += button("Add Item") {
action {
requestView.responses.add( Request( "example.com",
"/foo/bar",
"{ \"foo\" : \"bar\"}".toByteArray()))
}
}
}
}
}
class RequestView : View() {
val responses = FXCollections.observableArrayList<Request>(
)
override val root = listview(responses) {
cellFormat {
graphic = cache {
form {
fieldset {
label(it.hostname) {
alignment = Pos.CENTER_RIGHT
style {
fontSize = 22.px
fontWeight = FontWeight.BOLD
}
}
field("Path") {
label(it.path)
}
}
}
}
}
}
}
class Request(val hostname: String, val path: String, val body: ByteArray) {
}
To configure a callback when an item in a ListView is selected, use the onUserSelect callback:
onUserSelect {
information("You selected $it")
}
You can optionally pass how many clicks constitutes a select as well, default is 2:
onUserSelect(1) {
information("You selected $it")
}
You are using some outdated constructs in your code, here is an updated version converted to best practices :)
class MyApp : App(HelloWorld::class)
class HelloWorld : View() {
override val root = borderpane {
left(LeftSide::class)
}
}
class LeftSide : View() {
val requestView: RequestView by inject()
override val root = vbox {
add(requestView)
button("Add Item").action {
requestView.responses.add(Request("example.com",
"/foo/bar",
"""{ "foo" : "bar"}""".toByteArray()))
}
}
}
class RequestView : View() {
val responses = FXCollections.observableArrayList<Request>()
override val root = listview(responses) {
cellFormat {
graphic = cache {
form {
fieldset {
label(it.hostname) {
alignment = Pos.CENTER_RIGHT
style {
fontSize = 22.px
fontWeight = FontWeight.BOLD
}
}
field("Path") {
label(it.path)
}
}
}
}
}
onUserSelect(1) {
information("You selected $it")
}
}
}
class Request(val hostname: String, val path: String, val body: ByteArray)