React useEffect() hook highly affects SEO - javascript

I have a static website made with react that requests data from the backend in the useEffect() hook:
export default const App = () => {
const [data, setData] = useState("");
useEffect(() => {
server.get().then(data => {
setData(data)
})
})
return(
<title>{data}</title>
<h1>{data}</h1>
)
}
However, when Bing crawls the webpage, the following problem occurs:
Bing Screenshot:
<title></title>
<h1></h1>
How can I solve this issue?

React isn't used for static sites. If you'd like to have better SEO and server-side rendering you can use nextjs.
The way your app is setup currently will only return some HTML with and empty body to a GET request to / (which is what I suppose crawlers like the one you mentioned use) and starts rendering components after the JavaScript is loaded.
But if you decide on a server-side rendering approach, whenever a request is made to your app the server will first render the app on it's side and the return an HTML string with the rendered components.

Did you check if your server.get() is returning some data? I can't see any url here, so maybe it's actually returning nothing.
Even so, maybe you forgot to pass the second argument of useEffect, which is an array of arguments, which this hooks uses to trigger itself. For example, if you want to trigger only once, when component is mounted, you need to pass [] as second argument of useEffect.

Related

calling setState only once inside of useEffect--is there a better method?

In my react app I use the following pattern quite a bit:
export default function Profile() {
const [username, setUsername] = React.useState<string | null>(null);
React.useEffect(()=>{
fetch(`/api/userprofiles?username=myuser`)
.then(res=>res.json())
.then(data => setUsername(data.username))
},[])
return(
<div>
{username}'s profile
</div>
)
}
When the page loads, some user data is fetched from the server, and then the page updates with that user data.
One thing I notice is that I only really need to call setUsername() once on load, which makes using state seem kinda excessive. I can't shake the feeling that there must be a better way to do this in react, but I couldn't really find an alternative when googling. Is there a more efficient way to do this without using state? Or is this the generally agreed upon way to load data when it only needs to be done once on page load
Without using any external libraries, no - that is the way to do it.
It would be possible to remove the state in Profile and have it render the username from a prop, but that would require adding the state into the parent component and making the asynchronous request there. State will be needed somewhere in the app pertaining to this data.
The logic can be abstracted behind a custom hook. For example, one library has useFetch where you could do
export default function Profile() {
const { data, error } = useFetch('/api/userprofiles?username=myuser');
// you can check for errors if desired...
return(
<div>
{data.username}'s profile
</div>
)
}
Now the state is inside useFetch instead of in your components, but it's still there.

Handling UI State in Remix.run

I am trying out remix and ohh boy.. I am stuck on creating a simple counter (clicking button increase count)
I guess I am not supposed to use the useState hook so I tried my luck with loader and action as I believe that this is where it should be handled in Remix
What I have on my component is:
export default function Game() {
const counter = useLoaderData();
return (
<>
<div>{counter}</div>
<div>
<Form method="post">
<button type="submit">click</button>
</Form>
</div>
</>
);
}
Server:
import { ActionFunction, LoaderFunction } from 'remix';
let counter: number = 0;
export const loader: LoaderFunction = async () => {
console.log('game object!', counter);
return counter;
};
export let action: ActionFunction = async ({ request, params }) => {
console.log('[action] game object!', ++counter);
return counter;
};
The code above will have counter always resetting to 0 on every click
Looked around some repositories and what I can find are those storing in Cookie/DB/SessionStorage etc, however what if I just want a simple state for my UI?
You are supposed to use useState for your client-side state in Remix.
If you clone the remix repository and do a grep -r useState in the remix/examples/ folder, you will find many occurrences of it.
For example you have a simple one in the Route modal example (in app/routes/invoices/$id/edit.tsx), being used for a form with controlled inputs.
What Remix does is make it easier to communicate between the client and the server by colocating their codes for the same functionnality, and providing simple ways to perform that communication. This is useful if you need to communicate the data to your server. If it's not the case, then it's totally ok to keep that information only on the client.
About server side rendering
Remix also server-side renders the components by default. Which means that it executes your React code on the server to generate the HTML, and sends that with the JavaScript code. That way the browser can display it even before it executes the JavaScript to run the React code on the browser.
This means that in case your code (or a third party library code you use) uses some browser API in you component, you may need to indicate not to server-side render that component.
There is a Client only components example that demonstrates how to do that. It includes an example with a counter storing its value in the local storage of the browser.

how to emulate messages/events with react useState and useContext?

I'm creating a react app with useState and useContext for state management. So far this worked like a charm, but now I've come across a feature that needs something like an event:
Let's say there is a ContentPage which renders a lot of content pieces. The user can scroll through this and read the content.
And there's also a BookmarkPage. Clicking on a bookmark opens the ContentPage and scrolls to the corresponding piece of content.
This scrolling to content is a one-time action. Ideally, I would like to have an event listener in my ContentPage that consumes ScrollTo(item) events. But react pretty much prevents all use of events. DOM events can't be caught in the virtual dom and it's not possible to create custom synthetic events.
Also, the command "open up content piece XYZ" can come from many parts in the component tree (the example doesn't completely fit what I'm trying to implement). An event that just bubbles up the tree wouldn't solve the problem.
So I guess the react way is to somehow represent this event with the app state?
I have a workaround solution but it's hacky and has a problem (which is why I'm posting this question):
export interface MessageQueue{
messages: number[],
push:(num: number)=>void,
pop:()=>number
}
const defaultMessageQueue{
messages:[],
push: (num:number) => {throw new Error("don't use default");},
pop: () => {throw new Error("don't use default");}
}
export const MessageQueueContext = React.createContext<MessageQueue>(defaultMessageQueue);
In the component I'm providing this with:
const [messages, setmessages] = useState<number[]>([]);
//...
<MessageQueueContext.Provider value={{
messages: messages,
push:(num:number)=>{
setmessages([...messages, num]);
},
pop:()=>{
if(messages.length==0)return;
const message = messages[-1];
setmessages([...messages.slice(0, -1)]);
return message;
}
}}>
Now any component that needs to send or receive messages can use the Context.
Pushing a message works as expected. The Context changes and all components that use it re-render.
But popping a message also changes the context and also causes a re-render. This second re-render is wasted since there is no reason to do it.
Is there a clean way to implement actions/messages/events in a codebase that does state management with useState and useContext?
Since you're using routing in Ionic's router (React-Router), and you navigate between two pages, you can use the URL to pass params to the page:
Define the route to have an optional path param. Something like content-page/:section?
In the ContentPage, get the param (section) using React Router's useParams. Create a useEffect with section as the only changing dependency only. On first render (or if section changes) the scroll code would be called.
const { section } = useParams();
useEffect(() => {
// the code to jump to the section
}, [section]);
I am not sure why can't you use document.dispatchEvent(new CustomEvent()) with an associated eventListener.
Also if it's a matter of scrolling you can scrollIntoView using refs

Next.js Fetching Data Inside Nested Child Component

Please consider my circumstance: In Next.js, I have built a component that is intended to be a child component that fetches data on its own (without any parent component) and now I have come to find this is not allowed by the authors of next.js. However, they mention the async-reactor library as a workaround:
May be you can try something like async-reactor
But I tried using async-reactor and was unable to render a fetch inside a nested child component in Next.js still. Here's what I tried:
// my child component
import React from 'react';
import {asyncReactor} from 'async-reactor';
import fetch from 'isomorphic-unfetch';
function Loader() {
return (
<div>
<h2>Loading ...</h2>
</div>
);
}
async function AsyncPosts() {
const data = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await data.json();
return (
<div>
<ul>
{posts.map((x) => <li key={x.id}>{x.title}</li>)}
</ul>
</div>
);
}
export default asyncReactor(AsyncPosts, Loader);
I expected this to work but it doesn't render anything except the word "Div" (which isn't even supposed to render "Div").
Is there a way to fetch within a child component in Next.js? Nothing I have tried so far worked but I find it hard to believe this is truly not possible.
As #Arunoda wrote:
We don't have plans to add support for calling getInitialProps in nested components.
The emphasis is on getInitialProps, you can make an ajax request inside any component, but know the benefits / drawback of it.
This ajax request will be implemented inside componentDidMount / useEffect hook which are not called at server-side.
One of the benefits can be lazy loading data, you don't need the entire page's data up front, that means less data => smaller network request.
One drawback can be that this section won't be passed to next's SSR mechanism, therefore won't be easily SEOed.

useQuery after server-side rendering

I'm using next.js and apollo with react hooks.
For one page, I am server-side rendering the first X "posts" like so:
// pages/topic.js
const Page = ({ posts }) => <TopicPage posts={posts} />;
Page.getInitialProps = async (context) => {
const { apolloClient } = context;
const posts = await apolloClient.query(whatever);
return { posts };
};
export default Page;
And then in the component I want to use the useQuery hook:
// components/TopicPage.js
import { useQuery } from '#apollo/react-hooks';
export default ({ posts }) => {
const { loading, error, data, fetchMore } = useQuery(whatever);
// go on to render posts and/or data, and i have a button that does fetchMore
};
Note that the useQuery here executes essentially the same query as the one I did server-side to get posts for the topic.
The problem here is that in the component, I already have the first batch of posts passed in from the server, so I don't actually want to fetch that first batch of posts again, but I do still want to support the functionality of a user clicking a button to load more posts.
I considered the option of calling useQuery here so that it starts at the second "page" of posts with its query, but I don't actually want that. I want the topic page to be fully loaded with the posts that I want (i.e. the posts that come from the server) as soon as the page loads.
Is it possible to make useQuery work in this situation? Or do I need to eschew it for some custom logic around manual query calls to the apollo client (from useApolloClient)?
Turns out this was just a misunderstanding on my part of how server side rendering with nextjs works. It does a full render of the React tree before sending the resulting html to the client. Thus, there is no need to do the "first" useQuery call in getInitialProps or anything of the sort. It can just be used in the component alone and everything will work fine as long as getDataFromTree is being utilized properly in the server side configuration.

Categories