re-updating the page state - reactjs

There are 2 pages: the first loads an array of posts with a jsonplaceholder, the second displays the body of a specific post by clicking on the post.
When I return from the post page to the page with all the posts, the data is updated and re-requested.
How can I save the page state?
const [data, setData] = useState([])
const [posts, setPosts] = useState([])
const getPostsData = async () => {
try {
const postsData = await getData('https://jsonplaceholder.typicode.com/posts')
setData(postsData)
} catch (error) {
console.log(error.message)
}
}
useEffect(() => { getPostsData() }, [])
useEffect(() => { setPosts(data) }, [data])
posts.map(post => <Link to={`${post.id}`} key={post.id}><li className="list-group-item" >{post.title}</li></Link>)
I use 2 states. One is for loading data, and the second is for displaying. I need it for sorting and searching.

I think for your case it's better to use redux , context or react-query, but if you don't want to, you must avoid using Link for the child component because when you change route your previous state will be removed
you can simply show post detail as a modal or part of the main page

Related

useEffect not working in Stack Navigation

I have 2 screens in Stack Navigator.
All Categories,
Add a new category
In the All Categories screen, all categories are displayed.
useEffect(() => {
loadCategories();
}, []);
This is the useEffect hook that load all the categories.
I have made a touchable opacity to navigate to Add a new category screen, So users can easily add a new category if needed.
What I'm expecting to do: So after adding a new category and going back to the All Categories screen loadCategories() should run again, So the user can see the newly added category there. But the problem is when I add a new category and go back the loadCategories() function doesn't execute again. What will be the cause for this?
If you want to load categories when screen is focus
function Categories({ navigation }) {
React.useEffect(() => {
const loadCategories = navigation.addListener('focus', () => {
loadCategories();
});
return loadCategories;
}, [navigation]);
...rest
}
Often we need to fetch data when we come to some screens back,
For this, there is a hook from the #react-navigation/native library (useFocusEffect), which we can use to accomplish such requirements.
For Ex.
import { useFocusEffect } from '#react-navigation/native';
function Profile({ userId }) {
const [user, setUser] = React.useState(null);
useFocusEffect(
React.useCallback(() => {
const unsubscribe = API.subscribe(userId, user => setUser(user));
return () => unsubscribe();
}, [userId])
);
return <ProfileContent user={user} />;
}
In the above code, Whenever the ProfileContent screen is getting focused, the code inside the useFocusEffect will re-run.
Refer to official docs for in-depth understanding.
I found this answer which is relatable to this question.
https://stackoverflow.com/a/62703838/19317939
import {useIsFocused} from '#react-navigation/native';
...
const isFocused = useIsFocused();
useEffect(() => {
if (isFocused) loadCategories();
}, [isFocused]);

Update server-side rendered props dynamically

