@@ -3,7 +3,8 @@ import {useReducer} from 'react';
33import { Lang , parse , stringify } from '../shared/components/object-parser' ;
44import { ScopedLocalStorage } from '../shared/scoped-local-storage' ;
55
6- type Action < T > = { type : 'setLang' ; payload : Lang } | { type : 'setObject' ; payload : string | T } | { type : 'resetObject' ; payload : string | T } ;
6+ type SetObjectAction < T > = { type : 'setObject' ; payload : { object : T ; serialization ?: string } } ;
7+ type Action < T > = { type : 'setLang' ; payload : Lang } | SetObjectAction < T > | { type : 'resetObject' ; payload : T } ;
78
89interface State < T > {
910 /** The parsed form of the object, kept in sync with "serialization" */
@@ -24,20 +25,17 @@ const storage = new ScopedLocalStorage('object-editor');
2425export function reducer < T > ( state : State < T > , action : Action < T > ) {
2526 const newState = { ...state } ;
2627 switch ( action . type ) {
27- case 'resetObject' :
2828 case 'setObject' :
29- if ( typeof action . payload === 'string' ) {
30- newState . object = parse ( action . payload ) ;
31- newState . serialization = action . payload ;
32- } else {
33- newState . object = action . payload ;
34- newState . serialization = stringify ( action . payload , newState . lang ) ;
35- }
36- if ( action . type === 'resetObject' ) {
37- newState . initialSerialization = newState . serialization ;
38- }
29+ newState . object = action . payload . object ;
30+ newState . serialization = action . payload . serialization ?? stringify ( newState . object , newState . lang ) ;
3931 newState . edited = newState . initialSerialization !== newState . serialization ;
4032 return newState ;
33+ case 'resetObject' :
34+ newState . object = action . payload ;
35+ newState . serialization = stringify ( newState . object , newState . lang ) ;
36+ newState . initialSerialization = newState . serialization ;
37+ newState . edited = false ;
38+ return newState ;
4139 case 'setLang' :
4240 newState . lang = action . payload ;
4341 storage . setItem ( 'lang' , newState . lang , defaultLang ) ;
@@ -61,19 +59,31 @@ export function createInitialState<T>(object?: T): State<T> {
6159 } ;
6260}
6361
62+ /**
63+ * Action creator for setObject that can accept a string and parse it.
64+ * The reason the parsing logic isn't in the reducer is because we want parse
65+ * errors to be propagated to the caller.
66+ */
67+ export function setObjectActionCreator < T > ( value : string | T ) : SetObjectAction < T > {
68+ return {
69+ type : 'setObject' ,
70+ payload : typeof value === 'string' ? { object : parse < T > ( value ) , serialization : value } : { object : value }
71+ } ;
72+ }
73+
6474/**
6575 * useEditableObject is a React hook to manage the state of an object that can be serialized and edited, encapsulating the logic to
6676 * parse/stringify the object as necessary.
6777 */
6878export function useEditableObject < T > ( object ?: T ) : State < T > & {
6979 setObject : ( value : string | T ) => void ;
70- resetObject : ( value : string | T ) => void ;
80+ resetObject : ( value : T ) => void ;
7181 setLang : ( lang : Lang ) => void ;
7282} {
7383 const [ state , dispatch ] = useReducer ( reducer < T > , object , createInitialState ) ;
7484 return {
7585 ...state ,
76- setObject : value => dispatch ( { type : 'setObject' , payload : value } ) ,
86+ setObject : value => dispatch ( setObjectActionCreator < T > ( value ) ) ,
7787 resetObject : value => dispatch ( { type : 'resetObject' , payload : value } ) ,
7888 setLang : value => dispatch ( { type : 'setLang' , payload : value } )
7989 } ;
0 commit comments