React: handing down state best practices - javascript

I'm in the midst of making a facebook clone. One of the main components of this site, is a feed that contains a list of posts. These posts are coming from the databse and are being rendered dynamically which is all good.
in Feed.js
const [posts, setPosts] = useState([]);
...
<div id='feed' role="feed">
{posts.map(
post => ( <Post setPosts={ setPosts } post={ post } key={ post._id } /> )
)}
</div>
in Post.js
const handleClick = async () => {
try {
await fetch(`/posts/${post._id}`, {
method: 'delete',
headers: {
'Authorization': `Bearer ${token}`
}
})
} catch (err) {
console.error(err.message);
}
};
<div className="post">
<article data-cy="post" key={ post._id }>{ post.message }</article>
<button onClick={handleClick}>delete post</button>
</div>
Where Post is a child of Feed.
When the delete button is clicked, the post is deleted from the database as expected, however it is not removed from the page until the browser is refreshed.
How could I have so that when the delete button within the post component is clicked, that the feed component is also re-rendered? I have thought about handing state down, but in order for me to delete a specific post from an array would require me to hand down both posts and setPosts as props, is this bad practice? And what would best practice in React for this scenario, as I feel this is something I will come across a lot.

Approach 1: mark the post as deleted
In your Post, you can handle request to the server to delete post.
After that, you can set the isDeleted flag (in Post). If the post is marked as deleted then you just return null. This way you will not render the Post and you didn't touch the state of the parent, which means no other component was rerendered.
Of course, this approach means that if something forces parent to rerender you can lose the internal state of your Post.
Approach 2: filter out deleted posts
As in approach 1 Post is handling deleting request. It also gets onDeleteSuccess callback prop. You can call with deleted post id. Then Feed can filter out deleted posts. This part unfortunately will rerender all posts.
Approach 3: Use React Query
Not direct your case but I think you will get there soon. As you will notice shortly fetching data from the server is not easy. There are many aspects to cover. React Query is a great tool for fetching data. It will cache your fetched data. After deleting the post, you can update the cache. It's similar to approach 2 but with all benefits of React Query.
Sorry if approach 3 is going too far from your original question ;-)

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.

How to update redux store with react suspense

In following code, Sample uses a async function to get some data. I will be using that data to update the Redux store so any component can access the username within the application.
const resource = fetchData();
function Sample() {
// throws a promise when retrieving data
const name = resource.read();
const dispatch = useDispatch();
dispatch(setUsername(name));
const username = useSelector((state) => state.username);
return <div>User: {username}</div>;
}
<Suspense fallback="loading....">
<Sample />
</Suspense>
Here, lets assume there is no way my application can proceed without the username. Well, <Suspense> at parent level achieves that up until data are fetched from the resource. However, there is a slight gap from Redux event dispatch to <Sample> component re-render where is displays User: instead of loading.... (event don't force the re-render immediately so username is empty). So I will see following content in the web page in the same order
loading.... --> User: --> User: Srinesh
So my requirement is to show loading.... until store is updated. On the web page, I'm expecting to see,
loading.... --> User: Srinesh
Is there a way to achieve this without using another condition at <Sample> component level to show loading.... if the username is null?
The first issue is that dispatching in the middle of component rendering logic is a side effect, and you must never do that.
A safer place to put that would be in a useLayoutEffect hook, which will dispatch as soon as the component is done rendering, but force a synchronous re-render before the browser has a chance to paint. That way you won't see the flash.

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.

React and Redux losing State

Hope you all are fine. I am new to react redux world. I am learning and working on a call logging project. I have a few questions and it would great if someone can guide me whether I am doing it wrong or tell me the alternative.
I am using JWT to authenticate a user. Once the user details are verified. I am dispatching success action and in the reducer, I am setting the state to authenticated true and the response. I am also storing the token and expiryTime in localStorage
In the root file which is index file. I am checking if the token exists in localStorage and if so then dispatching sign in action.
Everything is working. But I am losing other values like a response from a server. When he logged in for the first time. How can I tackle this problem ?
Secondly, there is a User initial icon on the top right corner. I get the initial when a user logs in and it gets stored in auth state. it works fine but once again if I refresh the page it becomes null and I lose that initial.
so I tried another way and stored the initial in localStorage. But the navbar already rendered on the screen and I don't see any initial until I refresh the page.
I have passed new key in mapStateToProps. ii and stored initial from localStorage in it and it working fine. Is this a valid way of doing it ???
Regards
Meet
const SignedInLinks = (props) => {
return (
<ul className="right">
<li><NavLink to="/signin" onClick=
{props.signOut}>Log Out</NavLink></li>
<li><NavLink className="btn btn-floating pink lighten-1" to="/">
{props.ii ? props.ii : null }
</NavLink></li>
</ul>
)}
const mapStateToProps = state => {
return {
auth: state.auth,
ii: window.localStorage.getItem('ui')
}
}
export default connect(mapStateToProps, { signOut })(SignedInLinks);
Rather than using localStorage in mapStateToProps, intialize your ii state in your reducer corresponding to that state and then pass it to your component via mapStateToProps. Something like this.
const iiReducer = (state = window.localStorage.getItem('ui') || false, action) => {
/*.
.
. Other Logic
.
.*/
return state
}
and then use it normally as you would from a store's state
const mapStateToProps = state => {
return {
auth: state.auth,
ii: state.ii
}
}
Hope this helps !
I believe I have an idea of what the problem is (I'm kind of a beginner in react and redux aswell, so tell me if I'm speaking nonsense).
You say that you store the token in localstorage (it is recommended not to do that btw, the recommended way is to store it in a cookie), and if a valid token is found in localstorage, you log in. I'm guessing that you store the response from the server (including the icon) in the app's state, or in the redux store? If that is the case, this information will be removed when you update the browser (f5),therefore not being loaded anymore.
The solution is to make sure to load the relevant data when the component mounts, so in your componentDidMount method (if you don't have one, make one), and set the state in that method.

Which is better CRUD coding style using Redux?

I have a simple question about coding style for single page application. My front end is using React Redux
For example I have a standard CRUD page where data is displayed in table and pop up modal form. Data table is filtered from the server not from the client.
My question : If i create, update or remove a data should I call a refresh function or just edit it in redux store?
Refresh function :
Data always updated
Newly added data is filtered
Two times request, slower, unresponsive (Main problem)
Redux store:
App looks responsive
One time request
Lost server side filter function and data is not updated if multiple users is using the app (Main Problem)
Any advice will be appreciated
Edit the store locally to give immediate feedback, then send the request and when you get the reply back consolidate the store with the new data
basically, do both things and get the best benefit of both worlds
Dispatch an async action which queries the server where filter happens and when it resolves, update redux state with the refreshed, filtered data.
Pseudocode
// dispatches an action to refresh data without page reload
export function refreshDataAction() {
return (dispatch, getState) => {
return (
fetch('api/data', options) // fetch the data from server and let it filter
.then(data => dispatch(updateDataAction(data)))
);
};
}
// dispatches an action to update redux state with filtered data
export default function updateDataAction(data) {
return {
type: 'UPDATE_DATA',
...data,
}
}
Then you could just call dispatch(refreshDataAction()). Data is filtered, no page refresh.
Calling refresh in a React application (not only React, but any real-time front-end app) kind of defies whole principal of using React.
What you should do is, whenever there occurs a data-changing operation in your client, you should trigger an API call, that alters your server-side data accordingly. Send the data back to the client (you can send it to all clients, if you fancy web-socket), save it to the Redux state to trigger a re-render.

Categories