I'm having an issue when it comes to rendering some data out in my React app. I have a page /users that renders a list of users and clicking on a specific user routes the client to a page with more info on that user /users/:id. That page has a component with user data inside of it and that data is being fetched from a local JSON file. The issue is that when I click on one of the users from the /users page, the page for that specific user breaks. Console is telling me userData is undefined.
I have attempted to render the specific user page once the data has been fetched but I don't think I'm doing it correctly. I have tried setting an isLoading state with useState as well as conditionally rendering the component based on the state of the data being fetched but I'm still not having much luck. Below is the User page and the UserInfo component.
User page
function User() {
const [userData, setUserData] = useState([]);
const { id } = useParams();
const fetchData = async () => {
const response = await fetch(`../data/userData/${id}.json`);
const data = await response.json();
setUserData(data);
};
useEffect(() => {
fetchData;
}, []);
return (
<div>
{userData ? (
<UserInfo userData={userData} />
) : (
<>
<h1>Loading...</h1>
</>
)}
</div>
);
}
UserInfo component
function UserInfo({ userData }) {
return (
<div className='userInfo__details'>
<div className='userInfo__name'>
<h1>{userData[0].name}</h1>
</div>
</div>
);
}
The page is being rendered before the userData is being retrieved by the fetch request and that's causing the page to break. Ideally I'd like to have a loading spinner or something while the data is retrieved but anytime I've been able to having else condition render to indicate the data is still being fetched, it just hangs there and never actually renders the page with the fetched data.
Any help is appreciated.
[] as your default state is will return as true which renders the UserInfo component before time.
You can do this instead
return (
<div>
{!!userData.length ? (
<UserInfo userData={userData} />
) : (
<>
<h1>Loading...</h1>
</>
)}
</div>
);
Related
export default function Page({ data1 }) {
const [bookmark, setBookmark] = useState(
typeof window !== 'undefined'
? JSON.parse(localStorage.getItem('bookmark'))
: []
);
const addToBookmark = (ayatLs) => {
setBookmark([...bookmark, ayatLs]);
};
useEffect(() => {
localStorage.setItem('bookmark', JSON.stringify(bookmark));
}, [bookmark]);
return (
<>
<div className="modal-body">
<ul>
{bookmark.map((x) => (
<li key={x.nomor}>{x.tr}</li>
))}
</ul>
</div>
</>
);
}
{data1.map((x)) => (
<div className="pb-6 flex justify-between">
<span
onClick={() => addToBookmark(x)}
className={`text-sm `}>
{x.tr}
</span>
</div>
)}
When i use typeof window !== 'undefined' the Error: Hydration failed because the initial UI does not match what was rendered on the server in my code like this.
and when i change to localStorage.getItem('bookmark') the error is localStorage is not defined
when i click addToBookmark it will store data from props data1 to localStorage, no problem here, but when I fetch the data earlier in localStorage and I want to map the data that error appears
What's wrong with my code, why can't I map data from localStorage, please help me solve this problem.
The issue is that Next.js pre-renders pages by default, which means they are rendered on the server first and then sent to the client to be hydrated.
The error comes when you're setting the default value of the state based on this condition: typeof window !== 'undefined'.
Consider the following example:
const Page = () => {
const [name, setName] = useState(typeof window !== 'undefined' ? 'Peter' : 'Rick')
return <h1>{name}</h1>
}
export default Page
This code will throw an error of the type Error: Text content does not match server-rendered HTML.
If you inspect the page's source, you will see that the name rendered on the server was "Rick" while on the client-side's first render the name rendered was "Peter". There must be a match between the server-side rendered content and the client-side first render's content.
What you can do is move the localStorage data gathering and parsing logic to another useEffect instead and set the state in there. This solves the issue because useEffect only runs on the client-side, therefore you will effectively match both server-side and client-side first render content.
export default function Page({ data1 }) {
const [bookmark, setBookmark] = useState([])
const addToBookmark = (ayatLs) => {
setBookmark([...bookmark, ayatLs])
}
useEffect(() => {
if (bookmark.length === 0) return
localStorage.setItem('bookmark', JSON.stringify(bookmark))
}, [bookmark])
useEffect(() => {
const bookmarkFromLocalStorage = localStorage.getItem('bookmark')
const parsedBookmark =
bookmarkFromLocalStorage !== null
? JSON.parse(bookmarkFromLocalStorage)
: []
setBookmark(parsedBookmark)
}, [])
return (
<>
<div className='modal-body'>
<ul>
{bookmark.map((x) => (
<li key={x.nomor}>{x.tr}</li>
))}
</ul>
</div>
</>
)
}
I'm trying to find a way to create a search component <SearchBar /> and display the result array through another component <TableData />. I managed to build it up to a certain point but is not showing the desired result. The problem is that is not shows the data until i start typing. When the parent component first loaded or i made a refresh, the data disappeared. Also when the parent component first loads, the filterProjects from useState render an empty array.
How can i display all the data when the components loads ?
Here's my code block:
const Overview = () => {
const appCtx = useContext(AppContext);
// Copy the incoming data from the context
const newFilterCopy = [...appCtx.projects];
// Create new state for filtered data
const [filterProjects, setFilterProjects] = useState(newFilterCopy);
// Function that takes the input coming from the SearchBar component
const filterFunc = (input) => {
if (input === '') {
setFilterProjects(filterProjects);
}
const resultProjects = newFilterCopy.filter((project) =>
project.projectCompanyName.toLowerCase().startsWith(input.toLowerCase())
);
setFilterProjects(resultProjects);
return resultProjects;
};
return (
<div className={classes.Overview}>
<Container>
<SearchBar
placeholder='Search project by company name'
onInput={filterFunc}
/>
<div className={classes.TableContainer}>
<ExportBtn />
<TableData data={filterProjects} />
</div>
</Container>
</div>
);
};
export default Overview;
I'm building a blog website in Next.js, the API for the blog is from some headless CMS.
In a page I want to do the following:
List some blogs.
Set of buttons available, based on each button click different set of blogs are loading (should replace the blogs in #1).
Since SEO is needed I'm pretty confused to use which approach should I choose.
What I thinking that I generate the initial list with
getStaticProps (Static Generation), and after loading I want to replace the blogs based on user action (button click).
But I'm confused, is it possible to use static generation and SWR in single page?
Here is my implementation.
pages/index.js
export async function getStaticProps() {
const resPosts = await fetch(`${process.env.API_BASE_URL}posts?per_page=4&&_embed`)
const posts = await resPosts.json()
return {
props: {
posts
},
revalidate:10
}
}
export default function Home({posts}) {
return (
<div>
//pass data to FeaturedBlogs component (Components/featuredBlogs.js)
<FeaturedBlogs categories={categories} posts={posts} />
</div>
)
}
Components/featuredBlogs.js
const FeaturedBlogs = ({posts }) => {
return (
<div className={`${blogStyles.feature_blogs_wrap}`}>
//need to load the below blogs based on button click
<button onClick={handleClick('health')}>Health</button>
<button onClick={handleClick('latest')}>Latest</button>
//listing blogs
{posts.map((item ) => (
<Link key={item.id} href="/blog/view" passHref={true}>
<section>
<Image alt="blog_img" src={item._embedded['wp:featuredmedia'][0].media_details.sizes.medium.source_url} width="200" height="200" />
<div className={`${blogStyles.feature_blogs_content}`}>
<div className={`${blogStyles.feature_blogs_label}`}>
<span>{item._embedded['wp:term'][0][0].name}</span>
</div>
<p>{item.title.rendered}</p>
<div className={`${blogStyles.feature_blogs_author}`}>
<Image alt="author" src={item._embedded.author[0].avatar_urls[48]} width="200" height="200" />
<span>{item._embedded.author[0].name}</span>
</div>
</div>
</section>
</Link>
))}
</div>
)
}
const handleClick = (id) => {
//console.log(id)
}
What I need is to load the blogs in handleClick event, but the problem is this will not work since it's generated from the server at build time.
In the FeaturedBlogs component, you can create a state variable to keep track when a new category is selected on the client-side.
const [category, setCategory] = useState()
You can then make useSWR conditionally fetch data based on the value of this category variable.
const { data, loading } = useSWR(category ? [category] : null, fetcher)
The fetcher function would have the logic to fetch the posts for a given category.
const fetcher = async (category) => {
const response = await fetch(/* Endpoint to get posts for given category */)
return await response.json()
}
With this in place, you can have the component render the posts retrieved in getStaticProps as a default, when category is not set. This would happen on the initial render of the page. However, when a button is clicked, and category gets set, that category's data will be fetched and rendered instead.
Here's the full code of a modified version of your original component.
// Components/featuredBlogs.js
const fetcher = async (category) => {
const response = await fetch(/* Endpoint to get posts for given category */)
return await response.json()
}
const FeaturedBlogs = ({ posts }) => {
// Add state variable to keep track of the selected category
const [category, setCategory] = useState()
// Fetch posts from category only if `category` is set
const { data, loading } = useSWR(category ? [category] : null, fetcher)
const handleClick = (cat) => () => {
setCategory(cat)
}
// If `category` is set render data with post for given category, otherwise render all posts from `getStaticProps`
const itemsToRender = category ? data : posts
return (
<div className={blogStyles.feature_blogs_wrap}>
<button onClick={handleClick('health')}>Health</button>
<button onClick={handleClick('latest')}>Latest</button>
{loading && <div>Loading...</div>}
{!!itemsToRender?.length && itemsToRender.map((item) => (
<!-- Render items here -->
))}
</div>
)
}
Basically i'm looking for react table library that can take a mutable object ( to be specific an useRef object) as the main source of data to be displayed.
Basically i want to do something like this:
const TableViewComponent = () =>{
const tableData = useRef([{ rowName: 'test', value:0}] -> basically an array of objects that represents data for every row (the structure doesnt matter)
# code that receives data from server and updates the tableData.current with the data needed
return(
<Table data={tableData.current}/>
)
}
Basically, since i get a bunch of messages from the server and i update the data constantly (the number of rows stays the same), i don't want to rerender the table everytime. So i want to use the useRef to change the data thats being displayed on the table without triggering a rerender from react.
Im not sure if its something that can be done but any help is appreciated :). I tried react-table, rc-table but they didnt seem to work.
Basically, it looks to me like you'll have to do it yourself.
There's some libraries that might help you (like useTable which focuses on headless components) but I don't think they offer what you're looking for.
So let's quickly do a mock-up! (note: this is a quick sketch, assume that the undefined variables are stored somewhere else or are given from the fetch)
function useTableData({ initialData, itemsPerPage, ...etc }) {
const data = useRef(initialData);
const [loading, setLoading] = useState(false);
useEffect(() => {
data.current = fetchFromSomeWhere(...etc);
() => (data.current = null);
}, [etc /* Place other dependencies that invalidate out data here*/]);
const handleNewPage = useCallback(
async ({ pageIndex }) => {
if (!data.current[pageIndex * itemsPerPage]) {
setLoading(true);
data.current = [...data.current, await fetchMoreData(pageIndex)];
}
setLoading(false);
return data.current;
},
[itemsPerPage, data, setLoading],
);
return [data, handleNewPage, loading];
}
Notice how every single thing returned from this hook is a constant reference except for the loading! Meaning, that we can safely pass this to a memoized table, and it won't trigger any re-renders.
const TableContainer = memo(etc => {
const [data, handleNewPage, loading] = useDataForTable(...etc);
return (
<>
{loading && <Spinner />}
{/* Look ma, no expensive-renders! */}
<Body {...{ data }} />
<Pagination {...{ handleNewPage }} />
<OtherActions />
</>
);
});
However, you might have noticed that the body won't re-render when we click on the next page! Which was the whole point of pagination! So now we need some sort of state that'll force the Body to re-render
const TableContainer = memo(etc => {
const [currentPage, setPage] = useState(0);
const [data, handleNewPage, loading] = useDataForTable(...etc);
return (
<>
{loading && <Spinner />}
{/* We're still re-rendering here :( */}
<Body {...{ data, currentPage }} />
<Footer {...{ handleNewPage, setPage }} />
<OtherActions />
</>
);
});
And so we're left with a table that to use properly we need to:
Exercise restraint and properly invalidate old data. Otherwise, we'd be displaying incorrect data.
'Unpack' the data from the current ref on the Body component, and then render it.
In essence, after all that work we're still left with a solution isn't particularly attractive, unless you have some really complicated actions or some expensive scaffolding around the TableComponent itself. This might however be your case.
I'm new to React/Node and working on a learning project. It's a platform that connects users (freelancers) with nonprofit companies. When a user logs in, they can view a list of companies and click a button to connect with that company. They can then go to the UserConnections page to view all the companies they connected with.
When they click the 'connect' button, the connection is made in the database, and the button becomes disabled. This is currently working correctly.. unless you refresh the page, in which case the button becomes clickable again.
I'm probably not using state correctly. I'm tracking two different states. The first is when it's a "fresh" connection the user just made. The second is when they visit their UserConnections page, I'm retrieving their "old" connections from the database.
What can I do to make sure the state of a connection persists if the user refreshes the page, or if they come back later? My code is below (shortened to only include relevant code)
App.js
function App() {
const [currentUser, setCurrentUser] = useState(null);
const [connectionHandles, setConnectionHandles] = useState([]);
// Check if connected to this company
function hasConnectedToCompany(companyHandle) {
return connectionHandles.includes(companyHandle);
}
// Make the connection in the database
function connectToCompany(companyHandle) {
if (hasConnectedToCompany(companyHandle)) return;
VolunteerApi.connectToCompany(currentUser.username, companyHandle);
setConnectionHandles([...connectionHandles, companyHandle]);
}
return (
<BrowserRouter>
<UserContext.Provider value={{ connectionHandles, setConnectionHandles, currentUser, setCurrentUser, hasConnectedToCompany, connectToCompany }}>
<div>
<Navigation />
<Routes />
</div>
</UserContext.Provider>
</BrowserRouter>
);
}
CompanyDetail.js
function CompanyDetail() {
const { companyHandle } = useParams();
const [company, setCompany] = useState(null);
const { currentUser, hasConnectedToCompany, connectToCompany } = useContext(UserContext);
const [connected, setConnected] = useState();
React.useEffect(function updateConnectedStatus() {
setConnected(hasConnectedToCompany(companyHandle));
}, [companyHandle, hasConnectedToCompany]);
// Handle connect
async function handleConnect(evt) {
if (hasConnectedToCompany(companyHandle)) return;
connectToCompany(companyHandle);
setConnected(true);
let connectUserInDb;
try {
connectUserInDb = await VolunteerApi.connectToCompany(currentUser.username, companyHandle);
} catch (err) {
setFormErrors(err);
return;
}
}
if (currentUser) {
return (
<div>
<h1>{company.companyName}</h1>
<p>
<button onClick={handleConnect} disabled={connected}> {connected ? "Connected" : "Connect"} </button>
</p>
</div>
);
}
}
UserConnections.js
function UserConnections() {
const { currentUser, connectionHandles } = useContext(UserContext);
const [companies, setCompanies] = useState([]);
useEffect(() => {
let isMounted = true;
const connections = currentUser.connections.concat(connectionHandles);
const comps = connections.map((c) => VolunteerApi.getCurrentCompany(c));
Promise.all(comps).then(comps => isMounted && setCompanies(comps));
return () => { isMounted = false };
}, [currentUser, connectionHandles]);
if (!companies || companies.length === 0) {
return (
<div>
<div>
<p>You have no connections</p>
</div>
</div>
)
} else {
return (
<div>
<div>
<h1>Connections</h1>
{companies && companies.map(c => (
<CompanyCard
key={c.companyHandle}
companyHandle={c.companyHandle}
companyName={c.companyName}
/>
))}
</div>
</div>
);
}
};
the state will reset when the page refreshes, but you can either use the DB to save the connection, or use local storage.
Indeed, State is lost with React when you Refresh the page.
You should use another method to keep this information even if the user refresh the page. Here are the options:
In the database. Could work if the user is behind an authentification (Login password). Example User name: Paul, id; 3131, did click on the button "connect". In your database, you add a column in the table User called userConnect = true
In the url. As soon as the user click on the button "connect" you change the URL with React router. For example the URL was mydomain.com, and after clicking it becomes mydomain.com?clicked=true. If the user refresh your page, you still have the information about the user who clicked on the button.
In a cookie (More info here https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
In local Storage (More info here https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/local)