Skip to content

Commit

Permalink
fix(render): race conditioning when rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
ido-pluto committed Jul 7, 2024
1 parent 3e99189 commit f442303
Show file tree
Hide file tree
Showing 16 changed files with 599 additions and 124 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,16 @@ import {WebForms} from '@astro-utils/forms/forms.js';
</WebForms>
```

### Easy debugging
When vite reloads the page, the browser will popup confirmation dialog. This is annoying when you are debugging. You can disable this by using the astro-utils integration
### Code Integration
This changes astro behavior to allow the form to work, it ensure the components render by the order they are in the file.

`astro.config.mjs`
```js
import { defineConfig } from 'astro/config';
import astroFormsDebug from "@astro-utils/forms/dist/integration.js";
import astroForms from "@astro-utils/forms/dist/integration.js";

export default defineConfig({
output: 'server',
integrations: [astroFormsDebug]
integrations: [astroForms]
});
```
3 changes: 2 additions & 1 deletion examples/simple-form/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {defineConfig} from 'astro/config';
import react from '@astrojs/react';
import astroFormsDebug from "@astro-utils/forms/dist/integration.js";

// https://astro.build/config
export default defineConfig({
output: "server",
integrations: [react()]
integrations: [react(), astroFormsDebug]
});
2 changes: 2 additions & 0 deletions examples/simple-form/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"dependencies": {
"@astro-utils/express-endpoints": "^0.0.1",
"@astro-utils/forms": "^0.0.1",
"@astrojs/check": "^0.7.0",
"@astrojs/react": "^3.6.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand All @@ -22,6 +23,7 @@
"react-dom": "^18.3.1",
"reactstrap": "^9.2.1",
"sleep-promise": "^9.1.0",
"typescript": "^5.5.3",
"zod": "^3.22.4"
}
}
611 changes: 533 additions & 78 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions packages/forms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,17 @@ import {WebForms} from '@astro-utils/forms/forms.js';
</WebForms>
```

### Easy debugging
When vite reloads the page, the browser will popup confirmation dialog. This is annoying when you are debugging. You can disable this by using the astro-utils integration
### Code Integration
This changes astro behavior to allow the form to work, it ensure the components render by the order they are in the file.

