Skip to content

Commit

Permalink
Merge pull request #3 from jawnstreams/johnrom/downstream
Browse files Browse the repository at this point in the history
Consolidate State and Add Tutorial + Fixtures.
  • Loading branch information
johnrom committed Mar 22, 2021
2 parents a5f4643 + c9b9579 commit 611432a
Show file tree
Hide file tree
Showing 27 changed files with 803 additions and 131 deletions.
21 changes: 21 additions & 0 deletions app/components/debugging/Collapse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react';

export const Collapse: React.FC = props => {
const [collapsed, setCollapsed] = React.useState(false);

return (
<div>
<button type="button" onClick={() => setCollapsed(!collapsed)}>
Collapse
</button>
<div
style={{
overflow: 'hidden',
height: collapsed ? 0 : 'auto',
}}
>
{props.children}
</div>
</div>
);
};
9 changes: 9 additions & 0 deletions app/components/debugging/DebugFieldState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as React from 'react';
import { UseFieldProps, useField } from 'formik';
import { DebugProps } from './DebugProps';

export const DebugFieldState = (props: UseFieldProps) => {
const [field, meta, helpers] = useField(props);

return <DebugProps {...{ ...field, ...meta, ...helpers }} />;
};
9 changes: 9 additions & 0 deletions app/components/debugging/DebugFormikState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as React from 'react';
import { useFormikContext } from 'formik';
import { DebugProps } from './DebugProps';

export const DebugFormikState = () => {
const formikState = useFormikContext();

return <DebugProps {...formikState} />;
};
19 changes: 19 additions & 0 deletions app/components/debugging/DebugProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';

export const DebugProps = (props?: any) => {
const renderCount = React.useRef(0);
return (
<div style={{ margin: '1rem 0' }}>
<pre
style={{
background: '#f6f8fa',
fontSize: '.65rem',
padding: '.5rem',
}}
>
<strong>props</strong> = {JSON.stringify(props, null, 2)}
<strong>renders</strong> = {renderCount.current++}
</pre>
</div>
);
};
9 changes: 9 additions & 0 deletions app/helpers/array-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { selectRandomInt } from './random-helpers';

export const selectRange = (count: number) => Array.from(Array(count).keys());

export const selectRandomArrayItem = <T extends any>(array: T[]) => {
const index = selectRandomInt(array.length);

return array[index];
};
89 changes: 89 additions & 0 deletions app/helpers/chaos-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { FormikApi } from 'formik';
import { useMemo, useEffect } from 'react';
import { selectRandomInt } from './random-helpers';

export type DynamicValues = Record<string, string>;

export const useChaosHelpers = (
formik: FormikApi<DynamicValues>,
array: number[]
) => {
return useMemo(
() => [
() =>
formik.setValues(
array.reduce<Record<string, string>>((prev, id) => {
prev[`Input ${id}`] = selectRandomInt(500).toString();

if (prev[`Input ${id}`]) {
}

return prev;
}, {})
),
() =>
formik.setErrors(
array.reduce<Record<string, string>>((prev, id) => {
const error = selectRandomInt(500);

// leave some errors empty
prev[`Input ${id}`] = error % 5 === 0 ? '' : error.toString();

return prev;
}, {})
),
() =>
formik.setTouched(
array.reduce<Record<string, boolean>>((prev, id) => {
prev[`Input ${id}`] = selectRandomInt(500) % 2 === 0;

return prev;
}, {})
),
() => formik.submitForm(),
() =>
formik.setFieldValue(
`Input ${selectRandomInt(array.length)}`,
selectRandomInt(500).toString()
),
() =>
formik.setFieldError(
`Input ${selectRandomInt(array.length)}`,
selectRandomInt(500).toString()
),
() =>
formik.setFieldTouched(
`Input ${selectRandomInt(array.length)}`,
selectRandomInt(2) % 2 === 0
),
() => formik.setStatus(selectRandomInt(500).toString()),
() => formik.resetForm(),
],
[array, formik]
);
};

let skipCount = 0;

/**
* https://github.com/dai-shi/will-this-react-global-state-work-in-concurrent-mode
*/
export const useAutoUpdate = () => {
useEffect(() => {
if (typeof document !== 'undefined') {
skipCount += 1;

if (skipCount % 10 === 0) {
document.getElementById('update-without-transition')?.click();
}
}
}, []);

// SSR
if (typeof performance !== 'undefined') {
const start = performance?.now();
while (performance?.now() - start < 2) {
// empty
}
}
};
9 changes: 9 additions & 0 deletions app/helpers/random-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @param minOrMax // The maximum is exclusive and the minimum is inclusive
* @param max
*/
export const selectRandomInt = (minOrMax: number, max?: number) => {
const min = max ? minOrMax : 0;
max = max ? max : minOrMax;
return Math.floor(Math.random() * (max - min)) + min;
};
41 changes: 41 additions & 0 deletions app/helpers/tearing-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { selectRange } from './array-helpers';
import { useState, useCallback, useMemo } from 'react';
import { useEffect } from 'react';

