Implementing Server-Side Rendering with Context in Next.js 14
Unanswered
Australian Freshwater Crocodile posted this in #help-forum
Australian Freshwater CrocodileOP
I'm working on a Next 14 App directory. I need to implement SSR for my home, but I'm facing challenges with managing state using Context API. I have a FilterContext to pass props to a SearchBar component, but I'm unsure how to make this work with SSR.
Here's I've set up my context:
I use this context in my SearchBar component, thats is child of Navbar:
And my homepage, which needs to be server-rendered, fetches data and maps it to a Card component:
I created a separate provider:
And I wrapped the Root Layout component with it:
```
import { Providers } ...
<body className={inter.className}>
<div>
<NavBar />
<Providers>{children}</Providers>
</div>
</body>
How do I consume this context to filter the Cards without turning my Home in a 'client side' component?
Here's I've set up my context:
// FilterContext.js
imports...
export const FilterContext = createContext({
filter: '',
setFilter: () => {}
});
export const FilterProvider = ({ children }) => {
const [filter, setFilter] = useState('');
return (
<FilterContext.Provider value={{ filter, setFilter }}>
{children}
</FilterContext.Provider>
);
}; I use this context in my SearchBar component, thats is child of Navbar:
And my homepage, which needs to be server-rendered, fetches data and maps it to a Card component:
// Home.js
// ...imports
const Home = async () => {
const response = await fetch('myURL');
const data = await response.json();
return (
<div>
{data.map(ad => <Card key={ad.id} data={ad} />)}
// ...other JSX
</div>
);
};
export default Home;I created a separate provider:
'use client'
import { FilterProvider } from '@/context/FilterContext'
export const Providers = ({ children }: { children: React.ReactNode }) => {
return (
<>
<FilterProvider>{children}</FilterProvider>
</>
)
} And I wrapped the Root Layout component with it:
```
import { Providers } ...
<body className={inter.className}>
<div>
<NavBar />
<Providers>{children}</Providers>
</div>
</body>
How do I consume this context to filter the Cards without turning my Home in a 'client side' component?
26 Replies
@Australian Freshwater Crocodile I'm working on a Next 14 App directory. I need to implement SSR for my home, but I'm facing challenges with managing state using Context API. I have a FilterContext to pass props to a SearchBar component, but I'm unsure how to make this work with SSR.
Here's I've set up my context:
// FilterContext.js
imports...
export const FilterContext = createContext({
filter: '',
setFilter: () => {}
});
export const FilterProvider = ({ children }) => {
const [filter, setFilter] = useState('');
return (
<FilterContext.Provider value={{ filter, setFilter }}>
{children}
</FilterContext.Provider>
);
};
I use this context in my SearchBar component, thats is child of Navbar:
And my homepage, which needs to be server-rendered, fetches data and maps it to a Card component:
// Home.js
// ...imports
const Home = async () => {
const response = await fetch('myURL');
const data = await response.json();
return (
<div>
{data.map(ad => <Card key={ad.id} data={ad} />)}
// ...other JSX
</div>
);
};
export default Home;
I created a separate provider:
'use client'
import { FilterProvider } from '@/context/FilterContext'
export const Providers = ({ children }: { children: React.ReactNode }) => {
return (
<>
<FilterProvider>{children}</FilterProvider>
</>
)
}
And I wrapped the Root Layout component with it:
import { Providers } ...
<body className={inter.className}>
<div>
<NavBar />
<Providers>{children}</Providers>
</div>
</body>
How do I consume this context to filter the Cards without turning my Home in a 'client side' component?
I would set the filter to url query string so there is no need to use context and in the home page you can get the searchParams like this
// Home.js
// ...imports
const Home = async ({searchParams}: {searchParams: {filter: string}}) => {
const response = await fetch('myURL?' + new URLSearchParams(searchParams).toString());
const data = await response.json();
return (
<div>
{data.map(ad => <Card key={ad.id} data={ad} />)}
// ...other JSX
</div>
);
};
export default Home;also, you can set the filter with form
the action is only needed if the form is not on the same page
<form action='/'><input name='filter' /></form>the action is only needed if the form is not on the same page
Australian Freshwater CrocodileOP
Thanks for your answer! But I think there's no way. I will have to use useContext because I need to filter the array of cards
@Australian Freshwater Crocodile Thanks for your answer! But I think there's no way. I will have to use useContext because I need to filter the array of cards
you can use client component and push to that page with query params
Australian Freshwater CrocodileOP
Can I use the method filter to filter the array as I'm typing?
@Australian Freshwater Crocodile Can I use the method filter to filter the array as I'm typing?
did you mean set the filter as you type in a input?
Australian Freshwater CrocodileOP
I just need to filter the array as I type in the input. I achieved this in react by using the context and adding the logic on the Home page. Which uses the filter method.
I'm migrating the react app to next. So, I thought there might be a way in next, not to turn this into a client side page
I'm migrating the react app to next. So, I thought there might be a way in next, not to turn this into a client side page
@Australian Freshwater Crocodile I just need to filter the array as I type in the input. I achieved this in react by using the context and adding the logic on the Home page. Which uses the filter method.
I'm migrating the react app to next. So, I thought there might be a way in next, not to turn this into a client side page
the next way is to use form, but if you need more complex filtering, it will need client component
the page can be a server component and render the client component as children
and pass the data to it
Australian Freshwater CrocodileOP
I have used 'use client' in some components that are children of Card, but for this approach of using the filter method to filter the array of cards in the Home, I will have to use the context
Australian Freshwater CrocodileOP
I have my filterContext file
import React, { useState, createContext } from 'react'
export const FilterContext = createContext(
{} as {
filter: string
setFilter: React.Dispatch<React.SetStateAction<string>>
}
)
export const FilterProvider = ({ children }: { children: React.ReactNode }) => {
const [filter, setFilter] = useState('')
return (
<FilterContext.Provider value={{ filter, setFilter }}>
{children}
</FilterContext.Provider>
)
} My Providers file: 'use client'
import { FilterProvider } from '@/context/FilterContext'
export const Providers = ({ children }: { children: React.ReactNode }) => {
return (
<>
<FilterProvider>{children}</FilterProvider>
</>
)
} and wrapped my Root Layout <body className={inter.className}>
<section className='flex flex-col relative'>
<div>
<NavBar />
<Providers>{children}</Providers>
</div>
</section>
</body> I have no idea what you're talking about, sorryoh ok, I mean you could render it here
const Home = async ({searchParams}: {searchParams: {filter: string}}) => {
const response = await fetch('myURL?' + new URLSearchParams(searchParams).toString());
const data = await response.json();
return (
<Provider>
{data.map(ad => <Card key={ad.id} data={ad} />)}
// ...other JSX
</Provider>
);
};
export default Home;but layout.tsx is fine too
then you should be fine, just keep the page as server component and receive the searchParams object to fetch the data
after you filtering the query, do
router.push("/?filter=filter")Australian Freshwater CrocodileOP
Does that make sense to you? Layout:
<body className={inter.className}>
<section className='flex flex-col relative'>
<div>
<Providers>
<NavBar />
{children}
</Providers>
</div>
</section>
</body> SearchBar child of NavBar: 'use client'
import { useRouter } from 'next/router'
import React, { useState, useEffect } from 'react'
const SearchBar = () => {
const [searchTerm, setSearchTerm] = useState('')
const router = useRouter()
useEffect(() => {
if (!router.isReady) return
}, [router.isReady])
const handleSearchChange = (e) => {
setSearchTerm(e.target.value)
}
const handleSearchSubmit = (e) => {
e.preventDefault()
if (router.isReady) {
router.push(`/?filter=${searchTerm}`)
}
}
return (
<div>
<form onSubmit={handleSearchSubmit}>
<input
className='bg-input-bg rounded-lg w-96 text-copy-secondary text-base px-4 py-2'
value={searchTerm}
onChange={handleSearchChange}
/>
<button type='submit'>Search</button>
</form>
</div>
)
}
export default SearchBar Home: import Card from './components/front/Card'
import { DataItem } from '@/types/ads'
const Home = async ({ searchParams }: { searchParams: { filter: string } }) => {
const queryParams = new URLSearchParams(searchParams).toString()
const url = URL?${queryParams}`
const response = await fetch(url, {
method: 'GET',
headers: {
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string,
},
})
const data = await response.json()
return (
<div className='container'>
<div className='grid xl:grid-cols-3 xl:gap-5 xl:gap-y-12'>
{data.map((item: DataItem) => (
<Card key={item.id} data={item} />
))}
</div>
</div>
)
}
export default Home I'm getting this error: Error: NextRouter was not mounted.@Australian Freshwater Crocodile Does that make sense to you? Layout:
<body className={inter.className}>
<section className='flex flex-col relative'>
<div>
<Providers>
<NavBar />
{children}
</Providers>
</div>
</section>
</body>
SearchBar child of NavBar:
'use client'
import { useRouter } from 'next/router'
import React, { useState, useEffect } from 'react'
const SearchBar = () => {
const [searchTerm, setSearchTerm] = useState('')
const router = useRouter()
useEffect(() => {
if (!router.isReady) return
}, [router.isReady])
const handleSearchChange = (e) => {
setSearchTerm(e.target.value)
}
const handleSearchSubmit = (e) => {
e.preventDefault()
if (router.isReady) {
router.push(`/?filter=${searchTerm}`)
}
}
return (
<div>
<form onSubmit={handleSearchSubmit}>
<input
className='bg-input-bg rounded-lg w-96 text-copy-secondary text-base px-4 py-2'
value={searchTerm}
onChange={handleSearchChange}
/>
<button type='submit'>Search</button>
</form>
</div>
)
}
export default SearchBar Home: import Card from './components/front/Card'
import { DataItem } from '@/types/ads'
const Home = async ({ searchParams }: { searchParams: { filter: string } }) => {
const queryParams = new URLSearchParams(searchParams).toString()
const url = URL?${queryParams}`
const response = await fetch(url, {
method: 'GET',
headers: {
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string,
},
})
const data = await response.json()
return (
<div className='container'>
<div className='grid xl:grid-cols-3 xl:gap-5 xl:gap-y-12'>
{data.map((item: DataItem) => (
<Card key={item.id} data={item} />
))}
</div>
</div>
)
}
export default Home I'm getting this error:
Error: NextRouter was not mounted.
import { useRouter } from 'next/navigation';
const SearchBar = () => {
const [searchTerm, setSearchTerm] = useState('')
const router = useRouter()
const handleSearchChange = (e) => {
setSearchTerm(e.target.value)
}
const handleSearchSubmit = (e) => {
router.push(`/?filter=${searchTerm}`)
}Australian Freshwater CrocodileOP
Thanks @Ray !!
@Australian Freshwater Crocodile Thanks <@743561772069421169> !!
np, does it work for you?
Australian Freshwater CrocodileOP
I'll try it out. I'll post here if it works
Australian Freshwater CrocodileOP
More errors, I'll leave to use Next in my next project. Thank you very much for your attention!
no prob'