NavigationStackStore send an Action to a child

While working with NavigationStackStore found really easy to read actions from child features presented by Path like:

case let .path(action):
    switch action {
    case .element(id: _, action: .screenB(.screenAButtonTapped)):
         state.path.append(.screenA())
         return .none
    }
}

but couldn't find a way to send actions from path to a child feature,
is it possible?

like the following:

    return .send(.path(.child1(.initialize)))

Hey @jecht83 ! I think it isn't a good practice to call actions from parent to child. Usually, I tend - to get a more coherent system - to re-create child states. Please, look at this example, which handles an identified array of states, to check if it fits your case.

It is not recommend to share logic between domains by sending actions like this. We have this details in the docs.

The article above describes some alternatives, such as a mutating func on the child state, and the way you use that technique with StackState is like the following:

case let .element(id: id, action: …):
  state.path[id: id]?.someMutatingFunc()
1 Like

That's exactly what I was looking for

A way to demonstrate that the parent feature calling actions from it's child
is not a good approach backed up with documentation

And an option to work with mutating funcs in StackState

Thanks for your time

By following this approach added a mutating func into the Child and got the error

// Path.State does not contain mutating func 
case let .element(id: id, action: …):
  state.path[id: id]?.someMutatingFunc()

// Cast from 'ContentFeature.Path.State?' to unrelated type 'SettingsFeature.State' always fails
case let .element(id: id, action: …):
    if let settingsState = state.path[id: id] as? SettingsFeature.State {
        settingsState.someMutatingFunc()
    }
    return .none

Also documentation recommends another approach, is this working with navigation stack?

case .buttonTapped:
  return Child().reduce(into: &state.child, action: .refresh)
    .map(Action.child)

Tried something like:

return SettingsFeature()
    .reduce(into: &state.path[id: id]!, action: .updateSettings)
    .map(Path.Action.settings)

any suggestion?

I'm guessing that Path.State is an enum of all the features that can be pushed on the stack, and so that is why there is no mutating function. You are probably wanting to call a mutating function on a particular case of the enum.

You can do this using the [id:case:] subscript on StackState:

state.path[id: id, case: \.settings]?.someMethod()

And you can use that subscript with reduce(into:action:) too.

1 Like

In my case it worked with:

state.path[id: id, case: /Path.State.settings]?.someMethod()

Thanks for your help

If you upgrade to using the newest version of TCA and the @Reducer macro you can shorten that to just case: \.settings.

I've been limited by my OS version but I'll try to update to the latest, thank you

OK one last thing, I can not make the reduce subscript to work with navigation stack store
I've tried the following with no success:

return SettingsFeature()
    .reduce(into: &state.path[id: id, case: \.settings]!, action: .refresh)
    .map(Path.Action.settings)  // Type of expression is ambiguous without a type annotation

@mbrandonw, how can I make it work with StackStore?

The .map on the effect needs to bundle a settings action back into the path. So it's a bit more complex:

.map { .path(.element(id: id, action: $0)) }

I really hate to bother you, tried the following code with no success

return SettingsFeature()
     .reduce(
            into: &state.path[id: id, case: /Path.State.settings]!,
            action: .updateTitle
     )
     .map { .path(.element(id: id, action: $0)) }

// Error: Cannot convert value of type 'SettingsFeature.Action' 
// to expected argument type 'ContentFeature.Path.Action'

Ah sorry, it should be:

.map { .path(.element(id: id, action: .settings($0))) }

Or whatever the name of the case for settings is inside the Path.Action enum.

1 Like

That did the trick, thank you so much for your help

Hi @mbrandonw do you know if i can access a different element id inside the stack while the current id is different? maybe hardcode the id number and access the state, for instance:
let customID: 3 as StackElementID
var customState = state.path[id: customID, case: \Settings]

if isn't possible, which looks like it isn't since it does crashes.
how could i update a different type of state while receiving an action from my root reducer from a different state, for instance:
case let .path(.element(id: id, action: .photoFiltering(.nextButtonTapped))):
// here access different path and change property inside it