Skip to content

Commit 12baa20

Browse files
authored
#71 onError callback (#76)
1 parent 6df4c97 commit 12baa20

File tree

13 files changed

+174
-40
lines changed

13 files changed

+174
-40
lines changed

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Features include:
1212
- self-contained — rendered with plain HTML/CSS, so no dependance on external UI libraries
1313
- search/filter data by key, value or custom function
1414
- provide your own [custom component](#custom-nodes) to integrate specialised UI for certain data.
15+
- [localisable](#localisation) UI
1516

1617
**[Explore the Demo](https://carlosnz.github.io/json-edit-react/)**
1718

@@ -23,6 +24,7 @@ Features include:
2324
- [Props overview](#props-overview)
2425
- [Update functions](#update-functions)
2526
- [OnChange function](#onchange-function)
27+
- [OnError function](#onerror-function)
2628
- [Copy function](#copy-function)
2729
- [Filter functions](#filter-functions)
2830
- [Examples](#examples-1)
@@ -96,6 +98,8 @@ The only *required* value is `data`.
9698
| `onDelete` | `UpdateFunction` | | A function to run whenever a value is **deleted**. |
9799
| `onAdd` | `UpdateFunction` | | A function to run whenever a new property is **added**. |
98100
| `onChange` | `OnChangeFunction` | | A function to modify/constrain user input as they type -- see [OnChange functions](#onchange-function). |
101+
| `onError` | `OnErrorFunction` | | A function to run whenever the component reports an error -- see [OnErrorFunction](#onerror-function). |
102+
| `showErrorMessages` | `boolean ` | `true` | Whether or not the component should display its own error messages (you'd probably only want to disable this if you provided your own `onError` function) |
99103
| `enableClipboard` | `boolean\|CopyFunction` | `true` | Whether or not to enable the "Copy to clipboard" button in the UI. If a function is provided, `true` is assumed and this function will be run whenever an item is copied. |
100104
| `indent` | `number` | `3` | Specify the amount of indentation for each level of nesting in the displayed data. |
101105
| `collapse` | `boolean\|number\|FilterFunction` | `false` | Defines which nodes of the JSON tree will be displayed "opened" in the UI on load. If `boolean`, it'll be either all or none. A `number` specifies a nesting depth after which nodes will be closed. For more fine control a function can be provided — see [Filter functions](#filter-functions). |
@@ -169,6 +173,26 @@ The input object is similar to the Update function input, but with no `newData`
169173
}
170174
```
171175

176+
### OnError function
177+
178+
Normally, the component will display simple error messages whenever an error condition is detected (e.g. invalid JSON input, duplicate keys, or custom errors returned by the [`onUpdate` functions)](#update-functions)). However, you can provide your own `onError` callback in order to implement your own error UI, or run additional side effects. (In the former case, you'd probably want to disable the `showErrorMessages` prop, too.) The input to the callback is similar to the other callbacks:
179+
180+
```js
181+
{
182+
currentData, // data state before update
183+
currentValue, // the current value of the property being updated
184+
errorValue, // the erroneous value that failed to update the property
185+
name, // name of the property being updated
186+
path, // full path to the property being updated, as an array of property keys
187+
// (e.g. [ "user", "friends", 1, "name" ] ) (equivalent to "user.friends[1].name"),
188+
error: {
189+
code, // one of 'UPDATE_ERROR' | 'DELETE_ERROR' | 'ADD_ERROR' | 'INVALID_JSON' | 'KEY_EXISTS'
190+
message // the (localised) error message that would be displayed
191+
}
192+
}
193+
```
194+
(An example of a custom Error UI can be seen in the [Demo](#https://carlosnz.github.io/json-edit-react/) with the "Custom Nodes" data set -- when you enter invalid JSON input a "Toast" notification is displayed instead of the normal component error message.)
195+
172196
### Copy function
173197

174198
A similar callback is executed whenever an item is copied to the clipboard (if passed to the `enableClipboard` prop), but with a different input parameter:
@@ -560,7 +584,7 @@ A few helper functions, components and types that might be useful in your own im
560584
- `Theme`: a full [Theme](#themes--styles) object
561585
- `ThemeInput`: input type for the `theme` prop
562586
- `JsonEditorProps`: all input props for the Json Editor component
563-
- [`UpdateFunction`](#update-functions), [`OnChangeFunction`](#onchange-function), [`FilterFunction`](#filter-functions), [`CopyFunction`](#copy-function), [`SearchFilterFunction`](#searchfiltering), [`CompareFunction`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), [`LocalisedString`](#localisation), [`CustomNodeDefinition`](#custom-nodes), [`CustomTextDefinitions`](#custom-text)
587+
- [`UpdateFunction`](#update-functions), [`OnChangeFunction`](#onchange-function), [`OnErrorFunction`](#onerror-function) [`FilterFunction`](#filter-functions), [`CopyFunction`](#copy-function), [`SearchFilterFunction`](#searchfiltering), [`CompareFunction`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), [`LocalisedString`](#localisation), [`CustomNodeDefinition`](#custom-nodes), [`CustomTextDefinitions`](#custom-text)
564588
- `TranslateFunction`: function that takes a [localisation](#localisation) key and returns a translated string
565589
- `IconReplacements`: input type for the `icons` prop
566590
- `CollectionNodeProps`: all props passed internally to "collection" nodes (i.e. objects/arrays)
@@ -584,6 +608,9 @@ This component is heavily inspired by [react-json-view](https://github.com/mac-s
584608
585609
## Changelog
586610
611+
- **1.12.0**:
612+
- Preserve editing mode when changing Data Type
613+
- [`onError` callback](#onerror-function) available for custom error handling
587614
- **1.11.8**: Fix regression for empty data root name introduces in 1.11.7
588615
- **1.11.7**: Handle \<empty-string\> object keys / prevent duplicate keys
589616
- **1.11.3**: Bug fix for invalid state when changing type to Collection node

demo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@types/react-dom": "^18.2.18",
1818
"firebase": "^10.7.2",
1919
"framer-motion": "^11.0.3",
20-
"json-edit-react": "^1.11.9",
20+
"json-edit-react": "^1.12.0",
2121
"just-compare": "^2.3.0",
2222
"react": "^18.2.0",
2323
"react-datepicker": "^5.0.0",

demo/src/App.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ import {
3333
} from '@chakra-ui/react'
3434
import logo from './image/logo_400.png'
3535
import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons'
36-
import { demoData } from './demoData'
36+
import { DemoData, demoData } from './demoData'
3737
import { useDatabase } from './useDatabase'
3838
import './style.css'
3939
import { version } from './version'
40+
import { OnErrorFunction } from './json-edit-react/src/types'
4041

4142
function App() {
4243
const [selectedData, setSelectedData] = useState('intro')
@@ -260,6 +261,21 @@ function App() {
260261
}
261262
: undefined
262263
}
264+
onError={
265+
demoData[selectedData].onError
266+
? (errorData) => {
267+
const error = (demoData[selectedData].onError as OnErrorFunction)(errorData)
268+
toast({
269+
title: 'ERROR 😢',
270+
description: error as any,
271+
status: 'error',
272+
duration: 5000,
273+
isClosable: true,
274+
})
275+
}
276+
: undefined
277+
}
278+
showErrorMessages={demoData[selectedData].showErrorMessages}
263279
collapse={collapseLevel}
264280
showCollectionCount={
265281
showCount === 'Yes' ? true : showCount === 'When closed' ? 'when-closed' : false

demo/src/_imports.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
*/
44

55
/* Installed package */
6-
export * from 'json-edit-react'
6+
// export * from 'json-edit-react'
77

88
/* Local src */
9-
// export * from './json-edit-react/src'
9+
export * from './json-edit-react/src'
1010

1111
/* Compiled local package */
1212
// export * from './package/build'

demo/src/demoData/dataDefinitions.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ import {
1515
DataType,
1616
DefaultValueFunction,
1717
OnChangeFunction,
18+
OnErrorFunction,
1819
SearchFilterFunction,
1920
ThemeStyles,
2021
UpdateFunction,
2122
} from '../json-edit-react/src/types'
2223
import { Input } from 'object-property-assigner/build'
2324

24-
interface DemoData {
25+
export interface DemoData {
2526
name: string
2627
description: JSX.Element
2728
data: object
@@ -51,6 +52,8 @@ interface DemoData {
5152
path: CollectionKey[]
5253
}) => any
5354
onChange?: OnChangeFunction
55+
onError?: OnErrorFunction
56+
showErrorMessages?: boolean
5457
defaultValue?: unknown | DefaultValueFunction
5558
customNodeDefinitions?: CustomNodeDefinition[]
5659
customTextDefinitions?: CustomTextDefinitions
@@ -428,6 +431,12 @@ export const demoData: Record<string, DemoData> = {
428431
</Text>
429432
<Text>
430433
In this example, compare the raw JSON (edit the data root) with what is presented here.
434+
(You can also see a custom{' '}
435+
<Link href="https://github.com/CarlosNZ/json-edit-react#onerror-function" isExternal>
436+
<span className="code">onError</span>
437+
</Link>{' '}
438+
function that displays a Toast notification rather than the standard error message when
439+
you enter invalid JSON input.)
431440
</Text>
432441
<Text>
433442
You can also see how the property count text changes depending on the data. This is using
@@ -459,6 +468,8 @@ export const demoData: Record<string, DemoData> = {
459468
restrictEdit: ({ level }) => level > 0,
460469
restrictAdd: true,
461470
restrictDelete: true,
471+
onError: (errorData) => errorData.error.message,
472+
showErrorMessages: false,
462473
customNodeDefinitions: [
463474
{
464475
condition: ({ key, value }) =>

demo/src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const version = '1.11.9'
1+
export const version = '1.12.0-rc1'

demo/yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7965,10 +7965,10 @@ [email protected]:
79657965
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
79667966
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
79677967

7968-
json-edit-react@^1.11.9:
7969-
version "1.11.9"
7970-
resolved "https://registry.yarnpkg.com/json-edit-react/-/json-edit-react-1.11.9.tgz#9057940f4bc6e50d057238811f80d3beedec532c"
7971-
integrity sha512-uhRWiJzgf8RGvcc75+lq/LNpTCw6jxM62DHN4xS3JJOuoR6AoECbKsj4xcz5i/aWs6f19OkDn6DJ9syv9VD03w==
7968+
json-edit-react@^1.12.0:
7969+
version "1.12.0-rc1"
7970+
resolved "https://registry.yarnpkg.com/json-edit-react/-/json-edit-react-1.12.0-rc1.tgz#878338843fd872e136448c985eb9fb25ca9590a8"
7971+
integrity sha512-NzREei+lEtshSzSfiy/SkQz9P9CpnnJzCxTKTW7uhEo6M+y7f9cSiAh9uTWTeonO7SQH0VuShesPzmayEcQnCA==
79727972
dependencies:
79737973
json5 "^2.2.3"
79747974
just-clone "^6.2.0"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "json-edit-react",
3-
"version": "1.11.9",
3+
"version": "1.12.0-rc1",
44
"description": "React component for editing or viewing JSON/object data",
55
"main": "build/index.cjs.js",
66
"module": "build/index.esm.js",

src/CollectionNode.tsx

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import {
77
type CollectionNodeProps,
88
type ErrorString,
99
type NodeData,
10+
type JerError,
1011
ERROR_DISPLAY_TIME,
12+
CollectionData,
1113
} from './types'
1214
import { Icon } from './Icons'
1315
import { filterNode, isCollection } from './filterHelpers'
@@ -16,6 +18,7 @@ import { AutogrowTextArea } from './AutogrowTextArea'
1618
import { useTheme } from './theme'
1719
import { useTreeState } from './TreeStateProvider'
1820
import { toPathString } from './ValueNodes'
21+
import extractProperty from 'object-property-extractor'
1922

2023
export const CollectionNode: React.FC<CollectionNodeProps> = ({
2124
data,
@@ -37,6 +40,8 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
3740
onEdit,
3841
onAdd,
3942
onDelete,
43+
onError: onErrorCallback,
44+
showErrorMessages,
4045
restrictEditFilter,
4146
restrictDeleteFilter,
4247
restrictAddFilter,
@@ -112,6 +117,31 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
112117
showCollectionWrapper = true,
113118
} = useMemo(() => getCustomNode(customNodeDefinitions, nodeData), [])
114119

120+
const showError = (errorString: ErrorString) => {
121+
if (showErrorMessages) {
122+
setError(errorString)
123+
setTimeout(() => setError(null), ERROR_DISPLAY_TIME)
124+
}
125+
console.warn('Error', errorString)
126+
}
127+
128+
const onError = useMemo(
129+
() => (error: JerError, errorValue: CollectionData | string) => {
130+
showError(error.message)
131+
if (onErrorCallback) {
132+
onErrorCallback({
133+
currentData: nodeData.fullData,
134+
errorValue,
135+
currentValue: data,
136+
name,
137+
path,
138+
error,
139+
})
140+
}
141+
},
142+
[onErrorCallback, showErrorMessages]
143+
)
144+
115145
// Early return if this node is filtered out
116146
if (!filterNode('collection', nodeData, searchFilter, searchText) && nodeData.level > 0) {
117147
return null
@@ -144,25 +174,22 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
144174
}
145175
}
146176

147-
const showError = (errorString: ErrorString) => {
148-
setError(errorString)
149-
setTimeout(() => setError(null), ERROR_DISPLAY_TIME)
150-
console.log('Error', errorString)
151-
}
152-
153177
const handleEdit = () => {
154178
try {
155179
const value = JSON5.parse(stringifiedValue)
156180
setCurrentlyEditingElement(null)
157181
setError(null)
158182
if (JSON.stringify(value) === JSON.stringify(data)) return
159183
onEdit(value, path).then((error) => {
160-
if (error) showError(error)
184+
if (error) {
185+
onError({ code: 'UPDATE_ERROR', message: error }, value as CollectionData)
186+
}
161187
})
162188
} catch {
163-
setError(translate('ERROR_INVALID_JSON', nodeData))
164-
setTimeout(() => setError(null), ERROR_DISPLAY_TIME)
165-
console.log('Invalid JSON')
189+
onError(
190+
{ code: 'INVALID_JSON', message: translate('ERROR_INVALID_JSON', nodeData) },
191+
stringifiedValue
192+
)
166193
}
167194
}
168195

@@ -173,7 +200,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
173200
const parentPath = path.slice(0, -1)
174201
const existingKeys = Object.keys(parentData)
175202
if (existingKeys.includes(newKey)) {
176-
showError(translate('ERROR_KEY_EXISTS', nodeData))
203+
onError({ code: 'KEY_EXISTS', message: translate('ERROR_KEY_EXISTS', nodeData) }, newKey)
177204
return
178205
}
179206

@@ -194,22 +221,27 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
194221
const newValue = getDefaultNewValue(nodeData)
195222
if (collectionType === 'array') {
196223
onAdd(newValue, [...path, (data as unknown[]).length]).then((error) => {
197-
if (error) showError(error)
224+
if (error) onError({ code: 'ADD_ERROR', message: error }, newValue as CollectionData)
198225
})
199226
} else if (key in data) {
200-
showError(translate('ERROR_KEY_EXISTS', nodeData))
227+
onError({ code: 'KEY_EXISTS', message: translate('ERROR_KEY_EXISTS', nodeData) }, key)
201228
} else {
202229
onAdd(newValue, [...path, key]).then((error) => {
203-
if (error) showError(error)
230+
if (error) onError({ code: 'ADD_ERROR', message: error }, newValue as CollectionData)
204231
})
205232
}
206233
}
207234

208235
const handleDelete =
209236
path.length > 0
210237
? () => {
211-
onDelete(data, path).then((result) => {
212-
if (result) showError(result)
238+
onDelete(data, path).then((error) => {
239+
if (error) {
240+
onError(
241+
{ code: 'DELETE_ERROR', message: error },
242+
extractProperty(data, path) as CollectionData
243+
)
244+
}
213245
})
214246
}
215247
: undefined

src/JsonEditor.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const Editor: React.FC<JsonEditorProps> = ({
2727
onDelete: srcDelete = onUpdate,
2828
onAdd: srcAdd = onUpdate,
2929
onChange,
30+
onError,
31+
showErrorMessages = true,
3032
enableClipboard = true,
3133
theme = 'default',
3234
icons,
@@ -174,6 +176,8 @@ const Editor: React.FC<JsonEditorProps> = ({
174176
onDelete,
175177
onAdd,
176178
onChange,
179+
onError,
180+
showErrorMessages,
177181
showCollectionCount,
178182
collapseFilter,
179183
restrictEditFilter,

0 commit comments

Comments
 (0)