Form

This section assumes you have installed StateX and React. See the Getting Started page for how to get started with StateX and React from scratch.

In this example, we'll cover atoms, selectors, and the hooks exposed by the StateX API. We'll also cover some advanced options used with atoms.

Try entering your first name and watch all other fields, last name, full name, full name count (async), full JSON view auto populate with what you enter and scoll below for the code walkthrough on how this form is built. Click Demo to scroll the below demo in to full view.

Demo

View this code on github

Code Walkthrough

First Name Field

Note that the text typed in the first name gets transformed to uppercase and at the same time get's copied to last name in lowercase. This is achived by using the updater option of atom.

const firstName = atom({
path: ['form', 'person', 'firstName'],
defaultValue: '',
updater: ({ value, oldValue, get, set }) => {
const name = get(lastName);
if (name?.toUpperCase() === oldValue?.toUpperCase()) {
set(lastName, value.toLowerCase());
}
return value.toUpperCase();
},
});

Dynamic Field

The path can be dynamically changed as shown below where it's swtiched between firstName & lastName.

function DynamicField() {
const [field, setField] = useState('firstName');
return (
<div>
<RadioGroup value={field} onChange={(e, value) => setField(value)} row>
<FormControlLabel
value="firstName"
control={<Radio />}
label="First Name"
/>
<FormControlLabel
value="lastName"
control={<Radio />}
label="Last Name"
/>
</RadioGroup>
<TextInput label={field} path={['form', 'person', field]} />
</div>
);
}
function TextInput({ path, label, autoFocus }: Props) {
const [text, setText] = useStateX(path, '');
return (
<>
<TextField
value={text}
onChange={(e) => setText(e.target.value)}
autoFocus={autoFocus}
label={label}
style={{ margin: 8 }}
placeholder={label}
helperText={`@ path [${path.join('.')}]`}
fullWidth
margin="normal"
InputLabelProps={{
shrink: true,
}}
/>
</>
);
}

Full Name Selector

Full Name is derived using a selector. Note that we access firstName & lastName using the get function that is passed as an option to the selector's get function.

const fullName = selector({
path: ['form', 'person', 'fullName'],
defaultValue: '',
get: ({ get }) => {
const fn = get(firstName);
const ln = get(lastName);
if (fn && ln) {
return `${fn} ${ln}`;
}
return '';
},
});

Full Name Character Count Async Selector

Full Name Character Count is derived asyncronusly using a selector that returns a Promise. Note that we access fullName, which is another selector.

const fullNameCount = selector({
path: ['form', 'person', 'fullNameCount'],
defaultValue: 0,
get: ({ get }) => {
const fn = get(fullName);
// return fn.length;
return new Promise((resolve) => {
setTimeout(() => resolve(fn.length), 1000);
});
},
});
function FullNameCount() {
const count = useStateXValue(fullNameCount);
if (count === 0) {
return <>...</>;
}
return <>{count} chars!</>;
}

Clear Button

Use useStateXValueSetter hook to just perform a state update without subscribing to it's value.

function Clear() {
const setFirstName = useStateXValueSetter(firstName);
const setLastName = useStateXValueSetter(lastName);
function clear() {
setFirstName('');
setLastName('');
}
return (
<>
<Button variant="contained" onClick={clear}>
Clear
</Button>
</>
);
}

State JSON

And finally, we can access the complete state by using the parent path e.g. ['form']

function ShowState() {
const json = useStateXValue(['form'], {});
return (
<Card variant="outlined" color="black">
<CardContent>
<pre>{JSON.stringify(json, null, ' ')}</pre>
</CardContent>
</Card>
);
}