Wrap credential form submission code in useRequest to avoid the "unmounted component" errors we've seen if we try to push a new route onto the history but the coomponent is already unmounted.

This commit is contained in:
mabashian 2020-06-02 12:04:20 -04:00
parent b01e312b8f
commit 66df922956
3 changed files with 130 additions and 105 deletions

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useCallback, useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { PageSection, Card } from '@patternfly/react-core';
import { CardBody } from '../../../components/Card';
@ -11,14 +11,64 @@ import {
CredentialsAPI,
} from '../../../api';
import CredentialForm from '../shared/CredentialForm';
import useRequest from '../../../util/useRequest';
function CredentialAdd({ me }) {
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [credentialTypes, setCredentialTypes] = useState(null);
const [formSubmitError, setFormSubmitError] = useState(null);
const history = useHistory();
const {
error: submitError,
request: submitRequest,
result: credentialId,
} = useRequest(
useCallback(
async values => {
const { inputs, organization, ...remainingValues } = values;
const nonPluginInputs = {};
const pluginInputs = {};
Object.entries(inputs).forEach(([key, value]) => {
if (value.credential && value.inputs) {
pluginInputs[key] = value;
} else {
nonPluginInputs[key] = value;
}
});
const {
data: { id: newCredentialId },
} = await CredentialsAPI.create({
user: (me && me.id) || null,
organization: (organization && organization.id) || null,
inputs: nonPluginInputs,
...remainingValues,
});
const inputSourceRequests = [];
Object.entries(pluginInputs).forEach(([key, value]) => {
inputSourceRequests.push(
CredentialInputSourcesAPI.create({
input_field_name: key,
metadata: value.inputs,
source_credential: value.credential.id,
target_credential: newCredentialId,
})
);
});
await Promise.all(inputSourceRequests);
return newCredentialId;
},
[me]
)
);
useEffect(() => {
if (credentialId) {
history.push(`/credentials/${credentialId}/details`);
}
}, [credentialId, history]);
useEffect(() => {
const loadData = async () => {
try {
@ -42,45 +92,7 @@ function CredentialAdd({ me }) {
};
const handleSubmit = async values => {
const { inputs, organization, ...remainingValues } = values;
const pluginInputs = [];
Object.entries(inputs).forEach(([key, value]) => {
if (value.credential && value.inputs) {
pluginInputs.push([key, value]);
delete inputs[key];
}
});
setFormSubmitError(null);
try {
const {
data: { id: credentialId },
} = await CredentialsAPI.create({
user: (me && me.id) || null,
organization: (organization && organization.id) || null,
inputs: inputs || {},
...remainingValues,
});
const inputSourceRequests = [];
pluginInputs.forEach(([key, value]) => {
if (value.credential && value.inputs) {
inputSourceRequests.push(
CredentialInputSourcesAPI.create({
input_field_name: key,
metadata: value.inputs,
source_credential: value.credential.id,
target_credential: credentialId,
})
);
}
});
await Promise.all(inputSourceRequests);
const url = `/credentials/${credentialId}/details`;
history.push(`${url}`);
} catch (err) {
setFormSubmitError(err);
}
await submitRequest(values);
};
if (error) {
@ -113,7 +125,7 @@ function CredentialAdd({ me }) {
onCancel={handleCancel}
onSubmit={handleSubmit}
credentialTypes={credentialTypes}
submitError={formSubmitError}
submitError={submitError}
/>
</CardBody>
</Card>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useCallback, useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { object } from 'prop-types';
@ -11,15 +11,84 @@ import {
import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading';
import CredentialForm from '../shared/CredentialForm';
import useRequest from '../../../util/useRequest';
function CredentialEdit({ credential, me }) {
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [credentialTypes, setCredentialTypes] = useState(null);
const [inputSources, setInputSources] = useState(null);
const [formSubmitError, setFormSubmitError] = useState(null);
const [inputSources, setInputSources] = useState({});
const history = useHistory();
const { error: submitError, request: submitRequest, result } = useRequest(
useCallback(
async (values, inputSourceMap) => {
const createAndUpdateInputSources = pluginInputs =>
Object.entries(pluginInputs).map(([fieldName, fieldValue]) => {
if (!inputSourceMap[fieldName]) {
return CredentialInputSourcesAPI.create({
input_field_name: fieldName,
metadata: fieldValue.inputs,
source_credential: fieldValue.credential.id,
target_credential: credential.id,
});
}
if (fieldValue.touched) {
return CredentialInputSourcesAPI.update(
inputSourceMap[fieldName].id,
{
metadata: fieldValue.inputs,
source_credential: fieldValue.credential.id,
}
);
}
return null;
});
const destroyInputSources = inputs => {
const destroyRequests = [];
Object.values(inputSourceMap).forEach(inputSource => {
const { id, input_field_name } = inputSource;
if (!inputs[input_field_name]?.credential) {
destroyRequests.push(CredentialInputSourcesAPI.destroy(id));
}
});
return destroyRequests;
};
const { inputs, organization, ...remainingValues } = values;
const nonPluginInputs = {};
const pluginInputs = {};
Object.entries(inputs).forEach(([key, value]) => {
if (value.credential && value.inputs) {
pluginInputs[key] = value;
} else {
nonPluginInputs[key] = value;
}
});
const [{ data }] = await Promise.all([
CredentialsAPI.update(credential.id, {
user: (me && me.id) || null,
organization: (organization && organization.id) || null,
inputs: nonPluginInputs,
...remainingValues,
}),
...destroyInputSources(inputs),
]);
await Promise.all(createAndUpdateInputSources(pluginInputs));
return data;
},
[credential.id, me]
)
);
useEffect(() => {
if (result) {
history.push(`/credentials/${result.id}/details`);
}
}, [result, history]);
useEffect(() => {
const loadData = async () => {
try {
@ -54,67 +123,11 @@ function CredentialEdit({ credential, me }) {
const handleCancel = () => {
const url = `/credentials/${credential.id}/details`;
history.push(`${url}`);
};
const createAndUpdateInputSources = pluginInputs =>
Object.entries(pluginInputs).map(([fieldName, fieldValue]) => {
if (!inputSources[fieldName]) {
return CredentialInputSourcesAPI.create({
input_field_name: fieldName,
metadata: fieldValue.inputs,
source_credential: fieldValue.credential.id,
target_credential: credential.id,
});
}
if (fieldValue.touched) {
return CredentialInputSourcesAPI.update(inputSources[fieldName].id, {
metadata: fieldValue.inputs,
source_credential: fieldValue.credential.id,
});
}
return null;
});
const destroyInputSources = inputs => {
const destroyRequests = [];
Object.values(inputSources).forEach(inputSource => {
const { id, input_field_name } = inputSource;
if (!inputs[input_field_name]?.credential) {
destroyRequests.push(CredentialInputSourcesAPI.destroy(id));
}
});
return destroyRequests;
};
const handleSubmit = async values => {
const { inputs, organization, ...remainingValues } = values;
const pluginInputs = {};
Object.entries(inputs).forEach(([key, value]) => {
if (value.credential && value.inputs) {
pluginInputs[key] = value;
delete inputs[key];
}
});
setFormSubmitError(null);
try {
await Promise.all([
CredentialsAPI.update(credential.id, {
user: (me && me.id) || null,
organization: (organization && organization.id) || null,
inputs: inputs || {},
...remainingValues,
}),
...destroyInputSources(pluginInputs),
]);
await Promise.all(createAndUpdateInputSources(pluginInputs));
const url = `/credentials/${credential.id}/details`;
history.push(`${url}`);
} catch (err) {
setFormSubmitError(err);
}
await submitRequest(values, inputSources);
};
if (error) {
@ -133,7 +146,7 @@ function CredentialEdit({ credential, me }) {
credential={credential}
credentialTypes={credentialTypes}
inputSources={inputSources}
submitError={formSubmitError}
submitError={submitError}
/>
</CardBody>
);

View File

@ -37,9 +37,9 @@ function CredentialPluginField(props) {
isValid={isValid}
label={label}
>
{field.value.credential ? (
{field?.value?.credential ? (
<CredentialPluginSelected
credential={field.value.credential}
credential={field?.value?.credential}
onClearPlugin={() => helpers.setValue('')}
onEditPlugin={() => setShowPluginWizard(true)}
/>