Reputation: 17997
I have the following component that takes a generic node
and patchCurrentNode
props.
import { css } from '@emotion/react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, {
PropsWithChildren,
useState,
} from 'react';
import BaseNodeData from '../../types/BaseNodeData';
import { PatchCurrentNode } from '../../types/BaseNodeProps';
type Props<NodeData extends BaseNodeData = BaseNodeData> = {
node: NodeData;
patchCurrentNode: PatchCurrentNode<NodeData>;
};
/**
* Input that stores the name of the node's selected value.
*
* Some nodes ask for an input (text, choice, etc.), the selected value will be stored and be indexed using the selected variable's name.
*/
export const VariableNameInput: <NodeData extends BaseNodeData = BaseNodeData>(p: PropsWithChildren<Props<NodeData>>) => React.ReactElement = (props) => {
const {
node,
patchCurrentNode,
} = props;
const {
width = 200,
} = node;
console.log('node', node);
const [variableName, setVariableName] = useState<string | undefined>(node?.data?.variableName);
console.log('VariableNameInput variableName', variableName);
const onChange = (event: any) => {
console.log('onChange variableName', event, event?.target?.value);
setVariableName(event?.target?.value);
};
const onSubmit = () => {
console.log('onSubmit variableName', variableName);
patchCurrentNode({
data: {
variableName: variableName,
},
});
};
return (
<div
className={'variable-name-container'}
css={css`
position: absolute;
bottom: 0;
background-color: black;
width: ${width}px;
height: 50px;
margin-left: -15px;
margin-bottom: -15px;
padding-left: 15px;
border-radius: 5px;
.variable-name {
width: ${width - 50}px;
margin-top: 12px;
margin-left: -5px;
padding-left: 5px;
background-color: black;
color: ${variableName?.length ? 'white' : '#6E6E6E'}; // Change different color between placeholder and actual value
border: 1px solid #6E6E6E;
}
.submit {
color: #6E6E6E;
margin-left: 10px;
cursor: pointer;
}
`}
>
<input
className={'variable-name'}
placeholder={'Variable name'}
value={variableName}
onChange={onChange}
/>
<FontAwesomeIcon
className={'submit'}
icon={['fas', 'paper-plane']}
onClick={onSubmit}
/>
</div>
);
};
export default VariableNameInput;
It is used like this:
<VariableNameInput node={node} patchCurrentNode={patchCurrentNode} />
With the following types:
import BaseNodeData from '../BaseNodeData';
import { QuestionNodeAdditionalData } from './QuestionNodeAdditionalData';
export type QuestionNodeData = BaseNodeData<QuestionNodeAdditionalData>;
---
import BaseNodeAdditionalData from '../BaseNodeAdditionalData';
import { QuestionChoiceType } from './QuestionChoiceType';
export type QuestionNodeAdditionalData = BaseNodeAdditionalData & {
text?: string;
questionType?: QuestionChoiceType;
variableName?: string;
};
The implementation of the generic in the component works. But, I still have TS errors:
How can I reuse the generic NodeData
type in the component's implementation?
I was thinking I could do something like node?.data?.variableName as NodeData
, but NodeData
isn't known there.
Edit: Solution
Upvotes: 0
Views: 1441
Reputation: 42170
You aren't getting access to the generic NodeData
inside of the component body due to how you have defined the component's type signature separately from it's implementation.
export const VariableNameInput: <NodeData extends BaseNodeData = BaseNodeData>(
p: PropsWithChildren<Props<NodeData>>
) => React.ReactElement = (props) => {
// ...
};
Instead of applying the type annotations to the VariableNameInput
variable, apply them directly to the function arguments and return type.
export const VariableNameInput = <NodeData extends BaseNodeData = BaseNodeData>(
props: PropsWithChildren<Props<NodeData>>
): React.ReactElement => {
// ...
};
You are now able to access NodeData
inside the component.
useState<string | undefined>((node as NodeData).data?.variableName);
But you don't get any additional information by asserting this since node
is a required prop and it is always type NodeData
.
It seems more likely that you need to define variableName
as an optional string
prop on the BaseNodeAdditionalData
type. If you edit your post to include the types for BaseNodeData
and PatchCurrentNode
(and their dependencies) then perhaps I can help you fix the underlying issues.
It's the callback patchCurrentNode
that is going to give you the most trouble because NodeData
could extend BaseNodeData
by requiring additional properties or more specific values. You can assert as Partial<NodeData>
, but you want to be confident that any assertion you make is actually correct.
Upvotes: 3