( body: FormData, serverManifest: ServerManifest, )
| 105 | } |
| 106 | |
| 107 | export function decodeAction<T>( |
| 108 | body: FormData, |
| 109 | serverManifest: ServerManifest, |
| 110 | ): Promise<() => T> | null { |
| 111 | // We're going to create a new formData object that holds all the fields except |
| 112 | // the implementation details of the action data. |
| 113 | const formData = new FormData(); |
| 114 | |
| 115 | let action: Promise<(formData: FormData) => T> | null = null; |
| 116 | const seenActions = new Set<string>(); |
| 117 | |
| 118 | // $FlowFixMe[prop-missing] |
| 119 | body.forEach((value: string | File, key: string) => { |
| 120 | if (!key.startsWith('$ACTION_')) { |
| 121 | // $FlowFixMe[incompatible-call] |
| 122 | formData.append(key, value); |
| 123 | return; |
| 124 | } |
| 125 | // Later actions may override earlier actions if a button is used to |
| 126 | // override the default form action. However, we don't expect the same |
| 127 | // action ref field to be sent multiple times in legitimate form data. |
| 128 | if (key.startsWith('$ACTION_REF_')) { |
| 129 | if (seenActions.has(key)) { |
| 130 | return; |
| 131 | } |
| 132 | seenActions.add(key); |
| 133 | const formFieldPrefix = '$ACTION_' + key.slice(12) + ':'; |
| 134 | const metaData = decodeBoundActionMetaData( |
| 135 | body, |
| 136 | serverManifest, |
| 137 | formFieldPrefix, |
| 138 | ); |
| 139 | action = loadServerReference(serverManifest, metaData); |
| 140 | return; |
| 141 | } |
| 142 | // A simple action with no bound arguments may appear twice in the form data |
| 143 | // if a button specifies the same action as the default form action. We only |
| 144 | // load the first one, as they're guaranteed to be identical. |
| 145 | if (key.startsWith('$ACTION_ID_')) { |
| 146 | if (seenActions.has(key)) { |
| 147 | return; |
| 148 | } |
| 149 | seenActions.add(key); |
| 150 | const id = key.slice(11); |
| 151 | action = loadServerReference(serverManifest, { |
| 152 | id, |
| 153 | bound: null, |
| 154 | }); |
| 155 | return; |
| 156 | } |
| 157 | }); |
| 158 | |
| 159 | if (action === null) { |
| 160 | return null; |
| 161 | } |
| 162 | // Return the action with the remaining FormData bound to the first argument. |
| 163 | return action.then(fn => fn.bind(null, formData)); |
| 164 | } |
no test coverage detected