TLDR
Use let's you read the value of a Promise or a Context inside a component
const value = use(resource)
A short example we can use it to read context or promise values like so:
import { use } from "react";
function MessageComponent ({ messagePromise }) {
const message = use(messagePromise);
const theme = use(ThemeContext);
}
The longer explanation...
The component where use is called needs to be integrated with Suspense
and optionally error boundaries
. While resolving the promise and getting the data from context
, the fallback of Suspense
will be shown. Once the Promise is resolved, the fallback is replaced by the rendered components using the data returned by the use
API.
If the Promise passed to use
is rejected, the fallback of the nearest Error Boundary will be displayed.
Using "use" with React Context
Setting up Context
First, we need to get an app hooked up with Context
.
Say we want to set up a custom theme, light and dark. We should create a file called ThemeContext.ts
:
// ThemeContext.ts
import { createContext, Dispatch, SetStateAction } from 'react';
type Theme = 'light' | 'dark';
interface IThemeContext {
theme: Theme;
setTheme: Dispatch<SetStateAction<Theme>>;
}
export const ThemeContext = createContext<IThemeContext>({
theme: 'dark',
setTheme: () => {},
});
The above creates our ThemeContext object, which we can import from any file to grab the current theme and the setTheme will be used to update said theme. We're defaulting the theme to be "dark" as that looks a lot cooler.
Then to store all of our apps contexts we will create a Context.tsx
component, to keep our app nicely organised:
// Context.tsx
import React, { useState } from 'react';
import { ThemeContext } from './ThemeContext';
import { Theme } from '../types';
export default function Context({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>('dark');
return (
<ThemeContext.Provider
value={{
theme,
setTheme: setTheme,
}}
>
{children}
</ThemeContext.Provider>
);
}
We will pass the state object to our ThemeContext.Provider
, which will be used for reading and updating theme through our app. Finally, to get this all hooked up, we need to wrap our <App />
component in our <Context />
component.
// index.tsx
// ... Other imports etc.
import Context from './Context.tsx'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<Context>
<App />
</Context>
</React.StrictMode>
);
Now <App />
and its children (which is basically our whole project) will have access to our ThemeContext
.
Putting "use" to use with Context
Let's create a component that will update the theme of our app! Components/ContextExample.tsx
:
// ContextExample.tsx
import { use } from 'react';
import { ThemeContext } from '../context/ThemeContext';
export default function ContextExample() {
const { setTheme } = use(ThemeContext);
const handleThemeToggle = () => {
setTheme((prevTheme) => {
if (prevTheme === 'light') {
return 'dark';
} else {
return 'light';
}
});
};
return (
<div>
<button onClick={() => handleThemeToggle()}>Switch Theme</button>
</div>
);
}
We're grabbing the setTheme
method from ThemeContext
. We're rendering a <button>
that onClick
calls handleThemeToggle()
which will check the current theme. If it's currenly it "light" we set the Theme to be "dark" and visa versa.
However... this won't do anything until we import it and set-up some styles. In our App.tsx
:
// App.tsx
// Other imports...
import { ThemeContext } from './context/ThemeContext';
import ContextExample from './components/ContextExample';
function App() {
const { theme } = use(ThemeContext);
return (
<div className={`App ${theme}`}>
<ContextExample />
</div>
)
}
export default App;
We're importing and passing the theme
from ThemeCotnext
as a class to our App.tsx
's wrapping <div >
. Now we can go into our App.css
file and add some styles to see if our Theme switching works!:
/* App.css */
.dark {
background-color: black;
color: white;
}
.light {
background-color: white;
color: black;
}
It ain't much... but it'll show us if our use of use
and React Context is up to scratch. Go on! Give that button a toggle.
Will you look at that?! Ugly, but functional. That concludes using "use" with React Context. Carry on reading if you want to see if in action with a Promise!
Using "use" with an Async Promise
First of all, we will create a server
action, for more information about server actions and how to use them with AWS check out this video.
Create a new file called lib.ts
:
// lib.ts
'use server';
export const fetchMessage = async () => {
await new Promise((resolve) => setTimeout(resolve, 2500));
return 'Secret Message Recieved!';
};
We're "faking" an API request here, in a real app this would be a call to a database or a fetch request to some API to grab some external data. But by using an await
, Promise()
and setTimeout
we can make our Promise
last 2.5 seconds in this example.
We need to create a couple of components to get this working, a wrapper that'll use Suspense
then inside that component and another component that'll use use
to grab the result of our fetchMessage()
Promise we just created in lib.ts
.
Create these two new files Components/MessageContainer.tsx
& Components/PromiseExample.tsx
:
MessageContaienr.tsx
:
// MessageContainer.tsx
import { Suspense } from 'react';
import PromiseExample from './PromiseExample';
export default function MessageContainer({
messagePromise,
}: {
messagePromise: Promise<string>;
}) {
return (
<Suspense fallback={<p>Waiting for the message...</p>}>
<PromiseExample messagePromise={messagePromise} />
</Suspense>
);
}
The <p>
inside the fallback is what will display while we wait for our Promise to resolve.
Now for the PromiseExample.tsx file
:
// PromiseExample
'use client';
import { use } from 'react';
export default function PromiseExample({
messagePromise,
}: {
messagePromise: Promise<string>;
}) {
const messageConent = use(messagePromise);
return <p>Here's the message: {messageConent}</p>;
}
Here's where the magic happens, we're using the use
component to get the resolved promise value, the string "Secret Message Recieved" we return in our fetchMessage()
Promise. How easy is that? use()
and <Suspense>
sure make working with Asynchronous data a breeze...
Let's hook up our MessageContainer
to the App.tsx
so we can see it in action!
App.tsx
:
// App.tsx
// Other imports...
import { fetchMessage } from './lib';
import MessageContainer from './components/MessageContainer';
function App() {
const { theme } = use(ThemeContext);
const [messagePromise, setMessagePromise] = useState<Promise<string> | null>(
null
);
const handleFetchMessage = () => {
setMessagePromise(fetchMessage());
};
return (
<div className={`App ${theme}`}>
<ContextExample />
<button onClick={() => handleFetchMessage()}>Send Message</button>
{messagePromise && <MessageContainer messagePromise={messagePromise} />}
</div>
)
}
export default App;
We create a new state object to store the messagePromise
, defaulting it to null
. This is so we can hide the message if we haven't requested it yet.
The handleFetchMessage()
function simply sets the messagePromise
state to contain the message Promise from our fetchMessage
server action. We created a <button>
to call handleFetchMessage. Finally, we conditionally show <MessageCotnainer />
on our FE depending on the state of messagePromise
, this means it will only show our <MessagePromiseContainer />
component once the fetchMessage()
promise has been initiated.
So after hitting this button: <button onClick={() => handleFetchMessage()}>Send Message</button>
. We call our fetchMessage()
server action, the fallback in the Suspense
is triggered while use()
waits for the Promise to be resolved.
Once the Promise has been resolved, we use the data we extracted from the Promise using the use
hook and display that to our FE:
Phew... that was a lot, I hope it makes sense! The key part to remember while working with use
and Promises is to wrap the Component you're using use
within a Suspense
and after that, it's as simple as calling use(promiseIWantTheValueOf)
<-- Awful variable name right?
Conclusion
Use, unlike React Hooks, can be used inside an if
or for
statement. However, use
can Only be used inside a Component or Hook. I can see this being handy when you have different privileges in your application. You may have one API req for Admins and another for normal users. Before you couldn't call React hooks conditonally, however use
is breaking the mold here.
So you can do something like:
if (isAdmin) {
use(getAllUsers)
} else {
use(getUsersInMyAccount)
}
^ Please comment below if you can think of better examples of this new superpower of being able to conditionally call the hook.
React sure is making working with servers and asynchronous code easier, with a focus towards SSR
and working with forms, I'm all for it.
If you read this far I'm very impressed! I didn't think it would take this much explaining but I think it's important to see where/how this hook is going to be useful.
Thank you all and if you want to support me, please give my YouTube Channel a look as I look to go more in-depth into everything React & Web Dev in general!
Top comments (1)
The GitHub link if you want to see the whole project of using use in action!