Best practices for passing props from location state, and then storing in local memory

Hey everyone!

Let’s say I have a table in my ui and it lists all currently active contracts of the same Template @ app/template. Each row has a unique name.

Using Link router I can click on a button that takes me to a new page @ app/template/view to view all of the details of that particular contract.

                <Button 
                  component={RouterLink} 
                  color="primary"
                  to={{
                        pathname: "/app/template/view",
                        state: { 
                          proposalName: getValue(c, "payload.proposalName"),
                          columnsDetails: columnsDetails
                        }
                      }}
                  >
                    {getValue(c, "payload.proposalName")}
                </Button>

The problem is that once the /app/template/view is refreshed, the page crashes because the props.location.state from history are lost.

`

TypeError: Cannot destructure property ‘templateName’ of ‘props.location.state’ as it is undefined.

`

I have seen some threads suggesting to transfer props from location to local state history on the first load of the page, so that they are available if the page refreshed or recalled later on. I haven’t been able to make this work yet.

Has anyone else dealt with a similar issue and how did you go about fixing the issue?

I’m no React expert but maybe this blog post on working with props will help: https://medium.com/@cristi.nord/props-and-how-to-pass-props-to-components-in-react-part-4-2cc375c17a23

Hey @anthony. Thanks! I’ll check it out.

The more I read, the more I realize that using location.state.props may not be the best approach when passing props in the way that I am looking for.

First, the proposalName is not guaranteed to be unique by the user. They could enter anything (including the same proposalName) and then the view would have a duplicate.

And second, most forums suggest only using location or history props when passing temporary data (where you don’t have to worry about the page being refreshed, etc.).

To solve the first part, I think we need to add contract keys.

The second piece may involve using contract keys as route params (using react-router-dom) and then using the useParams() hook to capture the contract key prop to fetch the specified contract via useStreamFetchByKeys. And this second piece may require more engineering than is necessary for right now.

1 Like

I think the idiomatic way of doing this would be to use parameters in the path. Ie the view of a template wouldn’t live at app/template/view, but at app/template/CONTRACTID. History and state are not preserved if you refresh the page (unless you store them in cookies… :face_vomiting:) so you need to preserve your location in the address.

The great advantage of address-based parameter passing is that you can give someone else your address and they get to the same view. Have a look at the React Router and path-to-regexp docs.

TL;DR: set your path to /app/template/:contractId and then grab the contract Id inside your compoennt using props.match.params.contractId.

1 Like

Hey @bernhard! Thanks for the advice. Using route params seems like the best way forward.

Would you see any benefit in doing this with contract keys (since they would be stable per template)?

Earlier this year, @Chris_Rivers and I discussed with @georg ways to deal with disappearing components. One way he suggested was to add status parameters to contracts (say ‘pending’ and ‘approved’).

For instance, let’s say that Party A has requested the issuance of a bond from the CSD by creating an IssuanceRequest contract with status='pending'. When the CSD approves by making the consuming IssuanceRequest_Accept choice, instead of returning this:

controller bondAccount.provider can
  IssuanceRequest_Accept: (ContractId FixedRateBondFact, ContractId AssetDeposit)
    with
      isin: Text

return an IssuanceRequest contract with status='Approved':

controller bondAccount.provider can
  IssuanceRequest_Accept: (ContractId IssuanceRequest, ContractId FixedRateBondFact, ContractId AssetDeposit)
    with
      isin: Text
...
   issuanceRequest' <- create this with status="Approved", identifier=isin
      return (issuanceRequest', fiedRateBondFactCid, assetCid)

We feel this creates a smoother user experience. Instead of the contract being archived and the user having to assume that everything went smoothly (or navigating to a different tab where they can see the resulting FixedRateBondFact), the user sees that they successfully completed the workflow from right where they completed the choice.

My thought is that this wouldn’t necessarily be possible if we used contractId's since the parameter contractId would change once a choice is made on the contract.

Would this be a sensible way to use contract keys or are there side effects that wouldn’t allow them to be useful in this way?

Have you found users of DAML apps to be confused by disappearing components or is that something that is just inherent to DAML applications?

1 Like

Yes, much better if your contracts have consuming choices.

Yes, I actually just made the same change in my secret santa app. Moved from archiving to setting a status so I can show the final state in the UI.

2 Likes

Would it be better to use a data type instead of a string for status?

Hey @anthony. Good question. I would think a more polished DAML model would use a status data type, like the DVP contract in the finance lib does (note these are just snippets and aren’t in the same file, but in the same model)

template Dvp
  with
    masterAgreement : MasterAgreement
      -- ^ A trade is allocated to a master agreement and backed by the
      -- masterAgreement.id.signatories. Depending on the desired trust model this
      -- might be both counterparties or a third party agent.
    tradeId : Id
      -- ^ The identifier of the trade within the master agreement.
      -- The tradeId.signatories can be left empty.
    buyer : Party
      -- ^ The buyer is the party that sends the payments and receives the deliveries.
      -- The seller is the other counterparty mentioned in the master agreement.
    status : SettlementStatus
      -- ^ The settlement status of the trade.
    settlementDate : Optional Date
      -- ^ The settlement date of the trade. None indicates instant settlement.
    payments : [Asset]
      -- ^ The assets that need to be paid from the buyer to the seller.
    deliveries : [Asset]
      -- ^ The assets that need to be delivered from the seller to the buyer.
    observers : Set Party

data SettlementStatus
      = SettlementStatus_Pending
        -- ^ An active trade prior to settlement
      | SettlementStatus_Instructed
        -- ^ A trade that has been instructed for settlement
      | SettlementStatus_Settled
        -- ^ A trade that has been settled
      deriving (Eq, Show)

For our purposes and our current stage, defining status as type text in the template to work just fine.

1 Like

That makes sense, thanks for the insight @jamesljaworski85