We want to separate the logic and view for our components. This makes it easer to design components in storybook, since we can just inject in hardcoded values when designing. When its time to actually use the designed component, we can simply hook it up to its logical counterpart.
We will use custom hooks to do this. See the following example:
import React, { useEffect, useState } from "react";
interface TestComponentProps {
useController: typeof useTestComponentController;
}
const TestComponent: React.FC<TestComponentProps> = ({ useController }) => {
const { name, setName } = useController();
return (
<>
<div>Hello, {name}</div>
<input value={name} onChange={(e) => setName(e.target.value)} />
</>
);
};
export const useTestComponentController = () => {
const [name, setName] = useState("");
useEffect(() => {
console.log(name);
}, [name]);
return {
name,
setName,
};
};
export const useMockTestComponentController: typeof useTestComponentController =
() => {
return { name: "Fraser", setName: () => {} };
};
export default TestComponent;
Our main component TestComponent
will accept a function as a prop. This function should act as a custom hook. This custom hook should contain all logic needed for this component to function, excluding any normal props that can also be passed into the component.
We then define two custom hooks, a real hook, and a mock hook.
This component would be used as follows:
// in the actual application
return (
<TestComponent useController={useTestComponentController} />
)
// in storybook or testing
return (
<TestComponent useController={useMockTestComponentController} />
)
In VS Code, open:
Ctrl + Shift + P
⇒ Preferences: Configure User Snippets
⇒ typescriptreact.json
and paste the following into the snippets object. Once this is done, you can type rtcc
in a tsx
file to quickly create a component that follows the above specification.
"Create complete typescript component with controller hook": {
"prefix": "rtcc",
"body": [
"import React from \\"react\\";",
"",
"interface ${1:$TM_FILENAME_BASE}Props {",
" useController: typeof use${1:$TM_FILENAME_BASE}Controller;",
"}",
"",
"const ${1:$TM_FILENAME_BASE}: React.FC<${1:$TM_FILENAME_BASE}Props> = ({ useController }) => {",
" const {} = useController();",
"",
" return (",
" <div>",
" $0",
" </div>",
" );",
"};",
"",
"export const use${1:$TM_FILENAME_BASE}Controller = () => {",
" return {};",
"};",
"",
"export const useMock${1:$TM_FILENAME_BASE}Controller: typeof use${1:$TM_FILENAME_BASE}Controller = () => {",
" return {};",
"};",
"",
"export default $TM_FILENAME_BASE;"
]
}