Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only apply forwardRef for React when props.ref is passed #1172

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

crutchcorn
Copy link
Contributor

This PR builds on top of #1170, please merge that first

Description

This PR ensures that forwardRefs only get passed in React when:
- props.ref is passed
- useMetadata has a passed name that matches a ref

@crutchcorn crutchcorn requested a review from samijaber as a code owner May 9, 2023 13:25
@vercel
Copy link

vercel bot commented May 9, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
mitosis-fiddle ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 23, 2023 4:58am

@crutchcorn crutchcorn changed the title React renderer Only apply forwardRef for React when props.ref is passed May 9, 2023
@crutchcorn
Copy link
Contributor Author

Fixes #1159

@samijaber
Copy link
Contributor

can you handle the conflicts in this branch and merge main so we see a cleaner diff?

# Conflicts:
#	packages/core/src/__tests__/__snapshots__/preact.test.ts.snap
#	packages/core/src/generators/react/generator.ts
@@ -14,7 +14,8 @@ const prohibitedKeywordRE = new RegExp(
'extends,finally,continue,debugger,function,arguments,typeof,void,' +
// both below conditions added based on https://github.com/vuejs/vue/blob/e80cd09fff570df57d608f8f5aaccee6d7f31917/src/core/instance/state.ts#L89-L96
// below line added from https://github.com/vuejs/vue/blob/8880b55d52f8d873f79ef67436217c8752cddef5/src/shared/util.ts#L130
'key,ref,slot,slot-scope,is,' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is used by many generators, and we need to forbid these reserved words for Vue as per the comment above: https://github.com/vuejs/vue/blob/8880b55d52f8d873f79ef67436217c8752cddef5/src/shared/util.ts#L130

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative method:

Can we break this into two distinct regex keywords so that they only apply to the specific generator?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah.

I'd do that by adding a target argument to getProps, and toggle parts of the regex based on the need. Something like:

target === 'react' ? '' : 'ref'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized this function is never used by the React generatore, so I'm not sure your changes are doing anything.

# Conflicts:
#	packages/core/src/generators/react/generator.ts
@nx-cloud
Copy link

nx-cloud bot commented May 23, 2023

☁️ Nx Cloud Report

CI is running/has finished running commands for commit d249c8e. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this branch


✅ Successfully ran 4 targets

Sent with 💌 from NxCloud.

@@ -14,7 +14,8 @@ const prohibitedKeywordRE = new RegExp(
'extends,finally,continue,debugger,function,arguments,typeof,void,' +
// both below conditions added based on https://github.com/vuejs/vue/blob/e80cd09fff570df57d608f8f5aaccee6d7f31917/src/core/instance/state.ts#L89-L96
// below line added from https://github.com/vuejs/vue/blob/8880b55d52f8d873f79ef67436217c8752cddef5/src/shared/util.ts#L130
'key,ref,slot,slot-scope,is,' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized this function is never used by the React generatore, so I'm not sure your changes are doing anything.

traverse(json).forEach(function (item) {
if (isMitosisNode(item)) {
const binding = item.bindings.ref;
const regexp = /(.+)?props\.(ref)( |\)|;|\()?$/;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to avoid adding new regexes to the codebase moving forward and rely instead on Babel transformers. Regexes are very error-prone and brittle, and hard to understand. I can tell you copy/pasted similar helpers used by other generators but this stuff causes lots of bugs.

To use Babel, you can rely on https://astexplorer.net/ to figure out how the AST is structured, or look at some examples in the codebase:

const replaceRefsInString = (code: string, refs: string[], mapper: RefMapper) => {
return babelTransformExpression(code, {
Identifier(path: NodePath<types.Identifier>) {
const name = path.node.name;
const isRef = refs.includes(name);
if (isRef) {
path.replaceWith(types.identifier(mapper(name)));
}
},
});
};
export const mapRefs = (component: MitosisComponent, mapper: RefMapper): void => {
const refSet = getRefs(component);
// grab refs not used for bindings
Object.keys(component.refs).forEach((ref) => refSet.add(ref));
const refs = Array.from(refSet);
for (const key of Object.keys(component.state)) {
const stateVal = component.state[key];
if (typeof stateVal?.code === 'string') {
const value = stateVal.code;
switch (stateVal.type) {
case 'method':
case 'getter':
const isGet = stateVal.type === 'getter';
const isSet = Boolean(value.match(SETTER));
component.state[key] = {
code: replaceRefsInString(
value.replace(/^(get |set )?/, 'function '),
refs,
mapper,
).replace(/^function /, isGet ? 'get ' : isSet ? 'set ' : ''),
type: stateVal.type,
};
break;
case 'function':
component.state[key] = {
code: replaceRefsInString(value, refs, mapper),
type: 'function',
};
break;
default:
break;
}
}
}
traverse(component).forEach(function (item) {
if (isMitosisNode(item)) {
for (const key of Object.keys(item.bindings)) {
const value = item.bindings[key];
if (typeof value === 'object' && key !== 'ref') {
item.bindings[key] = {
...value,
code: replaceRefsInString(value.code as string, refs, mapper),
};
}
}
}
});
for (const key of Object.keys(component.hooks) as (keyof typeof component.hooks)[]) {
const hooks = component.hooks[key];
if (Array.isArray(hooks)) {
hooks.forEach((hook) => {
if (hook.code) {
hook.code = replaceRefsInString(hook.code, refs, mapper);
}
if (hook.deps) {
hook.deps = replaceRefsInString(hook.deps, refs, mapper);
}
});
} else {
const hookCode = hooks?.code;
if (hookCode) {
hooks.code = replaceRefsInString(hookCode, refs, mapper);
}
if (hooks?.deps) {
hooks.deps = replaceRefsInString(hooks?.deps, refs, mapper);
}
}
}
};

I made a quick example that should do what you want here: https://astexplorer.net/#/gist/5b7fcfe92b39eb399aea7236758befa2/latest

You can reuse babelTransformExpression to run the transform on the code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants