Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: Update UploadField redux state when form schema data changes (fixes #960) #1263

Merged
merged 1 commit into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

51 changes: 36 additions & 15 deletions client/src/components/UploadField/UploadField.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import getStatusCodeMessage from 'lib/getStatusCodeMessage';
import * as uploadFieldActions from 'state/uploadField/UploadFieldActions';
import * as modalActions from 'state/modal/ModalActions';
import PropTypes from 'prop-types';
import md5 from 'crypto-js/md5';

/**
* Check if two arrays of file objects have different id keys
Expand Down Expand Up @@ -61,19 +62,24 @@ class UploadField extends Component {
}

componentDidMount() {
// Copy form schema data into redux and then ignore it
const { id, data, actions, value, files } = this.props;

// If the data within the "files" prop already matches the value then we don't need to copy
// schema data into redux
if (
value && value.Files && files && value.Files.length === files.length
&& files.filter(file => !value.Files.includes(file.id)).length === 0
) {
const { id, formSchemaFilesHash, data, actions, files } = this.props;

// This tracks changes to the underlying schema data for this field. It may be desirable in
// future to remove this and instead reset redux state whenever a "legacy" form triggers a
// PJAX load. See https://github.com/silverstripe/silverstripe-asset-admin/issues/960
const newFormSchemaFilesHash = md5(JSON.stringify(data.files)).toString();

// If this is the first time this field has mounted, or the schema data has changed (typically
// caused by a PJAX load from saving a legacy non-react form), load the list of files from the
// schema data (data.files)
if (formSchemaFilesHash !== newFormSchemaFilesHash) {
actions.uploadField.setFormSchemaFilesHash(id, newFormSchemaFilesHash);
actions.uploadField.setFiles(id, data.files);
return;
}

actions.uploadField.setFiles(id, data.files);
// Otherwise, we're safe to load from redux state
actions.uploadField.setFiles(id, files);
}

componentWillReceiveProps(nextProps) {
Expand All @@ -88,17 +94,29 @@ class UploadField extends Component {
}

componentDidUpdate(prevProps) {
// If the value updates but there's no files entry for the value then we need to perform a "set
// files" action... This can happen when the value (stored with redux-form) is updated
const { value: { Files: prevValue } } = prevProps;
const {
id,
formSchemaFilesHash,
data,
files,
value: { Files: value },
actions: { uploadField: { setFiles } }
actions: { uploadField: { setFormSchemaFilesHash, setFiles } }
} = this.props;

const newFormSchemaFilesHash = md5(JSON.stringify(data.files)).toString();

// If the schema data has changed (typically caused by a PJAX load from saving a legacy
// non-react form), load the list of files from the schema data (data.files)
if (formSchemaFilesHash !== newFormSchemaFilesHash) {
setFormSchemaFilesHash(id, newFormSchemaFilesHash);
setFiles(id, data.files);
return;
}

// If the value updates but there's no files entry for the value then we need to perform a "set
// files" action... This can happen when the value (stored with redux-form) is updated
const { value: { Files: prevValue } } = prevProps;

if (
// If the lengths match
value.length === prevValue.length
Expand Down Expand Up @@ -555,6 +573,7 @@ UploadField.propTypes = {
Files: PropTypes.arrayOf(PropTypes.number),
}),
files: PropTypes.arrayOf(fileShape), // Authoritative redux state
formSchemaFilesHash: PropTypes.string, // Hash of initial schema data, see componentDidMount()
readOnly: PropTypes.bool,
disabled: PropTypes.bool,
data: PropTypes.shape({
Expand Down Expand Up @@ -585,15 +604,17 @@ UploadField.defaultProps = {
function mapStateToProps(state, ownprops) {
const id = ownprops.id;
let files = [];
let formSchemaFilesHash = null;
if (state.assetAdmin
&& state.assetAdmin.uploadField
&& state.assetAdmin.uploadField.fields
&& state.assetAdmin.uploadField.fields[id]
) {
files = state.assetAdmin.uploadField.fields[id].files || [];
formSchemaFilesHash = state.assetAdmin.uploadField.fields[id].formSchemaFilesHash || null;
}
const securityId = state.config.SecurityID;
return { files, securityId };
return { files, securityId, formSchemaFilesHash };
}

function mapDispatchToProps(dispatch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe('UploadField', () => {
onChange: jest.fn(),
actions: {
uploadField: {
setFormSchemaFilesHash: jest.fn(),
setFiles: jest.fn(),
removeFile: jest.fn(),
},
Expand Down
1 change: 1 addition & 0 deletions client/src/state/uploadField/UploadFieldActionTypes.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export default {
UPLOADFIELD_SET_FORM_SCHEMA_FILES_HASH: 'UPLOADFIELD_SET_FORM_SCHEMA_FILES_HASH',
UPLOADFIELD_ADD_FILE: 'UPLOADFIELD_ADD_FILE',
UPLOADFIELD_SET_FILES: 'UPLOADFIELD_SET_FILES',
UPLOADFIELD_REMOVE_FILE: 'UPLOADFIELD_REMOVE_FILE',
Expand Down
14 changes: 14 additions & 0 deletions client/src/state/uploadField/UploadFieldActions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import ACTION_TYPES from './UploadFieldActionTypes';

/**
* Store a hash of the form schema data for an UploadField instance
*
* @param {String} fieldId - Identifier of UploadField
* @param {String} hash - Hash of data
*/
export function setFormSchemaFilesHash(fieldId, hash) {
return (dispatch) =>
dispatch({
type: ACTION_TYPES.UPLOADFIELD_SET_FORM_SCHEMA_FILES_HASH,
payload: { fieldId, hash },
});
}

/**
* Adds a file which has not been persisted to the server yet.
*
Expand Down
5 changes: 4 additions & 1 deletion client/src/state/uploadField/UploadFieldReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ const initialState = {
* Default object for an empty field state
* @type {{files: Array}}
*/
const initialFieldState = { files: [] };
const initialFieldState = { formSchemaFilesHash: null, files: [] };

function uploadFieldReducer(state = initialState, action) {
// Get field reducer
const reduceField = getFieldReducer(state, action, initialFieldState);

// Update state for this field
switch (action.type) {
case ACTION_TYPES.UPLOADFIELD_SET_FORM_SCHEMA_FILES_HASH:
return reduceField(() => ({ formSchemaFilesHash: action.payload.hash }));

case ACTION_TYPES.UPLOADFIELD_ADD_FILE:
return reduceField((field) => {
// don't re-add
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"babel-runtime": "6.26.0",
"bootstrap": "^4.3.1",
"classnames": "^2.2.5",
"crypto-js": "^4.1.1",
"deep-freeze-strict": "^1.1.1",
"dropzone": "5.7.2",
"graphql": "^0.13.2",
Expand Down
39 changes: 11 additions & 28 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2910,12 +2910,7 @@ colormin@^1.0.5:
css-color-names "0.0.4"
has "^1.0.1"

colors@^1.1.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==

colors@~1.1.2:
[email protected], colors@^1.1.2, colors@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
Expand Down Expand Up @@ -3238,6 +3233,11 @@ crypto-browserify@^3.11.0:
randombytes "^2.0.0"
randomfill "^1.0.3"

crypto-js@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==

crypto-random-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
Expand Down Expand Up @@ -3440,7 +3440,7 @@ debug@^3.1.0, debug@^3.2.7:
dependencies:
ms "^2.1.1"

debuglog@*, debuglog@^1.0.1:
debuglog@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
Expand Down Expand Up @@ -5851,7 +5851,7 @@ imports-loader@^0.6.5:
loader-utils "0.2.x"
source-map "0.1.x"

imurmurhash@*, imurmurhash@^0.1.4:
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
Expand Down Expand Up @@ -7445,11 +7445,6 @@ lodash._basecopy@^3.0.0:
resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=

lodash._baseindexof@*:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=

lodash._baseuniq@~4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
Expand All @@ -7458,16 +7453,11 @@ lodash._baseuniq@~4.6.0:
lodash._createset "~4.0.0"
lodash._root "~3.0.0"

lodash._bindcallback@*, lodash._bindcallback@^3.0.0:
lodash._bindcallback@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4=

lodash._cacheindexof@*:
version "3.0.2"
resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=

lodash._createassigner@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11"
Expand All @@ -7477,19 +7467,12 @@ lodash._createassigner@^3.0.0:
lodash._isiterateecall "^3.0.0"
lodash.restparam "^3.0.0"

lodash._createcache@*:
version "3.1.2"
resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=
dependencies:
lodash._getnative "^3.0.0"

lodash._createset@~4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=

lodash._getnative@*, lodash._getnative@^3.0.0:
lodash._getnative@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
Expand Down Expand Up @@ -7670,7 +7653,7 @@ lodash.pick@^4.2.0, lodash.pick@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=

lodash.restparam@*, lodash.restparam@^3.0.0:
lodash.restparam@^3.0.0:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
Expand Down