diff --git a/Example/Tokamak.xcodeproj/project.pbxproj b/Example/Tokamak.xcodeproj/project.pbxproj index d31c32c7..357ba76d 100644 --- a/Example/Tokamak.xcodeproj/project.pbxproj +++ b/Example/Tokamak.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ A6D5AF87221B131400DBF186 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D5AF86221B131400DBF186 /* Image.swift */; }; C449B806DFEE55B6CEE6478C /* libPods-TokamakDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B96B435A9D67621D318616E /* libPods-TokamakDemo.a */; }; D11DB6432219C03000013FC3 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11DB6422219C03000013FC3 /* Timer.swift */; }; + D1BB3D302223F6B400C30062 /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BB3D2F2223F6B400C30062 /* Animation.swift */; }; D1BFAF772215795900845EA0 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF762215795900845EA0 /* Router.swift */; }; D1BFAF792215800A00845EA0 /* Counter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF782215800A00845EA0 /* Counter.swift */; }; D1BFAF7B22158B4000845EA0 /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF7A22158B4000845EA0 /* List.swift */; }; @@ -43,6 +44,7 @@ A9EEF813955DAEEFE1D52ED4 /* Pods-TokamakDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TokamakDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TokamakDemo/Pods-TokamakDemo.debug.xcconfig"; sourceTree = ""; }; C6DA99382B6892EAB361742F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; D11DB6422219C03000013FC3 /* Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = ""; }; + D1BB3D2F2223F6B400C30062 /* Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Animation.swift; sourceTree = ""; }; D1BFAF762215795900845EA0 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; D1BFAF782215800A00845EA0 /* Counter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Counter.swift; sourceTree = ""; }; D1BFAF7A22158B4000845EA0 /* List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = ""; }; @@ -162,6 +164,7 @@ D1F2C3262214407B008358DC /* TableModal.swift */, D11DB6422219C03000013FC3 /* Timer.swift */, A6D5AF86221B131400DBF186 /* Image.swift */, + D1BB3D2F2223F6B400C30062 /* Animation.swift */, ); path = Components; sourceTree = ""; @@ -283,6 +286,7 @@ D1F7185D2215A4A1004E5951 /* LayerProps.swift in Sources */, D1BFAF792215800A00845EA0 /* Counter.swift in Sources */, D1F718612215A617004E5951 /* Modals.swift in Sources */, + D1BB3D302223F6B400C30062 /* Animation.swift in Sources */, D1F7185522159EAD004E5951 /* DatePickers.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Example/Tokamak/Components/Animation.swift b/Example/Tokamak/Components/Animation.swift new file mode 100644 index 00000000..4da99b97 --- /dev/null +++ b/Example/Tokamak/Components/Animation.swift @@ -0,0 +1,67 @@ +// +// Animation.swift +// TokamakDemo +// +// Created by Max Desiatov on 25/02/2019. +// Copyright © 2019 Tokamak. All rights reserved. +// + +import Tokamak + +struct Animation: CompositeComponent { + private static let colors: [(Color, String)] = [ + (.white, "white"), + (.red, "red"), + (.green, "green"), + (.blue, "blue"), + ] + + typealias Props = Null + typealias Children = [AnyNode] + + static func render(props: Null, children: Children, hooks: Hooks) -> AnyNode { + let previousColor = hooks.state(0) + let currentColor = hooks.state(0) + let ref = hooks.ref(type: UIView.self) + + hooks.effect(currentColor.value) { + guard let view = ref.value else { return } + + guard currentColor.value != previousColor.value else { + view.backgroundColor = UIColor(colors[currentColor.value].0) + return + } + + UIView.animate(withDuration: 0.5, animations: { + view.backgroundColor = UIColor(colors[currentColor.value].0) + }, completion: { _ in + previousColor.set(currentColor.value) + }) + } + + return View.node( + .init(Style( + Edges.equal(to: .parent), + backgroundColor: .white + )), + StackView.node(.init( + Edges.equal(to: .safeArea), + axis: .vertical, + distribution: .fillEqually + ), [ + View.node(ref: ref, .init(), children), + SegmentedControl.node( + .init( + value: currentColor.value, + valueHandler: Handler { + // sometimes UISegmentedControl allows deselecting all segments + guard $0 >= 0 else { return } + currentColor.set($0) + } + ), + colors.map { $0.1 } + ), + ]) + ) + } +} diff --git a/Example/Tokamak/Components/Modals.swift b/Example/Tokamak/Components/Modals.swift index 07dc7981..ffe7f63c 100644 --- a/Example/Tokamak/Components/Modals.swift +++ b/Example/Tokamak/Components/Modals.swift @@ -29,42 +29,18 @@ struct NavigationModal: PureLeafComponent { } } -struct SimpleModal: LeafComponent { +struct SimpleModal: PureLeafComponent { struct Props: Equatable { let isPresented: State } - private static let colors: [(Color, String)] = [ - (.white, "white"), - (.red, "red"), - (.green, "green"), - (.blue, "blue"), - ] - - static func render(props: Props, hooks: Hooks) -> AnyNode { - let backgroundColor = hooks.state(0) - + static func render(props: Props) -> AnyNode { return props.isPresented.value ? ModalPresenter.node( - View.node( - .init(Style( - Edges.equal(to: .parent), - backgroundColor: colors[backgroundColor.value].0 - )), - StackView.node(.init( - Edges.equal(to: .parent), - axis: .vertical, - distribution: .fillEqually - ), [ - Button.node(.init( - onPress: Handler { props.isPresented.set(false) } - ), "Close Modal"), - SegmentedControl.node( - .init(value: backgroundColor.value, - valueHandler: Handler(backgroundColor.set)), - colors.map { $0.1 } - ), - ]) - ) + Animation.node(Null(), + Button.node(.init( + Style(Center.equal(to: .parent)), + onPress: Handler { props.isPresented.set(false) } + ), "Close Modal")) ) : Null.node() } } diff --git a/Example/Tokamak/Components/Timer.swift b/Example/Tokamak/Components/Timer.swift index 8d0ab411..7fd98d38 100644 --- a/Example/Tokamak/Components/Timer.swift +++ b/Example/Tokamak/Components/Timer.swift @@ -17,7 +17,7 @@ struct TimerCounter: LeafComponent { let timer = hooks.ref(type: Timer.self) let interval = hooks.state(1.0) - hooks.effect(interval.value) { () -> () -> () in + hooks.finalizedEffect(interval.value) { timer.value = Timer.scheduledTimer( withTimeInterval: interval.value, repeats: true diff --git a/Example/Tokamak/Router.swift b/Example/Tokamak/Router.swift index 3cb7618d..82b57e55 100644 --- a/Example/Tokamak/Router.swift +++ b/Example/Tokamak/Router.swift @@ -18,6 +18,7 @@ enum AppRoute: String, CaseIterable { case layerProps = "Layer Props" case timer case image + case animation } extension AppRoute: CustomStringConvertible { @@ -59,6 +60,8 @@ struct Router: NavigationRouter { result = TimerCounter.node() case .image: result = ImageExample.node() + case .animation: + result = Animation.node() } return NavigationItem.node( diff --git a/Sources/Tokamak/AnyNode.swift b/Sources/Tokamak/AnyNode.swift index af2913c4..0bd67ff7 100644 --- a/Sources/Tokamak/AnyNode.swift +++ b/Sources/Tokamak/AnyNode.swift @@ -77,6 +77,12 @@ extension Component where Children == [AnyNode] { } } +extension Component where Props == Null, Children == [AnyNode] { + public static func node() -> AnyNode { + return node(Null(), []) + } +} + extension Component where Props: Default, Props.DefaultValue == Props, Children == [AnyNode] { public static func node(_ child: AnyNode) -> AnyNode { @@ -127,3 +133,23 @@ extension RefComponent { ) } } + +extension RefComponent where Children == [AnyNode] { + public static func node( + ref: Ref, + _ props: Props, + _ child: AnyNode + ) -> AnyNode { + return node(ref: ref, props, [child]) + } + + public static func node(ref: Ref, _ props: Props) -> AnyNode { + return node(ref: ref, props, []) + } +} + +extension RefComponent where Children == Null { + public static func node(ref: Ref, _ props: Props) -> AnyNode { + return node(ref: ref, props, Null()) + } +} diff --git a/Sources/Tokamak/Hooks/Effect.swift b/Sources/Tokamak/Hooks/Effect.swift index bcea9260..5fbfea8c 100644 --- a/Sources/Tokamak/Hooks/Effect.swift +++ b/Sources/Tokamak/Hooks/Effect.swift @@ -15,7 +15,7 @@ extension Hooks { closure should return a cleanup closure to be executed before the next call to `render` or when a component is unmounted. */ - public func effect(closure: @escaping () -> () -> ()) { + public func finalizedEffect(closure: @escaping () -> () -> ()) { scheduleEffect(nil, closure) } @@ -55,7 +55,10 @@ extension Hooks { trigger effect execution (unsubscribing from updates on old user ID and subscribing for new user ID) when the ID has changed. */ - public func effect(_ observed: T, closure: @escaping () -> () -> ()) + public func finalizedEffect( + _ observed: T, + closure: @escaping () -> () -> () + ) where T: Equatable { scheduleEffect(AnyEquatable(observed), closure) } diff --git a/Sources/Tokamak/Hooks/State.swift b/Sources/Tokamak/Hooks/State.swift index 7c848480..9f310c21 100644 --- a/Sources/Tokamak/Hooks/State.swift +++ b/Sources/Tokamak/Hooks/State.swift @@ -5,16 +5,6 @@ // Created by Max Desiatov on 09/02/2019. // -/// Formalizes an update to a value with a given action. Note that `update` -/// function is mutating here to allow efficient in-place updates. As long as -/// `Updatable` is implemented on a value type, this still allows freezing it -/// into an immutable value when needed. -public protocol Updatable { - associatedtype Action - - mutating func update(_ action: Action) -} - typealias Updater = (inout T) -> () /** Note that `set` functions are not `mutating`, they never update the @@ -54,14 +44,6 @@ public struct State { extension State: Equatable where T: Equatable {} -extension State where T: Updatable { - /// For any `Reduceable` state you can dispatch an `Action` to reduce that - /// state to a different value. - public func set(_ action: T.Action) { - updateHandler.value { $0.update(action) } - } -} - extension Hooks { /** Allows a component to have its own state and to be updated when the state changes. Returns a very simple state container, which on initial call of diff --git a/Sources/Tokamak/MountedComponents/MountedHostComponent.swift b/Sources/Tokamak/MountedComponents/MountedHostComponent.swift index 16f47362..2e9c85f1 100644 --- a/Sources/Tokamak/MountedComponents/MountedHostComponent.swift +++ b/Sources/Tokamak/MountedComponents/MountedHostComponent.swift @@ -42,6 +42,8 @@ public final class MountedHostComponent: MountedComponent { self.target = target + reconciler.renderer?.update(target: target, with: self) + switch node.children.value { case let nodes as [AnyNode]: mountedChildren = nodes.map { $0.makeMountedComponent(target) } diff --git a/Sources/TokamakTestRenderer/TestRenderer.swift b/Sources/TokamakTestRenderer/TestRenderer.swift index cff47077..660c57b9 100644 --- a/Sources/TokamakTestRenderer/TestRenderer.swift +++ b/Sources/TokamakTestRenderer/TestRenderer.swift @@ -28,8 +28,6 @@ public final class TestRenderer: Renderer { let result = TestView(component.node) parent.add(subview: result) - update(target: result, with: component) - return result } diff --git a/Sources/TokamakUIKit/Boxes/ViewBox.swift b/Sources/TokamakUIKit/Boxes/ViewBox.swift index 5489076e..766f8edf 100644 --- a/Sources/TokamakUIKit/Boxes/ViewBox.swift +++ b/Sources/TokamakUIKit/Boxes/ViewBox.swift @@ -19,4 +19,8 @@ class ViewBox: ViewControllerBox { super.init(viewController, node) } + + override var refTarget: Any { + return view + } } diff --git a/Sources/TokamakUIKit/Boxes/ViewControllerBox.swift b/Sources/TokamakUIKit/Boxes/ViewControllerBox.swift index 5855058a..a30b4d18 100644 --- a/Sources/TokamakUIKit/Boxes/ViewControllerBox.swift +++ b/Sources/TokamakUIKit/Boxes/ViewControllerBox.swift @@ -19,4 +19,8 @@ class ViewControllerBox: UITarget { override var viewController: UIViewController { return containerViewController } + + override var refTarget: Any { + return containerViewController + } } diff --git a/Sources/TokamakUIKit/Components/Host/Button.swift b/Sources/TokamakUIKit/Components/Host/Button.swift index 9ceb8ee8..051fe665 100644 --- a/Sources/TokamakUIKit/Components/Host/Button.swift +++ b/Sources/TokamakUIKit/Components/Host/Button.swift @@ -19,6 +19,7 @@ final class TokamakButton: UIButton, Default { extension Button: UIControlComponent { typealias Target = TokamakButton + public typealias RefTarget = UIButton static func update(control box: ControlBox, _ props: Button.Props, diff --git a/Sources/TokamakUIKit/Components/Host/DatePicker.swift b/Sources/TokamakUIKit/Components/Host/DatePicker.swift index 2c357b5f..3fc5ab8c 100644 --- a/Sources/TokamakUIKit/Components/Host/DatePicker.swift +++ b/Sources/TokamakUIKit/Components/Host/DatePicker.swift @@ -27,6 +27,7 @@ final class TokamakDatePicker: UIDatePicker, Default, ValueStorage { extension DatePicker: UIValueComponent { typealias Target = TokamakDatePicker + public typealias RefTarget = UIDatePicker static func update(valueBox: ValueControlBox, _ props: DatePicker.Props, diff --git a/Sources/TokamakUIKit/Components/Host/Image.swift b/Sources/TokamakUIKit/Components/Host/Image.swift index af49503a..e6b3c0fe 100644 --- a/Sources/TokamakUIKit/Components/Host/Image.swift +++ b/Sources/TokamakUIKit/Components/Host/Image.swift @@ -28,6 +28,8 @@ extension UIImage.RenderingMode { } extension Image: UIViewComponent { + public typealias RefTarget = UIImageView + static func update( view box: ViewBox, _ props: Image.Props, diff --git a/Sources/TokamakUIKit/Components/Host/Label.swift b/Sources/TokamakUIKit/Components/Host/Label.swift index fa915d14..3e51905e 100644 --- a/Sources/TokamakUIKit/Components/Host/Label.swift +++ b/Sources/TokamakUIKit/Components/Host/Label.swift @@ -32,6 +32,8 @@ extension NSTextAlignment { } extension Label: UIViewComponent { + public typealias RefTarget = UILabel + static func update(view box: ViewBox, _ props: Label.Props, _ children: String) { diff --git a/Sources/TokamakUIKit/Components/Host/ListView.swift b/Sources/TokamakUIKit/Components/Host/ListView.swift index f20cfa5c..f54b27c9 100644 --- a/Sources/TokamakUIKit/Components/Host/ListView.swift +++ b/Sources/TokamakUIKit/Components/Host/ListView.swift @@ -9,6 +9,8 @@ import Tokamak import UIKit extension ListView: UIViewComponent { + public typealias RefTarget = UITableView + static func box( for view: TokamakTableView, _ viewController: UIViewController, diff --git a/Sources/TokamakUIKit/Components/Host/NavigationItem.swift b/Sources/TokamakUIKit/Components/Host/NavigationItem.swift index fd5cbccf..3b5263b8 100644 --- a/Sources/TokamakUIKit/Components/Host/NavigationItem.swift +++ b/Sources/TokamakUIKit/Components/Host/NavigationItem.swift @@ -41,7 +41,6 @@ extension NavigationItem: UIHostComponent { let viewController = UIViewController() let result = ViewControllerBox(viewController, component.node) - update(target: result, node: component.node) parent.containerViewController.pushViewController( viewController, diff --git a/Sources/TokamakUIKit/Components/Host/SegmentedControl.swift b/Sources/TokamakUIKit/Components/Host/SegmentedControl.swift index 13d1c728..5bc6c5d5 100644 --- a/Sources/TokamakUIKit/Components/Host/SegmentedControl.swift +++ b/Sources/TokamakUIKit/Components/Host/SegmentedControl.swift @@ -25,6 +25,7 @@ final class TokamakSegmentedControl: UISegmentedControl, Default, ValueStorage { extension SegmentedControl: UIValueComponent { typealias Target = TokamakSegmentedControl + public typealias RefTarget = UISegmentedControl static func update(valueBox: ValueControlBox, _ props: SegmentedControl.Props, diff --git a/Sources/TokamakUIKit/Components/Host/Slider.swift b/Sources/TokamakUIKit/Components/Host/Slider.swift index dd1c7127..e47f0ec6 100644 --- a/Sources/TokamakUIKit/Components/Host/Slider.swift +++ b/Sources/TokamakUIKit/Components/Host/Slider.swift @@ -16,6 +16,7 @@ final class TokamakSlider: UISlider, Default, ValueStorage { extension Slider: UIValueComponent { typealias Target = TokamakSlider + public typealias RefTarget = UISlider static func update(valueBox: ValueControlBox, _ props: Slider.Props, diff --git a/Sources/TokamakUIKit/Components/Host/StackView.swift b/Sources/TokamakUIKit/Components/Host/StackView.swift index 85c3da7d..1c30fb64 100644 --- a/Sources/TokamakUIKit/Components/Host/StackView.swift +++ b/Sources/TokamakUIKit/Components/Host/StackView.swift @@ -60,6 +60,8 @@ extension UIStackView.Distribution { } extension StackView: UIViewComponent { + public typealias RefTarget = UIStackView + static func update(view box: ViewBox, _ props: StackView.Props, _: [AnyNode]) { diff --git a/Sources/TokamakUIKit/Components/Host/Stepper.swift b/Sources/TokamakUIKit/Components/Host/Stepper.swift index 6a5952bd..4c3ad717 100644 --- a/Sources/TokamakUIKit/Components/Host/Stepper.swift +++ b/Sources/TokamakUIKit/Components/Host/Stepper.swift @@ -16,6 +16,7 @@ final class TokamakStepper: UIStepper, Default, ValueStorage { extension Stepper: UIValueComponent { typealias Target = TokamakStepper + public typealias RefTarget = UIStepper static func update(valueBox: ValueControlBox, _ props: Stepper.Props, diff --git a/Sources/TokamakUIKit/Components/Host/Switch.swift b/Sources/TokamakUIKit/Components/Host/Switch.swift index 8078a210..be450c0c 100644 --- a/Sources/TokamakUIKit/Components/Host/Switch.swift +++ b/Sources/TokamakUIKit/Components/Host/Switch.swift @@ -27,6 +27,7 @@ final class TokamakSwitch: UISwitch, Default, ValueStorage { extension Switch: UIValueComponent { typealias Target = TokamakSwitch + public typealias RefTarget = UISwitch static func update(valueBox: ValueControlBox, _ props: Switch.Props, diff --git a/Sources/TokamakUIKit/Components/Host/View.swift b/Sources/TokamakUIKit/Components/Host/View.swift index e73c8b2a..67d1d206 100644 --- a/Sources/TokamakUIKit/Components/Host/View.swift +++ b/Sources/TokamakUIKit/Components/Host/View.swift @@ -15,6 +15,8 @@ final class TokamakView: UIView, Default { } extension View: UIViewComponent { + public typealias RefTarget = UIView + static func update(view: ViewBox, _ props: View.Props, _: [AnyNode]) {} diff --git a/Sources/TokamakUIKit/Components/Props/Color.swift b/Sources/TokamakUIKit/Components/Props/Color.swift index 400f92e5..fd17f0fd 100644 --- a/Sources/TokamakUIKit/Components/Props/Color.swift +++ b/Sources/TokamakUIKit/Components/Props/Color.swift @@ -9,7 +9,7 @@ import Tokamak import UIKit extension UIColor { - convenience init(_ color: Color) { + public convenience init(_ color: Color) { switch color.space { case .sRGB: self.init(red: CGFloat(color.red), diff --git a/Sources/TokamakUIKit/Components/Props/Event.swift b/Sources/TokamakUIKit/Components/Props/Event.swift index 1bd6d80e..3a88f675 100644 --- a/Sources/TokamakUIKit/Components/Props/Event.swift +++ b/Sources/TokamakUIKit/Components/Props/Event.swift @@ -9,7 +9,7 @@ import Tokamak import UIKit extension UIControl.Event { - init(_ value: Event) { + public init(_ value: Event) { switch value { case .touchDown: self = .touchDown diff --git a/Sources/TokamakUIKit/Components/Protocols/UIViewComponent.swift b/Sources/TokamakUIKit/Components/Protocols/UIViewComponent.swift index 2cd24d7a..319a5702 100644 --- a/Sources/TokamakUIKit/Components/Protocols/UIViewComponent.swift +++ b/Sources/TokamakUIKit/Components/Protocols/UIViewComponent.swift @@ -8,7 +8,7 @@ import Tokamak import UIKit -protocol UIViewComponent: UIHostComponent, HostComponent { +protocol UIViewComponent: UIHostComponent, RefComponent { associatedtype Target: UIView & Default static func update(view box: ViewBox, @@ -94,16 +94,6 @@ extension UIViewComponent where Target == Target.DefaultValue, component: UIKitRenderer.MountedHost, _ renderer: UIKitRenderer ) -> UITarget? { - guard let children = component.node.children.value as? Children else { - childrenAssertionFailure() - return nil - } - - guard let props = component.node.props.value as? Props else { - propsAssertionFailure() - return nil - } - let target = Target.defaultValue let result: ViewBox @@ -167,9 +157,6 @@ extension UIViewComponent where Target == Target.DefaultValue, parentAssertionFailure() } - applyStyle(result, props) - update(view: result, props, children) - return result } diff --git a/Sources/TokamakUIKit/UIKitRenderer.swift b/Sources/TokamakUIKit/UIKitRenderer.swift index 14131eee..0789994b 100644 --- a/Sources/TokamakUIKit/UIKitRenderer.swift +++ b/Sources/TokamakUIKit/UIKitRenderer.swift @@ -30,7 +30,11 @@ struct HackyProvider: SimpleCellProvider { class UITarget: Target { var viewController: UIViewController { - fatalError("viewController should be overriden in UITarget subclass") + fatalError("\(#function) should be overriden in UITarget subclass") + } + + var refTarget: Any { + fatalError("\(#function) should be overriden in UITarget subclass") } } @@ -79,6 +83,12 @@ final class UIKitRenderer: Renderer { rendererComponent.update(target: target, node: component.node) + + guard + let componentType = component.type as? AnyRefComponent.Type, + let anyRef = component.node.ref else { return } + + componentType.update(ref: anyRef, with: target.refTarget) } func unmount( diff --git a/Tests/TokamakTests/HooksTests.swift b/Tests/TokamakTests/HooksTests.swift index 0320a5e3..2fbfbdeb 100644 --- a/Tests/TokamakTests/HooksTests.swift +++ b/Tests/TokamakTests/HooksTests.swift @@ -14,22 +14,6 @@ extension Button: RefComponent { public typealias RefTarget = TestView } -extension Int: Updatable { - public enum Action { - case increment - case decrement - } - - public mutating func update(_ action: Int.Action) { - switch action { - case .decrement: - self -= 1 - case .increment: - self += 1 - } - } -} - private extension Hooks { func custom() -> State { return state(42) @@ -42,7 +26,6 @@ struct Test: LeafComponent { static func render(props: Null, hooks: Hooks) -> AnyNode { let state1 = hooks.custom() let state2 = hooks.custom() - let state3 = hooks.custom() let ref = hooks.ref(type: TestView.self) return StackView.node([ @@ -55,9 +38,6 @@ struct Test: LeafComponent { Button.node(.init(onPress: Handler { state2.set { $0 + 1 } }), "Increment"), Label.node("\(state2.value)"), - Button.node(.init(onPress: Handler { state3.set(.increment) }), - "Increment"), - Label.node("\(state3.value)"), ]) } } @@ -74,8 +54,6 @@ final class HooksTests: XCTestCase { let button1Handler = button1Props.handlers[.touchUpInside]?.value, let button2Props = stack.subviews[2].props(Button.Props.self), let button2Handler = button2Props.handlers[.touchUpInside]?.value, - let button3Props = stack.subviews[4].props(Button.Props.self), - let button3Handler = button3Props.handlers[.touchUpInside]?.value, let button1Ref = stack.subviews[0].node.ref as? Ref else { XCTAssert(false, "components have no handlers") @@ -89,16 +67,11 @@ final class HooksTests: XCTestCase { button2Handler(()) button2Handler(()) - button3Handler(()) - button3Handler(()) - button3Handler(()) - let e = expectation(description: "rerender") DispatchQueue.main.async { XCTAssertEqual(stack.subviews[1].node.children, AnyEquatable("43")) XCTAssertEqual(stack.subviews[3].node.children, AnyEquatable("44")) - XCTAssertEqual(stack.subviews[5].node.children, AnyEquatable("45")) e.fulfill() }