ContextAPI is one of the most exciting features of ReactJS which was released in early 2019 that helps us implement the state management in our React apps without importing extra libraries, like Redux or MobX.
UserData type is not so important for this tutorial, and that’s why we’ll define some key properties which might be different based on your specific needs:
type UserData = {
userId: string;
name: string;
nickName: string
email: string;
}
Then we need to think about what we need your context to do. For this specific example I’ll use some basic functionalities without complex logic.
interface ContextProps {
readonly userData: UserData | null;
readonly setUserData: (userData: UserData) => void;
readonly loadUserData: () => Promise<void>;
}
const UserContext = React.createContext<ContextProps>({
userData: null,
setUserData: () => null,
loadUserData: async () => {},
});
The only thing you really need to to in order to create a context is to use React.createContext(). In our case we’re giving a type to the context, which is ContextProps and a default value as an argument.
Some devs prefer to define the context without initial value, but that way you might have to check if userContext.loadUserDate is defined before calling the function.
const UserProvider: React.FC = ({ children }) => {
const [userData, setUserData]
= React.useState<UserData | null>(null);
const loadUserData = async () => {
console.log('load')
};
const value = {
userData,
setUserData,
loadUserData,
};
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
};
The most important part from this file related to ContextAPI as a functionality is the return statement. This is the actual context provider (UserContext.Provider) that will create the “scope” for this context and make it available in all of it’s children at any level.
Let’s say, we want our context to load automatically the user data without needing to call the userContext.loadUserData() from some of the children. In order to achieve this, we’ll write an useEffect that will make our initial load.
useEffect(() => {
loadUserData();
}, []);
And now let’s update our loadUserData function and implement a sample API call to a non existing API, for the sake of example.
const loadUserData = async () => {
try {
const response: UserData | null = await fetch('https://my-website/api/user');
await response.json();
if (response) {
setUserData(response);
}
} catch (err) {
console.log(err);
}
};
And now we’re all set. Now let’s see how to access our context.
First of all, in order to have access to a context from a component, this component needs to be a child (at any level) of the Context.Provider. So, to demonstrate this we’ll create 2 components:
App.tsx
export default function App() {
return (
<UserProvider>
<UserInfo />
</UserProvider>
);
}
UserInfo.tsx
const UserInfo: React.FC = () => {
const { userData } = React.useContext(UserContext);
if (!userData) return null;
return (
<div>
<p>{userData.userId}</p>
<p>{userData.name}</p>
<p>{userData.nickName}</p>
<p>{userData.email}</p>
</div>
);
};
export default UserInfo;
If you need to use the context in a class component you’ll have to use contextType static property
class UserInfo extends React.Component {
static contextType = UserContext;
render() {
const { userData } = this.context;
if (!userData) return null;
return (
<div>
<p>{userData.userId}</p>
<p>{userData.name}</p>
<p>{userData.nickName}</p>
<p>{userData.email}</p>
</div>
);
}
}
export default UserInfo;
In conclusion, React’s Context API is a handy tool that helps to create global (and not only) states without using Redux or other similar libraries.
Also published here.