I am trying to do very basic stuff with React Context to learn it. I’m not sure if/how this complicates things, but I’m doing this in a NextJS 13 project. (I know server components are introduced here but I’m trying to simplify things by labeling everything with 'use client'
. The goal is just to have a React Context with a simple string in it, and a function that allows you to change that string. Here is the code in question:
Small file to define the TS type associated with my context’s shape:
'use client'
export type UserContextType = {
updateUsername: (username: string) => void;
username: string;
}
My top level layout.tsx
component (the layout from which all other pages are rendered):
'use client'
import './globals.css'
import { Inter } from 'next/font/google'
import { createContext, useState } from 'react'
import { UserContextType } from '@/contexts/UserContext'
const inter = Inter({ subsets: ['latin'] })
export const UserContext = createContext<UserContextType>({
username: '',
updateUsername: (newName: string) => { console.log(`needs implementation`); }
});
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const [username, setUsername] = useState<string>('');
function updateUsername(_username: string) {
setUsername(_username);
}
return (
<html lang="en">
<body className={inter.className}>
<UserContext.Provider value={{username, updateUsername}}>
{children}
</UserContext.Provider>
</body>
</html>
)
}
My "Login" component (so named because I want to expand this to a real project at some point):
'use client'
import React, { ReactNode, useContext, useState } from 'react';
import { UserContext } from '@/app/layout';
const Login = (): JSX.Element => {
const [content, setContent] = useState<string>('');
const userContext = useContext(UserContext);
const username = userContext?.username;
const updateUsername = userContext?.updateUsername;
function click() {
updateUsername(content);
}
return (
<div>
{username && <>Logged in as {username}</>}
<input
onChange={(evt) => setContent(evt.target.value)}
value={content}
/>
<button
onClick={() => click()}
>
Button
</button>
</div>
);
};
export default Login;
I can read the initial values of the Context from createContext...
but not the values passed to the Provider. In other words, in the Login
component I can see whatever value I put into the initial value of username
and I can run the initial version of updateUsername
, in other words I see "needs implementation" in the console. But the values I pass to the provider are ignored.
What am I doing wrong and how does one use React Context to get the provider to pass on correct values?
2
Answers
layout.tsx
is not in the component tree with NextJS page route components. For React Context to work, the Provider must be an ancestor of consumers (usinguseContext
). DespiteRootLayout
’s name and the fact it appears in the default NextJS app along with the default/index page, these red herrings do not mean that it is in the tree as an ancestor of routed NextJS pages. In fact, it isn’t, and that’s why the Provider isn’t actually providing anything to my page.I suggest you to create another function for Provider:
UserContextProvider
.Then, in the Login component, you could do something like this: