ReactJS Functional Component Renders Multiple Times and Setting Img Src With Props - javascript

I am a newbie in ReactJS and I am trying to pass an object as a props to a component. I am getting data from the .NET CORE WEB API and there is no problem about it. Here is my useEffect hook to do that:
useEffect(() => {
const response = axios.get("http://localhost:5122/GetUserInfo", { params: { userId: profileId } });
response.then((res) => {
setUserInfo(res.data);
})
}, [])
And I pass the userInfo state as props, as shown below:
return (
<div>
<ProfileCard user={userInfo} />
</div>
);
To better understand of problem I took a screenshot of console. I am print user prop at the top of ProfileCard component.
Console Screenshot
My problem is:
When I try to use props such as <label>{user.profilePicture}</label> I can see the file name on the screen but when I try to use props such as <img src={require(./File/${user.profilePicture})} /> cannot see the image on the screen and throws an error to console that basically saying cannot find the image. Note:I am sure that given source path is correct and image file exist.
Anybody help me please? Edit: I also added the console error.

Your screenshot shows that profilePicture is a filename à la 36ddd13a-d3b9-47ad-9719-7789892a1384.jpg.
Just doing <img src={profilePicture} /> would have your browser attempt to find the file by relative path, which is likely not to work.
require(...) only works for assets that are available at the build time of your React app and only for static strings due to bundling; an arbitrary filename that comes over the wire from your API won't be that.
If your HTTP server serves those images from under /File, then you'd need to do
<img src={`/File/${profilePicture}`} />
(or, if that HTTP server is not the same origin your React app is served from, but that http://localhost:5122/ from above),
<img src={`http://localhost:5122/File/${profilePicture}`} />
).
If you're using e.g. create-react-app, then you'd need to put the file in the /public/File/ directory.

You can't pass dynamic strings to require like require(./File/${user.profilePicture})
It maybe work sometimes, but only in some edge cases.
What you should do is tell the compiler exactly which files it needs to bundle:
const map = {
'profilePic_1': require('./File/profilePic_1'),
'profilePic_2': require('./File/profilePic_2'),
// and so on...
}
By creating a map you can then "index" the bundled files based on server response.
<img src={map[user.profilePicture]} />
If you have a fairly large number of images I'd recommend dynamically loading them:
const map = {
'profilePic_1': () => import('./File/profilePic_1'),
'profilePic_2': () => import('./File/profilePic_2'),
// and so on...
}
const YourComponent = ({userProfilePicture})
{
// without placeholder
const [url, setUrl] = useState('');
// if you want to load a placeholder from a server
// const [url, setUrl] = useState('http://placeholder_url');
// if you want to bundle the placeholder
// const [url, setUrl] = useState(require('./placeholder_url'));
useEffect(() => {
map[userProfilePicture]().then(setUrl);
}, []);
return <img src={url} alt=''/>;
}

Please can you post the entire component with the useEffect and also the ProfileCard component.
It might be a problem with the useState.
You should have something like
const [userInfo, setUserInfo] = useState<TypeOfGetUserInfo | null>(null)
In you ProfileCard you should have something like
const ProfileCard = React.FC<ProfileCardProps> = ({user}) => {
...
return (
...something
)
}

Related

React server side component - alternative to `response.readRoot()` function

In React server components official GitHub example repo at exactly in this line here they are using response.readRoot().
I want to create a similar app for testing something with RSC's and it seems like the response does not contain the .readRoot() function any more (because they have updated that API in the react package on npm and I cannot find anything about it!). but it returns the tree in value property like below:
This means that whatever I render in my root server component, will not appear in the browser if I render that variable (JSON.parse(value) || not parsed) inside of my app context provider.
How can I render this?
Basically, if you get some response on the client side (in react server components) you have to render that response in the browser which has the new state from server but since I don't have access to readRoot() any more from response, what would be the alternative for it to use?
I used a trick o solve this issue, but one thing to keep in mind is that they are still unstable APIs that react uses and it's still recommended not to use React server component in the production level, uses it for learning and test it and get yourself familiar with it, so back to solution:
My experience was I had a lot of problems with caching layer they are using in their depo app. I just removed it. My suggestion is to not use it for now until those functions and APIs become stable. So I Removed it in my useServerResponse(...) function, which in here I renamed it to getServerResponse(...) because of the hook I created later in order to convert the promise into actual renderable response, so what I did was:
export async function getServerResponse(location) {
const key = JSON.stringify(location);
// const cache = unstable_getCacheForType(createResponseCache);
// let response = cache.get(key);
// if (response) return response;
let response = await createFromFetch(
fetch("/react?location=" + encodeURIComponent(key))
);
// cache.set(key, response);
return response;
}
and then creating a hook that would get the promise from the above function, and return an actual renderable result for me:
export function _useServerResponse(appState) {
const [tree, setTree] = useState(null);
useEffect(() => {
getServerResponse(appState).then((res) => {
setTree(res);
});
}, [appState]);
return { tree };
}
and finally in my AppContextProvider, I used that hook to get the react server component tree and use that rendered tree as child of my global context provider in client-side like below:
import { _useServerResponse } from ".../location/of/your/hook";
export default function AppContextProvider() {
const [appState, setAppState] = useState({
...someAppStateHere
});
const { tree } = _useServerResponse(appState);
return (
<AppContext.Provider value={{ appState, setAppState }}>
{tree}
</AppContext.Provider>
);
}
I know that this is like a workaround hacky solution, but it worked fine in my case, and seems like until we get stable APIs with proper official documentation about RSCs, it's a working solution for me at least!