`astro.config.mjs`
```js
import { defineConfig } from 'astro/config';
import astroFormsDebug from "@astro-utils/forms/dist/integration.js";
import astroForms from "@astro-utils/forms/dist/integration.js";

export default defineConfig({
output: 'server',
integrations: [astroFormsDebug]
integrations: [astroForms]
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default class ViewStateManager {
return this.stateProp && this._astro.request.method === 'POST';
}

constructor(private _bind: BindForm<any>, private _elementsState: any, private _astro: AstroGlobal, private _bindId: string) {
constructor(private _bind: BindForm<any>, private _elementsState: any, private _astro: AstroGlobal, private _bindId: string | number) {
this._FORM_OPTIONS = getFormOptions(_astro);

if (!this._FORM_OPTIONS.secret) {
Expand Down
3 changes: 1 addition & 2 deletions packages/forms/src/components/WebForms.astro
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ const context = {
...Astro.props,
webFormsSettings: { haveFileUpload: false },
tempValues: {},
statesKey: new Set<string>(),
};
const htmlSolt = await asyncContext(() => Astro.slots.render('default'), Astro, { name: '@astro-utils/forms', context, lock: 'webForms' });
const { webFormsSettings, tempValues, statesKey, loadingClassName = 'loading', ...props } = context;
const { webFormsSettings, tempValues, loadingClassName = 'loading', ...props } = context;
if (webFormsSettings.haveFileUpload) {
props.enctype = 'multipart/form-data';
}
Expand Down
1 change: 1 addition & 0 deletions packages/forms/src/components/form/BInput.astro
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface ModifyInputProps {
max?: number | string | undefined | null | Date;
min?: number | string | undefined | null | Date;
onSubmitClick?: string;
[key: `data-${string}`]: any;
}
export interface Props<T extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>> extends Partial<ModifyDeep<astroHTML.JSX.InputHTMLAttributes, ModifyInputProps>> {
Expand Down
2 changes: 2 additions & 0 deletions packages/forms/src/components/form/BTextarea.astro
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ZodType } from 'zod';
interface ModifyInputProps {
minlength?: number;
maxlength?: number;
[key: `data-${string}`]: any;
}
export interface Props<T extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>> extends Partial<ModifyDeep<astroHTML.JSX.TextareaHTMLAttributes, ModifyInputProps>> {
Expand All @@ -20,6 +21,7 @@ export interface Props<T extends keyof JSX.IntrinsicElements | React.JSXElementC
as?: T;
props?: React.ComponentProps<T>;
onSubmitClick?: string;
[key: `data-${string}`]: any;
}
if (!Astro.props.name) {
Expand Down
18 changes: 3 additions & 15 deletions packages/forms/src/components/form/BindForm.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,20 @@ import { asyncContext } from '@astro-utils/context';
import getContext from '@astro-utils/context';
import ViewStateManager from '../../components-control/form-utils/view-state.js';
import Bind from '../../components-control/form-utils/bind-form.js';
import { MissingKeyPropError } from '../../errors/MissingKeyPropError.js';
import { NotAUniqueKeyError } from '../../errors/NotAUniqueKeyError.js';
export interface Props {
bind?: ReturnType<typeof Bind>;
state?: boolean | string[];
omitState?: string[];
defaultSubmitClick?: string | false;
key: string;
}
const { statesKey, lastRender: parantLastRender, bindId: parantbindId = '' } = getContext(Astro, '@astro-utils/forms');
const { lastRender: parantLastRender, bindId: parantbindId = '' } = getContext(Astro, '@astro-utils/forms');
if (parantLastRender === false) {
return;
}
const { bind = Bind(), defaultSubmitClick, key } = Astro.props;
if (!key) {
throw new MissingKeyPropError();
} else {
if (statesKey.has(key)) {
throw new NotAUniqueKeyError(key);
}
statesKey.add(key);
}
const { bind = Bind(), defaultSubmitClick } = Astro.props;
const context = {
executeAfter: [],
Expand All @@ -39,7 +27,7 @@ const context = {
onSubmitClickGlobal: defaultSubmitClick,
buttonIds: [] as [string, string | null, boolean][],
settings: { showValidationErrors: false },
bindId: key,
bindId: Astro.locals.__formsInternalUtils.bindFormCounter++,
lastRender: false,
newState: false,
};
Expand Down
7 changes: 0 additions & 7 deletions packages/forms/src/errors/MissingKeyPropError.ts

This file was deleted.

7 changes: 0 additions & 7 deletions packages/forms/src/errors/NotAUniqueKeyError.ts

This file was deleted.

18 changes: 14 additions & 4 deletions packages/forms/src/integration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { writeFile } from "fs/promises";
import { refactorCodeInlineRenderComponent } from "./integration/codeTransform.js";

export default {
name: '@astro-utils/forms',
hooks: {
Expand All @@ -8,12 +11,19 @@ export default {

config.vite.plugins.push({
name: 'astro-utils-dev',
apply: 'serve',
enforce: 'post',
transform(code, id) {
async transform(code: string, id: string) {
if(code.includes('class RenderTemplateResult')){
code = refactorCodeInlineRenderComponent(code);
if(id.includes("/node_modules/astro/")){
await writeFile(id, code);
}
}

if (id.endsWith('node_modules/vite/dist/client/client.mjs')) {
return code.replaceAll(/\blocation\.reload\(\)(([\s;])|\b)/g, "window.open(location.href, '_self')$1")
return code.replace(/\blocation\.reload\(\)(([\s;])|\b)/g, "window.open(location.href, '_self')$1")
}

return code;
}
});
}
Expand Down
29 changes: 29 additions & 0 deletions packages/forms/src/integration/codeTransform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

/**
const expRenders = this.expressions.map(exp => {
return renderToBufferDestination(bufferDestination => {
if (exp || exp === 0) {
return renderChild(bufferDestination, exp);
}
});
});
*/
const ASYNC_RENDERS_REGEX = /const\s+expRenders\s*=\s*this\.expressions\.map\s*\(\s*\(?exp\)?\s*=>\s*{\s*return\s+renderToBufferDestination\s*\(\s*\(?bufferDestination\)?\s*=>\s*{\s*if\s*\(\s*exp\s*\|\|\s*exp\s*===\s*0\s*\)\s*{\s*return\s+renderChild\s*\(\s*bufferDestination\s*,\s*exp\s*\);\s*}\s*}\s*\);\s*}\s*\);/gs;

const SYNC_RENDERS_CODE = `
const expRenders = [];
for (const exp of this.expressions) {
const promise = renderToBufferDestination(bufferDestination => {
if (exp || exp === 0) {
return renderChild(bufferDestination, exp);
}
});
await promise.renderPromise;
expRenders.push(promise);
}
`;

export function refactorCodeInlineRenderComponent(sourceCode: string): string {
return sourceCode.replace(ASYNC_RENDERS_REGEX, SYNC_RENDERS_CODE);
}
3 changes: 2 additions & 1 deletion packages/forms/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export default function astroForms(settings: Partial<FormsSettings> = {}) {
locals.forms = new FormsReact(likeAstro);

locals.__formsInternalUtils = {
FORM_OPTIONS: FORM_OPTIONS
FORM_OPTIONS: FORM_OPTIONS,
bindFormCounter: 0
};

await ensureValidationSecret(likeAstro);
Expand Down
1 change: 1 addition & 0 deletions packages/forms/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ declare global {
*/
__formsInternalUtils: {
FORM_OPTIONS: FormsSettings;
bindFormCounter: number;
};
forms: FormsReact;
webFormOff?: boolean;
Expand Down

0 comments on commit f442303

Please sign in to comment.