Next.js Discord

Discord Forum

Implementing Server-Side Rendering with Context in Next.js 14

Unanswered
Australian Freshwater Crocodile posted this in #help-forum
Open in Discord
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:

// 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
<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 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
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, sorry
oh 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 instead of next/router
@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'