Does Nextjs generate duplicate pages at build time?

So I haven't found this answer anywhere and I would like to know this so that's why posting here. Suppose I have a blog for which I want to use SSG and ISR. SSG for the homepage and ISR for the individual posts.
So if I generated the homepage at build time with SSG to display 10 posts with the following code:
index.js file:
export const getStaticProps = async () => {
const posts = await get10PostsFromDB();
return {
props: {
posts
}
}
}
And then, if I use this code for each post and also use getStaticPaths to generate those exact 10 posts:
[post].js
export const getStaticPaths = async () => {
const posts = await get10PostsFromDB();
const paths = posts.map( ( item ) => ( {
params: {
slug: item.slug
}
} ) );
return {
paths,
fallback: 'blocking'
};
}
export const getStaticProps = async ({ params }) => {
const post = await getASinglePostFromDB( params.slug );
return {
props: {
post
},
revalidate: 1,
notFound: true
}
}
Will there be duplicate SSG generated pages as 10 are generated at build time for the homepage and then again those 10 pages are generated for getStaticPaths as well? Or what I'm thinking is totally wrong?
I think you have a bit of a misunderstanding of what getStaticPaths and getStaticProps do.
Short answer: no, it will not duplicate the pages.
Let me explain why:
getStaticProps vs getStaticPaths
getStaticProps (Static Generation): Fetch data at build time.
getStaticPaths (Static Generation): Specify dynamic routes to pre-render pages based on data.
What does getStaticProps do?
NextJS has this built-in page pre-rendering but this built-in process has a flaw if you wanna call it like this.
Take this code for example:
const HomePage = () => {
const [loadedPosts, setLoadedPosts] = useState();
useEffect(() => {
setLoadedPosts(DUMMY_POSTS);
}, []);
return (
loadedPosts ? (
<PostList posts={loadedPosts} />
) : ''
)
}
Note: This is a simple demo to convey the concept. In normal cases this issue will occur when you fetch data from an API in the useEffect hook.
Explanation: Let's imagine we are rendering a list of posts. DUMMY_POSTS is our data that comes from the db for example.
What happens here is that the useEffect hook will wait for the component to render before updating out loadedPosts.
When NextJS pre-renders a page it takes the snapshot of the first component rendering cycle as its content and that might be missing crucial data. This means that NextJS does not update the snapshot of our code AFTER the new data has come. This as you know is bad for SEO.
After this HTML page was received on the client, React will actually take over, the page is hydrated as this process is called, which means that now React will turn this into a single page application, take over control, and then this useEffect function will be executed, data might be fetched and the page might be updated - not on the server, not on the pre-rendered page but instead after this page was received in the browser. I.e no SSR.
Here's where getStaticProps() comes to play.
const HomePage = (props) => {
return (
<PostList posts={props.posts} />
)
}
export function getStaticProps() {
// fetch data from an API
return {
props: {
posts: DUMMY_POSTS
},
revalidate: 10
};
};
export default HomePage;
getStaticProps runs at build time and it fetches the data DUMMY_POSTS and then assigns it to props. This is then passed as props of the HomePage component where we can access props.posts.
What is getStaticPaths?
getStaticPaths is a function that you need to export in a page component file that is:
A dynamic page and
Is using getStaticProps
You DO NOT need it in a file that is using getServerProps and DO NOT NEED IT WITHOUT getStaticProps
Here I will use a very simple example to explain, but normally you generate the paths dynamically as you have done in your example:
export async function getStaticPaths() {
return {
fallback: false,
paths: [
{
params: {
postdId: 'p1'
}
},
{
params: {
postId: 'p2'
}
},
]
};
};
In the above example, we have two posts with IDs p1 and p2.
With getStaticProps a page is generated during the build process. This means that Next.js needs to pre-generate all versions for all dynamic pages in advance for all supported dynamic paths. Since it's dynamic Next.js needs to know for which IDs it needs to pre-generate a page. These functions are run during the build process NOT when a client visits the page. I.e. without getStaticPaths these dynamic pages will not be pre-generated on the server which kills the purpose of Next.js.
getStaticPaths tells Next.js what are the exact dynamic paths (in our case which post IDs) it needs to pre-generate. If the user enters an ID for which we did not pre-generate a page they will see a 404 error.
getStaticPaths returns an object where we describe all of the dynamic segment values. It has a paths property, which is an array that takes an object for each version of the dynamic page. This object has a params property, which is also an object that holds all of the key-value pairs that lead to your dynamic page.
TLDR;
getStaticProps fetches the data at build time before the component has loaded so that Next.js can pre-render the page WITH the data that we are fetching.
getStaticPaths tells Next.js about dynamic pages that need to be pre-rendered, because Next.js will not pre-render them on its own as it does not know exactly the dynamic IDs (routes). With this function you are basically telling Next.js how your dynamic routes are called so it knows what to pre-render. Otherwise it will show a 404 page.
Conclussion:
NextJS does not generate duplicate pages at build time.
EDIT:
In regards to pre-generating pages with getStaticPaths...
You DO NOT need to pre-generate every single dynamic route. In order to be able to load the other routes without a 404 error, you can add fallback: true or fallback: 'blocking'
These options can help in cases where you have a lot of pages that need to be pre-generated. In the case we have a website like Amazon with millions of products, pre-rendering all of them might take super long and there may be products that are rarely visited. Therefore, pre-generating such products is a waste of resources.
In such cases, we can set fallback: true and pre-generate some pages, not all.
export async function getStaticPaths() {
return {
fallback: true,
paths: [
{
params: {
prodId: 'p1'
}
}
]
}
}
Even if we don't list all of the pages, this setting tells NextJS that other pages can be valid and should be loaded when they are visited. They are generated just in time. This allows to pre-generate highly visited pages and pre-generate other pages only when they are needed.
However, if the request with the unsupported ID is sent directly (not internally through clicking on a product), it will return a 404. In such cases, we need to have a fallback case in our component.
const ProductDetailPage = ({ loadedProducts }) => {
if (!loadedProducts) {
return <p>Loading...</p>
}
return (
<>
<h1>{loadedProducts.title}</h1>
<p>{loadedProducts.description}</p>)
</>
)
}
In the above code, the user will see the Loading indicator instead of a 404 page, until the data comes and is then displayed on the page.
An alternative would be to set fallback: 'blocking'. In those cases, we don't need a fallback in the component, but the response will take a little longer.
const ProductDetailPage = ({ loadedProducts }) => {
return (
<>
<h1>{loadedProducts.title}</h1>
<p>{loadedProducts.description}</p>)
</>
)
}
export async function getStaticPaths() {
return {
fallback: 'blocking,
paths: [
{
params: {
prodId: 'p1'
}
}
]
}
}
It depends on your case. Sometimes you need to show something quickly (fallback: true). Sometimes it's worth waiting for it and you don't want to show an incomplete page to your visitors (fallback: 'blocking').
Read more about NextJS's Data fetching here.

