Advanced ts: playing with generics in a React.Context

This is a story of failure.
I wasn’t going to write this down… then I thought, well not all the stories need a happy ending.

To the point.

We have a custom React Context. Don’t want to dig deep into its internals. We’re ok checking just how we use…


This content originally appeared on DEV Community and was authored by Manuel Artero Anguita 🟨

This is a story of failure.

I wasn't going to write this down... then I thought, well not all the stories need a happy ending.

To the point.

We have a custom React Context. Don't want to dig deep into its internals. We're ok checking just how we use it:

import { XRouter, XRoute } from '@serious-company.react';
...

<XRouter>
  <XRoute path='/about' component={About} />
  <XRoute path='/checkout' component={Checkout} />
</XRouter>

&

import { useXRouter } from '@serious-company.react';
...

const { navigate } = useXRouter();
const onClick = () => navigate('/checkout');

...🤔 I wonder...

Is it possible to set the exact set of routes?
so both <XRoute /> and navigate() would narrow the type of the path from string to one of my routes?

I was looking for this:

type AvailableRoutes = '/about' | '/checkout';
...

const onClick = () => navigate('/ceckout');       // ❌ tsc
const onClick = () => navigate('/checkout');      // ✅
...

<XRouter>
  <XRoute path='/abbot' component={About} />      // ❌ tsc
  <XRoute path='/about' component={About} />      // ✅
</XRouter>

👉 The answer is yep that's possible ... but also No

Generics

Got down to work replacing each declaration of a path, from string to T where <T extends string>:

-type XRouterContextType {
-  navigate(path: string) => void;
-  history: string[];
+type XRouterContextType<T extends string> {
+  navigate(path: T) => void;
+  history: T[];
}
-function XRouter({ children }: XRouterProps) {
-  const [history, setHistory] = useState(['/'])
-
-  const navigate: (path: string) => {
+function XRouter<T extends string>({ children }): XRouterProps) {
+  const [history, setHistory] = useState<T[]>(['/')
+
+  const navigate: (path: T) => {
... 
-type XRouteProps {
-  path: string;
+type XRouteProps<T> {
+  path: T;
  component: ComponentType;
}

-function XRoute({ path, component }: XRouteProps) {
+function XRoute<T extends string>({ path, component }): XRouteProps<T> {
...

And everything was pure and flawless, just like JavaScript...
...till the context object

Creating a React context, we'll export a Context Provider (<XRouter> in these examples) and a way to consume that Context - probably - in a form of useContext() (useXRouter() in these examples)

Thing is, you need to create this context object:

const context = createContext<XRouterContextType>({ ... })

This object has a well defined type. You just can't create an object but keep it with a futuristic Generic T.

🖋️ A way of understanding Generics in Typescript might be:

"We just don't know T yet.
T will be set by the caller of the function.
They will know.
For now, T is unset"

So, creating an object:

const context = createContext<XRouterContextType>({ ... })

You are the caller. You need to set the type. You can't create an object with a "we'll see" type.

const context = createContext<XRouterContextType<T>>({ ... }) // ❌

So, is this an impossible problem?

Well, we may implement a work around... exporting a closure function - call it factory, call it Class, call it High Order function, call it whatever you feel - which, when called: creates the context object and returns the Provider component and the hook.

I got a branch with this working:

import { createXRouter } from '@serious-company.react';

type AvailableRoutes = '/about' | '/checkout';

const { XRouter, XRoute, useXRouter } = createXRouter<AvailableRoutes>();
...

const { navigate } = useXRouter();
const onClick = () => navigate('/ceckout');       // ❌ tsc
const onClick = () => navigate('/checkout');      // ✅
...

<XRouter>
  <XRoute path='/abbot' component={About} />      // ❌ tsc
  <XRoute path='/about' component={About} />      // ✅
</XRouter>
...

But this would mean changing the API of the package

import { XRouter } from '@serious-company.react';

=>

import { createXRouter } from '@serious-company.react';
const { XRouter } = createXRouter<'/a'|'/b'>();

And we finally discarded this work.

As I said, this is a story of failure. At least I did have a good time 🤘.

Thanks for reading 💛.


This content originally appeared on DEV Community and was authored by Manuel Artero Anguita 🟨


Print Share Comment Cite Upload Translate Updates
APA

Manuel Artero Anguita 🟨 | Sciencx (2023-03-31T12:11:44+00:00) Advanced ts: playing with generics in a React.Context. Retrieved from https://www.scien.cx/2023/03/31/advanced-ts-playing-with-generics-in-a-react-context/

MLA
" » Advanced ts: playing with generics in a React.Context." Manuel Artero Anguita 🟨 | Sciencx - Friday March 31, 2023, https://www.scien.cx/2023/03/31/advanced-ts-playing-with-generics-in-a-react-context/
HARVARD
Manuel Artero Anguita 🟨 | Sciencx Friday March 31, 2023 » Advanced ts: playing with generics in a React.Context., viewed ,<https://www.scien.cx/2023/03/31/advanced-ts-playing-with-generics-in-a-react-context/>
VANCOUVER
Manuel Artero Anguita 🟨 | Sciencx - » Advanced ts: playing with generics in a React.Context. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/03/31/advanced-ts-playing-with-generics-in-a-react-context/
CHICAGO
" » Advanced ts: playing with generics in a React.Context." Manuel Artero Anguita 🟨 | Sciencx - Accessed . https://www.scien.cx/2023/03/31/advanced-ts-playing-with-generics-in-a-react-context/
IEEE
" » Advanced ts: playing with generics in a React.Context." Manuel Artero Anguita 🟨 | Sciencx [Online]. Available: https://www.scien.cx/2023/03/31/advanced-ts-playing-with-generics-in-a-react-context/. [Accessed: ]
rf:citation
» Advanced ts: playing with generics in a React.Context | Manuel Artero Anguita 🟨 | Sciencx | https://www.scien.cx/2023/03/31/advanced-ts-playing-with-generics-in-a-react-context/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.