I've got posts with comments and like to implement a post view that includes all comments to that post.
My getServerSideProps passes the post (including all comments) to my page. Whenever a new comment is written the comments should be dynamically updated, but I'm currently facing some problems with that.
My post view:
const PostView: NextPage = ({ post }) => {
return (
<Layout>
{post.title}
<CommentList initialComments={post.comments} postId={post.id} />
</Layout>
);
};
export default PostView;
export const getServerSideProps = () => {
const post = await getPost(); // returns the post and all its comments
return { props: { post } };
};
The CommentList component:
const CommentList = (initialComments, postId) => {
const { data: comments } = useQuery(["comments", postId], async () => getComments(), { initialData: initialComments);
return (
<>
Comments: {comments.length}
... new comment form ...
... list of comments ...
</>
);
}
The reason why I still want to query comments with react-query is simple: comments should be server-side rendered so that they become seo-relevant, while I want human users to get a dynamic list that can be updated.
When writing new comments I update the QueryClient of react-query by hand:
export const useCreateCommentMutation = (postId: string) => {
const queryClient = useQueryClient();
return useMutation(
["comments", postId],
async (values) =>
await axios.post("/api/comments", values),
{
onSuccess: async res => {
queryClient.setQueryData<CommentWithAuthor[]>(
["comments", postId],
prev => [...(prev || []), res.data],
);
},
},
);
};
This seems to work at first glance; when I check the DOM the comments are included and when writing new comments they dynamically appear.
Unfortunately, when I refresh the page I get the following error:
Text content did not match. Server: "3" Client: "4"
3 (or 4) in this case is the comments.length output.
What am I doing wrong in this case?
Thanks
Edit 1:
I've also tried fixing it by using useEffect:
const [usedComments, setUsedComments] = useState([]);
useEffect(() => {
setUsedComments(comments || initialComments);
}, [comments])
And render usedComments instead - but unfortunately now the comments are no longer part of the DOM.
Why don't you try using useState() hook to store that the Comment data . Every time useQuery runs it will update the state which will cause re-rendering of the comment and also.
I can't think of anything other then this. I don't know what your comment json/data look like to do the server side dynamic rendering.
And useQuery runs on user action like click on add new comment button or a time loop.
and your error seems like It is caused by some Server and client attribute of component.

useEffect and state management library

I have this problem that I am trying to understand and solve. I want to fetch data in the main component and allow editing of this data. The problem is that the first time the data is properly loaded into the state but if I go back to the previous page where the table is and enter to edit another record then until I re-render the page the data from the previous record are in the state. I use Zustand to pass data between components. Please help guys :(
const { id } = useParams<string>();
const setData1 = useStore((state) => state.setData1)
const setData2 = useStore((state) => state.setData2)
React.useEffect(() => {
async function fetchData() {
const response = await getExampleData(id);
setData1(response.name);
setData2(response.values);
}
fetchData();
}, [id]);
return(
<ComponentData1/>
<ComponentData2/>)
And in i.e. ComponentData1 i'm using this below.
const data1 = useStore((state) => state.data1)

React component content disappears after page refresh

I am new to react and am having trouble figuring out why the data inside my Content component does not re-render on refresh.
When I visit one of the routes, say /sentences-of-the-day, and then I refresh the page, it seems all the stuff inside content is gone.
Can someone please help me out?
Here is the code sandbox:
https://codesandbox.io/s/mainichome-v7hrq
You need to load the data once the component is mounted (using useEffect) set to local state to trigger the render. In each refresh, mounting happens again and you have the data after each refresh.
Define another function in content.data.js
export const getContentData = () => {
return Promise.all(contentDataStories.map((func) => func()));
};
In your content.component.jsx
import { getContentData } from "./content.data.js";
const [content, setContent] = useState([]);
useEffect(() => {
(async () => {
setContent(await getContentData());
})();
}, []);
Code sandbox => https://codesandbox.io/s/mainichome-forked-4sx5n?file=/src/components/content/content.component.jsx:302-449
The problem is here:
import contentData from "./content.data.js";
//...
const [content] = useState(contentData);
That imports contentData and then sets it as state.
However, that value is asynchronous.
const contentData = [];
contentDataStories.forEach(function (func) {
func().then((json) => {
contentData.push(json);
});
});
export default contentData;
It's just [] until those promises reoslve.
So what's happening is that the page is loading fine, but the content from that file hasn't loaded before the first render.
This is a race condition. Which will happen first, the data loading or the render? Sometimes the render wins and everything is fine, but sometimes it doesn't and you get a blank page.
To fix it, you need to make React aware of your data loading, so that it can re-render when the data finishes loading.
First make a function that does your async loading:
export function getContentData() {
return new Promise((resolve) => {
// fetch async stuff here
resolve(myDataHere)
})
}
And then call that from a useEffect, which sets the state.
function Content() {
const { titleParam } = useParams();
const [content, setContent] = useState(contentData);
useEffect(() => {
getContentData().then(setContent);
}, [getContentData]);
//...
}
Now when you component mounts, it calls getContentData. And then that promise resolves, it sets the state, triggering a a new render.

fetching data in React with DynamicRoutes, nextJS and useEffect

I have a blog link. Link is dynamic route with blog id. It's the Link wrapper from Next.
//link
<h3 className="blogTitle">
<Link href="[blog]" as={props.item.postingId}>{props.item.title}</Link>
</h3>
Now I want to pass "blog id" to the component and to present data in a new page.
//page where link leads to
const ad = () => {
const router = useRouter()
const {
query: {blog},
} = router
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
console.log('....outside useEffect log', blog)
useEffect(() => {
console.log('useEffect consolelog', blog);
axios.get('httpwww.blogapiadress.com/'+ ad)
.then(response => setData(response.data))
.then(setLoading(false))
}, [])
return(
<Container fluid className="padding0">
/// data should be here.
</Container>
);
}
export default ad;
Problem: in useEffect console.log('blog', blog) returns undefined, so router does not return value from query. However, outside of useEffect it does. How to solve that issue, I want to fetch data related to the router query?
Since axios is getting undefined instead of blog id, I am getting 404.
You can use getStaticProps() to fetch the blog data at build time.
Example:
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getStaticProps() {
// Call an external API endpoint to get posts.
// Access route params:
const blog = context.query.blog // or context.params.blog for parametrized routes
return {
const res = await fetch('https://...')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
export default Blog
More info on NextJS docs.
I don't think you need to use the global window object to access dynamic data related to your route -- you should be able to use the Next router. I think the way you are defining href -- the only required prop for Link is causing issues. Looking at docs and your current exampel you probably want to use something like:
<Link
href={{
pathname: '/[blog]',
query: { blog: props.item.postingId },
}}
>
<a>{props.item.title}</a>
</Link>
// or
<Link href={`/${encodeURIComponent(props.item.postingId)}`}>
<a>{props.item.title}</a>
</Link>
Then you should be able to properly access [blog] (i.e., your postingId) using Router. For example, if your route was defined dynamically by /[blog].js, you could use the following:
import { useRouter } from 'next/router'
const ad = () => {
const router = useRouter()
const { blog } = router.query
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(()=>{
axios.get('httpwww.blogapiadress.com/'+ blog)
.then(response => {
setData(response.data)
setLoading(false)
})
}, [])
if (loading || !data) return <div> Loading... </div>
return(
<Container fluid className="padding0">
/// Render data
</Container>
);
}
export default ad;
Looking for answer for a few hours, and when I posted question on stack.. I figured out the answer.
So problem was that query is empty with next static generation at build time https://nextjs.org/docs/api-reference/next/router#router-object
I havent found best solution, but i found working one.
I got blog id from the windows.location href
useEffect(()=>{
const last = window.location.href.split('/').pop();
console.log('last', last)
axios.get('https://blogpostings.com/'+last)
.then(response => setData(response.data))
}, [])
I am not sure if its proper or good way, but it works.
I hope someone will find this helpful.
If you want get the query parameter from the link, you need to insert at the end of the url: https://sample-link.com?blog=123
Then same as your code above:
import { useRouter } from 'next/router'
const router = useRouter()
const {
query: {blog}
} = router;
console.log("blog: ", blog)
Result:
blog: 123

Resources