Next.js: Skipping generating static sites at build time (instead: only at runtime, as getStaticProps has no data in build phase)

I want to use the same application for different customers, where there's a different database for each (which could be on-premise).
As a result, I have no data at the build phase (e.g. in CI/CD) that I could use to create static sites.
I thought about skipping generating the sites in getStaticProps via an environment variable.
When building the sites, I could tell Next.js to not use any data, something like this:
export const getStaticProps: GetStaticProps<HomePageProps> = async () => {
const isBuildPhase = process.env.IS_BUILD;
const data = isBuildPhase ? null : await fetchData();
return {
props: {
data: data ?? null,
},
revalidate: 5 * 60,
};
};
Now, I want to create the sites only at runtime (with next start), because at runtime, the built application has access to its database. In each getStaticProps the environment variable will be configured so data is fetched. When the application starts initially, it will generate all static sites when they're accessed.
Are there big downsides with this approach?
Are there maybe better solutions to this problem?
We had a similar requirement where the app needed to serve multiple tenants pages, but each tenant data is different. Which meant the app did not have access to data at build time and only runtime.
We leveraged getStaticPaths and getStaticProps to do this.
getStaticPaths
This method returns a fallback option (true || false || blocking), which we can use to decide to show a loader on the UX or block until the actual page loads.
export async function getStaticPaths() {
// Return empty paths because we don't want to generate anything on build
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return {
paths: [],
fallback: 'blocking',
};
}
When using this method, there isn't a need to maintain IS_BUILD env variable. (Depending on your specific use-case you may choose to use it or not)
getStaticProps
This method does the actual data fetching based on the URL path params for a tenant.
export async function getStaticProps({ params }) {
// Run your data fetching code here
const data = await fetch(params);
return {
props: data,
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 10 seconds
revalidate: 10, // In seconds
notFound: !data,
};
}
We also leverage Incremental Static Regeneration, to make sure that we refresh the page every revalidate seconds.
Pages
Single Template Tenants
When all the tenants share the same components, it's pretty straight forward:
const SingleTenantPage = ({ data }) => {
return <Component {...data} />;
};
export default SingleTenantPage;
Multi-Template Tenants
The actual page, just parses the props (passed down from getStaticProps), and then loads the appropriate component for that page.
This way we leverage a single route for multiple tenants. example.com/app/tenantA, example.com/app/tenantB, example.com/app/tenantC all 3 routes can be served out of pages/app/[slug]/index.js.
Using dynamic imports, we make sure that only the component for a specific tenant is loaded. (also helps in code splitting)
// Dynamic Import so we load only the required bundles
const templates = {
tenantA: dynamic(() => import(`../templates/tenantA`)),
tenantB: dynamic(() => import(`../templates/tenantB`)),
tenantC: dynamic(() => import(`../templates/tenantC`)),
};
const MultiTenantPage = ({ data }) => {
// Provided template is present in the data
// template: 'tenantA' || 'tenantB' || 'tenantC'
const { template, ...rest } = data || {};
// If the template doesn't exist, show a 404 Page instead
const Component = templates[template] || (() => <Error statusCode="404" />);
return <Component {...rest} />;
};
export default MultiTenantPage;
The multi-tenant page works well for us. We use a larger revalidate value because data isn't changing that often. Only caveat we had were a little bit complex test cases because of all the dynamic imports.
The above solution is very much similar to what you've considered for your use-case, albeit without an additional env variable (IS_BUILD)