/**
* Check if all elements show the same number.
* https://github.com/dai-shi/will-this-react-global-state-work-in-concurrent-mode
*/
export const useCheckTearing = (elementCount: number, skip = 0) => {
const ids = useMemo(() => selectRange(elementCount).slice(skip), [
elementCount,
skip,
]);
const checkMatches = useCallback(() => {
const [first, ...rest] = ids;
const firstValue = document.querySelector(`#input-${first} code`)
?.innerHTML;
return rest.every(id => {
const thisValue = document.querySelector(`#input-${id} code`)?.innerHTML;
const tore = thisValue !== firstValue;
if (tore) {
console.log('useCheckTearing: tore');
console.log(thisValue);
console.log(firstValue);
}
return !tore;
});
}, [ids]);
const [didTear, setDidTear] = useState(false);

// We won't create an infinite loop switching this boolean once, I promise.
// (famous last words)
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
if (!didTear && !checkMatches()) {
setDidTear(true);
}
});

return didTear;
};
File renamed without changes.
56 changes: 56 additions & 0 deletions app/pages/fixtures/components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from 'react';
import { Formik, Field, Form, FieldProps } from 'formik';
import { DebugProps } from '../../components/debugging/DebugProps';

const initialValues = {
name: '',
};

const RenderComponent = (props: FieldProps<string, typeof initialValues>) => (
<>
<input data-testid="child" {...props.field} />
<DebugProps {...props} />
</>
);
const ComponentComponent = (
props: FieldProps<string, typeof initialValues>
) => (
<>
<input data-testid="child" {...props.field} />
<DebugProps {...props} />
</>
);
const AsComponent = (
props: FieldProps<string, typeof initialValues>['field']
) => (
<>
<input data-testid="child" {...props} />
<DebugProps {...props} />
</>
);

const ComponentsPage = () => (
<div>
<h1>Test Components</h1>
<Formik
initialValues={initialValues}
validate={values => {
console.log(values);
}}
onSubmit={async values => {
await new Promise(r => setTimeout(r, 500));
alert(JSON.stringify(values, null, 2));
}}
>
<Form>
<Field name="name" children={RenderComponent} />
<Field name="name" render={RenderComponent} />
<Field name="name" component={ComponentComponent} />
<Field name="name" as={AsComponent} />
<button type="submit">Submit</button>
</Form>
</Formik>
</div>
);

export default ComponentsPage;
123 changes: 123 additions & 0 deletions app/pages/fixtures/perf.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import * as React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';

let renderCount = 0;

const PerfPage = () => (
<div>
<h1>Sign Up</h1>
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
favorite: '',
checked: [],
picked: '',
lastName2: '',
lastName3: '',
lastName4: '',
lastName5: '',
lastName6: '',
lastName7: '',
lastName8: '',
lastName9: '',
lastName10: '',
lastName11: '',
lastName12: '',
lastName13: '',
lastName14: '',
lastName15: '',
lastName16: '',
lastName17: '',
lastName18: '',
lastName19: '',
lastName20: '',
}}
validationSchema={Yup.object().shape({
email: Yup.string().email('Invalid email address').required('Required'),
firstName: Yup.string().required('Required'),
lastName: Yup.string()
.min(2, 'Must be longer than 2 characters')
.max(20, 'Nice try, nobody has a last name that long')
.required('Required'),
})}
onSubmit={async values => {
await new Promise(r => setTimeout(r, 500));
alert(JSON.stringify(values, null, 2));
}}
>
<Form>
<Field name="firstName" placeholder="Jane" />
<ErrorMessage name="firstName" component="p" />

<Field name="lastName" placeholder="Doe" />
<Field name="lastName2" placeholder="Doe" />
<Field name="lastName3" placeholder="Doe" />
<Field name="lastName4" placeholder="Doe" />
<Field name="lastName5" placeholder="Doe" />
<Field name="lastName6" placeholder="Doe" />
<Field name="lastName7" placeholder="Doe" />
<Field name="lastName8" placeholder="Doe" />
<Field name="lastName9" placeholder="Doe" />
<Field name="lastName10" placeholder="Doe" />
<Field name="lastName11" placeholder="Doe" />
<Field name="lastName12" placeholder="Doe" />
<Field name="lastName13" placeholder="Doe" />
<Field name="lastName14" placeholder="Doe" />
<Field name="lastName15" placeholder="Doe" />
<Field name="lastName16" placeholder="Doe" />
<Field name="lastName17" placeholder="Doe" />
<Field name="lastName18" placeholder="Doe" />
<Field name="lastName19" placeholder="Doe" />
<Field name="lastName20" placeholder="Doe" />
<ErrorMessage name="lastName" component="p" />

<Field
id="email"
name="email"
placeholder="jane@acme.com"
type="email"
/>
<ErrorMessage name="email" component="p" />

<label>
<Field type="checkbox" name="toggle" />
<span style={{ marginLeft: 3 }}>Toggle</span>
</label>

<div id="checkbox-group">Checkbox Group </div>
<div role="group" aria-labelledby="checkbox-group">
<label>
<Field type="checkbox" name="checked" value="One" />
One
</label>
<label>
<Field type="checkbox" name="checked" value="Two" />
Two
</label>
<label>
<Field type="checkbox" name="checked" value="Three" />
Three
</label>
</div>
<div id="my-radio-group">Picked</div>
<div role="group" aria-labelledby="my-radio-group">
<label>
<Field type="radio" name="picked" value="One" />
One
</label>
<label>
<Field type="radio" name="picked" value="Two" />
Two
</label>
</div>
<button type="submit">Submit</button>
<div id="renderCounter">{renderCount++}</div>
</Form>
</Formik>
</div>
);

export default PerfPage;
Loading

0 comments on commit 611432a

Please sign in to comment.