Cannot update a component while rendering a different component
Answered
Turkish Angora posted this in #help-forum
Turkish AngoraOP
I think I am not understanding the interaction between client and server components, since the following client component yields an error:
The error indicated on the
My
If I remove the server component, no error.
If I remove the call to
For the life of me I cannot see where I am updating the state at the wrong time, during a render. This seems like perfectly legitimate react code to me, and so I believe there is something about the server component interaction.
No amount of AI has been able to help me 🤣
Can any of you humans?
Cannot update a component (Router
) while rendering a different component (default
). To locate the bad setState() call insidedefault
, follow the stack trace as described in https://react.dev/link/setstate-in-render
The error indicated on the
useSearchParams()
, but I believe it really just points at the beginning of the component.My
<SearchResults />
is server component which literally just spits out static HTML right now, doing nothing else, just while debugging.If I remove the server component, no error.
If I remove the call to
setQuery()
, no error.For the life of me I cannot see where I am updating the state at the wrong time, during a render. This seems like perfectly legitimate react code to me, and so I believe there is something about the server component interaction.
No amount of AI has been able to help me 🤣
Can any of you humans?
const Page = () => {
const searchParams = useSearchParams();
const router = useRouter();
const [inputValue, setInputValue] = useState('');
const [query, setQuery] = useState('');
// Initialize state based on query parameter
useEffect(() => {
const initialQuery = searchParams.get('q') || '';
if (initialQuery !== query) {
setInputValue(initialQuery);
setQuery(initialQuery);
}
}, [searchParams]);
const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
};
const handleSearch = (e: FormEvent<HTMLFormElement>) => {
};
return (
<Centred className="flex flex-col gutterless:pt-12 transition-all">
<div className="flex flex-col transition-all">
<Card className="card w-full overflow-hidden gutterless:rad-shadow3 max-gutterless:py-6 gutterless:mb-6 transition-all">
<CardHeader className="text-[var(--text1)]">
<CardTitle className="text-search">Search Kadampa teachings</CardTitle>
</CardHeader>
<CardContent className="text-[var(--text1)] w-full">
<SearchForm onSubmit={handleSearch} value={inputValue} onChange={handleOnChange} />
</CardContent>
</Card>
{query && (
<Suspense fallback={<Busy />}>
<SearchResults query={query} />
</Suspense>
)}
</div>
</Centred>
);
};
Answered by Asian black bear
I would suggest taking the
searchParams
from the Page Props, not via useSearchParams
, and move all the setInputValue
logic to the actual SearchForm
component, which is then a client component. That way, this Page
component stays server, and you can await your Results component55 Replies
Turkish AngoraOP
Hmm. The docs say you can. The example app that I built during the tutorial does so.
It renders fine, but the console has this error in it, which is disconcerting
@Turkish Angora Hmm. The docs say you can. The example app that I built during the tutorial does so.
The example app that I built during the tutorial does so.could you show how you use a server component in a client component in this case?
Turkish AngoraOP
Just looking, I think you may be right. I don't know why I thought that. Perhaps because the pages are server components.
That is a deal-breaker, almost the whole point for me. I don't know enough about the architecture, but I would have imagined they could have somehow allowed server components to be rendered by client components.
So a server component can't really take props from the state of a client component?
@Turkish Angora Just looking, I think you may be right. I don't know why I thought that. Perhaps because the pages are server components.
client components can rerender, server components cannot. so you cannot have server components inside client components.
but your client component can have a
but your client component can have a
children
prop, then you can do this, just fine:<ClientComponent>
<ServerComponent />
</ClientComponent>
@Turkish Angora So a server component can't really take props from the state of a client component?
yes. server components cannot simply take props, states or any runtime values from client components
but you can do this: https://nextjs-faq.com/sharing-client-side-state-with-server-components
Turkish AngoraOP
Blimey
Asian black bear
Or, the server component will become a client component, just without you realizing it / marking it with "use client". But the error here seems to have something to do with react states. I'm not sure what the
<Suspense>
is doing here within a client component?Turkish AngoraOP
Because <SearchResults /> is an awaitable server component. As I say, this is all working, just some messages in the console.
But I will change to the pattern, thanks @joulev
Asian black bear
Ah yes if that's
async
then it wouldn't work.Asian black bear
I would suggest taking the
searchParams
from the Page Props, not via useSearchParams
, and move all the setInputValue
logic to the actual SearchForm
component, which is then a client component. That way, this Page
component stays server, and you can await your Results componentAnswer
@joulev but you can do this: <https://nextjs-faq.com/sharing-client-side-state-with-server-components>
I just checked your code and it seems this one here is exactly what you should be doing. Same thing – storing search query and search for things server side
Turkish AngoraOP
Hmm, actually not seeing how that works. Where am I putting the SearchResults component? I cannot have it within a client component at all, even with no props?
It has to be a page?
I believe you alluded to this @joulev
Going to experiment with that pattern
Yes… but also what Kevin said above is correct. Only the form should be a client component, the search result and the entire page should remain a server component
Asian black bear
It has to be a server component. You can wrap
The simplest path here is to move literally all the states or handlers to your form component (client component).
Then get the query from the Page props.
So your paradigm looks like this, simplistically
SearchResults
with client components if you want, but you have to have it defined on a server component.The simplest path here is to move literally all the states or handlers to your form component (client component).
Then get the query from the Page props.
So your paradigm looks like this, simplistically
<Page> // server. Take the `searchParams.q` from the page props
<SearchForm /> // client, include all routing / input state logic
<Suspense>
<SearchResults query={query} /> // server, async
</Suspense>
</Page>
Turkish AngoraOP
Yes, I understood that. Thanks also @Asian black bear
Ok, I’ll give that a go. Presently installing new lights.
Although now the prop has re-appeared on the search results…?
query here is from searchParams.q and not from a react state
The react state is inside <SearchForm /> which stays there and cannot get to <SearchResults />. The prop here is from searchParams data directly available to the page server component
Turkish AngoraOP
Righto. Need to rework the architecture I had in my brain, got totally the wrong end of the stick with this. Thanks both 🙏
Turkish AngoraOP
Hello @joulev @Asian black bear
So I reworked it. Got it working fine.
A strange thing though
My search results are appearing almost instantaneously
Not a complaint
🤣
Just a weirdness
Turkish AngoraOP
I think the pattern of using a server component as a prop to a client component is much more flexible in terms passing props to the server component, is it not?
Otherwise, everything has to go into the URL
I am using MobX on the client side, and I have discovered that I can pass anything to the server component really through its props
const SearchResults = async ({ q }: { q: string }) => {
const searchResults = await fetchSearchResult(q);
// ...
}
No need to rely on the URL
@Turkish Angora I think the pattern of using a server component as a prop to a client component is much more flexible in terms passing props to the server component, is it not?
you cannot pass any props from client components to server components. just try. you can't.
Turkish AngoraOP
Well I have 🤣
@Turkish Angora Well I have 🤣
can you show the code?
Turkish AngoraOP
export default async function Page(props: { searchParams: SearchParams }) {
const searchParams = await props.searchParams;
const q = searchParams ? searchParams['q'] : undefined;
const page = searchParams && searchParams['page'] ? +searchParams['page'] : 0;
return (
<Centred className="flex flex-col gutterless:pt-12 transition-all">
<SearchCard />
{typeof q === 'string' && (
<Suspense fallback={<Busy />}>
<SearchResults q={q} page={page} />
</Suspense>
)}
</Centred>
);
};
const SearchResults = async (props: { q: string, page: number }) => {
const searchResults = await fetchSearchResult(props);
return (
<>
<ul>
{searchResults?.map((result, i) => (
<li
key={i}
className={`px-3 gutterless:px-5 py-4 transition-all ${i % 2 === 0 ? 'surface2' : 'surface3'}`}
>
<div className={'flex flex-row gap-3 items-start'}>
<img
src={`data:;base64,${result?.cover_image_base64}`}
alt={result?.title || ''}
className="object-contain w-16"
/>
<div className={'overflow-hidden text-ellipsis flex flex-col gap-1 w-full'}>
<div className={'flex justify-between'}>
<div className={'leading-5'}>
<div className={'text-search'}>{result?.title}</div>
<div className={'text2'}>{result.section}</div>
</div>
<div>
<div
className={'text-xs text-search-secondary border-search-secondary border px-1 inline-block w-fit rounded opacity-70'}
>
{result.score?.toFixed(scoreDp)}
</div>
</div>
</div>
<div className={'flex gap-2'}>
<Link
className={'a overflow-hidden line-clamp-2 text1'}
href={result?.href ?? ''}
target="_blank"
>
{result.text}
</Link>
</div>
</div>
</div>
</li>
)
)}
</ul>
</>
);
};
@Turkish Angora export default async function Page(props: { searchParams: SearchParams }) {
const searchParams = await props.searchParams;
const q = searchParams ? searchParams['q'] : undefined;
const page = searchParams && searchParams['page'] ? +searchParams['page'] : 0;
return (
<Centred className="flex flex-col gutterless:pt-12 transition-all">
<SearchCard />
{typeof q === 'string' && (
<Suspense fallback={<Busy />}>
<SearchResults q={q} page={page} />
</Suspense>
)}
</Centred>
);
};
this is NOT a client component.
Page
is a server componentTurkish AngoraOP
Oh I see
I need to dig into the source to see how this stuff works. No idea what is going on really.
So if I use that folded pattern of a child server component as prop, still I can't pass props?
you are just having lots of misunderstanding. reread the documentation.
Turkish AngoraOP
You're right, I have been impatient. I wanted to upgrade my app from 18 to 19, and encountered next, and rushed to get it done
I might have to abandon this idea then, because I need to pass lots of props in my current implementation and I ain't putting them all in the URL