React useEffect() hook highly affects SEO

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.

How do I make UseEffect hook run step by step and not last?

I have UseEffect hook that fetches data from DB and I want it to run FIRST in my component, but it runs last.
How do I make it run before "console.log(titleee)"?
Code:
const [cPost, setCPost] = useState([]);
const postId = id.match.params.id;
useEffect(() => {
axios.get('http://localhost:5000/posts/'+postId)
.then(posts => {
setCPost(posts.data);
console.log("test");
})
}, []);
const titleee = cPost.title;
console.log(titleee);
I don't think that's the correct path that you want to take.
In order to show the cPost on your page after the request /posts/+postId finished you can opt-out for two following options.
You can show a "loader" to the user if the cPost data is crucial for your whole component.
const [fetchingCPost, setFetchingCPost] = useState(false)
const [cPost, setCPost] = useState({});
const postId = id.match.params.id;
useEffect(() => {
setFetchingCPost(true)
axios.get('http://localhost:5000/posts/'+postId)
.then(posts => {
setFetchingCPost(false)
setCPost(posts.data);
})
}, []);
return fetchingCPost && <div>Loading</div>
Or you can have some default values set from the start for cPost. Just to make sure that your code doesn't break. I think the first solution might be more UX acceptable.
const [cPost, setCPost] = useState({title: '', description: ''});
If you want to store title as a separate variable you can use useMemo for instance or do it via useState same as with cPost. But even then you can't "create" it after the request finishes, you can simply change its value.
In case you want to use useMemo you can make it dependent on your cPost.
const cPostTitle = useMemo(() => {
return !!cPost.title ? cPost.title : ''
}, [cPost])
You have to change you'r way of thinking when programming in react. It is important to know how react works. React does not support imperative programming, it rather support declarative and top down approach , in which case you have to declare your markup and feed it with you'r data then the only way markup changes is by means of changing you'r data. So in you'r case you are declaring a watched variable using useState hook const [cPost, setCPost] = useState([]); , this variable (cPost) has initial values of [] then react continues rendering you'r markup using initial value, to update the rendered title to something you get from a network request (eg: a rest API call) you use another hook which is called after you'r component is rendered (useEffect). Here you have chance to fetch data and update you'r state. To do so you did as following :
useEffect(() => {
axios.get('http://localhost:5000/posts/'+postId)
.then(posts => {
setCPost(posts.data);
})
}, []);
this code results in a second render because part of data is changes. Here react engine goes ahead and repaint you'r markup according data change.
If you check this sandbox you'll see two console logs , in first render title is undefined in second render it's something we got from network.
Try adding async/await and see if it works. Here is the link btw for your reference https://medium.com/javascript-in-plain-english/how-to-use-async-function-in-react-hook-useeffect-typescript-js-6204a788a435

Categories