I'm new using React, so please excuse me for the basic question. I didn't know where to find the right answer (or even how to ask the proper question). My doubt is: I have a React app where the user will have his content, which is basically a text and an image (using base64). He must be logged in to access it. I'm saving the content inside Firebase, as a JSON file with logged user.id generated by Firebase, as the category, and each user content is composed of an UUID I generate, the image (base64), and a text.
So, main page I list all the content for each logged user, and I want to put a button there so (s)he can share his content on Facebook, for instance. This "content" I'm calling a user story. So, for each listed story I create a Link to load this particular story in another page:
arrayOfStories.map((story) => (
(...)
<Link className="logo-container" to='/loadedstory' state={story}>
))
I'm passing the story object, which is basically the JSON with storyText, image, and uuid properties in a useLocation() state.
Then, from my loadedstory.component.jsx context page, I do this:
const LoadedStory = () => {
const {state} = useLocation();
(...)
return (
<>
<div>
{ state && state.uuid ? (
<div className="story-content"><img alt="Your content!" src={`data:image/png;base64,${state.image}`} className='resize-image'></img></div>
<div className="story-content"><p><span className='stroke'>{state.storyText}</span></p></div>
(...)
):
(<div></div>)}
</div>
(...)
</>
As you can see, if the user wants to share this particular story on Facebook, the base URL link will be /loadedstory and won't load anything without the logged user context from useLocation().
What's the best practices for developing such shareable content within a logged context?
I was thinking about to do something like /loadedstory/:uuid to use the story UUID from each story and query Firebase everytime this context is needed, but the problem is my Firebase category is the user.id not the story.uuid. Would I need to query the whole users in the database then for this UUID? Is this the right approach?
If you 're using NextJS, you could probably keep the data as-is and generate a static page for every user and all their stories, and the pages pointing to those stories.
You could pass the user-id and story-id to the story page. That way the story can fetch the (all) the user's data and sift through it to find the story-id. If you use something like ReactQuery, it will cache so you won't feel so bad about asking for data you already have.
As far as 'best practice', generally you would make a new DB table to store each story as its own record with the user-id as an owner. You use something like GraphQL to fetch only a user, only a story, or a user and their first 25 stories.
You also need a "router" to facilitate navigating around (pages or content) without re-inventing the wheel.
One very popular solution is to build the app using NextJS. It's easy to get up and running and their starter example covers navigation.
Many projects use React Router v6 and it's well worth running through their tutorial whether you'll end up using it or not.
If you want extra type safety, check out the TanStack Router. It's new and built on the shoulders of many great ideas that preceded it.
Related
I am implementing an Instagram clone. In my current app, I have two contexts:
UsersContext
PostsContext
In the users context, I store all the users data, and in the other one, I store the posts data (images, dimensions, description, totalLikes, totalComments, and location).
As you can see, I am not storing nothing about the post owner... neither his id, nor his avatar or username... literally there is no data about users in the PostsContext.
In my card component, I am consuming both contexts, in order to synchronize all the UI with the most up-to-date data which is relative to users and posts.
My question is: should I include the posts owners' data inside the PostsContext? Is there any kind of pattern or something? This is my first time using React Context API, I am a little lost. Is the only purpose of contexts to make things globally and synchronize all routes?
Any example?
Generally I would order the providers this way.
<UserContextProvider>
...
<PostContextProvider uid={uid}>
...
</PostContextProvider>
</UserContextProvider>
Where I make a provider function that will look like
export function PostContextProvider({uid, children}) {
const posts = getPosts(uid);
return (
<PostContext.Provider values={{posts}}>
{children}
</PostContext.Provider>
);
}
then access the posts.
If a user goes to a page that requires a context beyond what's on the url, I'd like to redirect them elsewhere. The use case is:
/todos/list - this page shows the user their list of todos. It contains links to:
/todos/edit?id=1 - this page allows the user to view/edit details about a particular todo.
If a user were to go directly to /todos/edit (with no id), I'd like to redirect them to /todos/list. I have tried doing this via navigate('list') conditionally in the constructor. This does update the browser url correctly, but it doesn't render the /todos/list page. Is this possible to do? Or is this not possible to do the para below?
I understand the more common url would be /todos/edit/1 so that reach router would handle my issue w/out me needing to deal with it. However, I'm just using this as an example of a piece of information required to render the page that isn't necessarily part of the the url path.
of course as soon as I type the question in stackoverflow, I find the answer is in the docs right in front of my face:
https://reach.tech/router/api/Redirect
I have a blog front-end app built using NextJS and it looks like this:
Each card here is a functional component called PostPreview.jsx. As you can see, each component comes with a heart icon. By default, this icon is to stay gray. However, when clicked it turns red, signifying that the post has been liked. This action only occurs if the user is logged in. If not, clicking the heart icon presents a login modal.
Right now, I'm only focussing on making the "like" persist between client-side navigations, i.e. without any interaction with the db/server.
As of now, clicking the icon, toggles the color alright. However, it fails to persist when you navigate away, say, by clicking on a post title and then hitting the back button to return to this page. What is the recommended way to achieve this functionality?
The entire codebase can be found at my repo here: https://github.com/amitschandillia/proost/tree/master/web
The code for the component in question (PostPreview.jsx) is at: https://github.com/amitschandillia/proost/blob/master/web/components/blog/PostPreview.jsx
The site is live at https://www.schandillia.com/blog.
I understand I could use Redux, but not sure how to prevent the value from being reset upon each re-render even when using Redux.
Illustrating the problem better
Visit blog page; several instances of component (PostPreview) render for the first time:
Receive array of post "likers" via the likedBy prop object.
Retrieve logged-in user's ID from the Redux store via userInfo.userID.
Look up userInfo.userID against the likedBy.readers array of IDs.
If user ID exists in readers array, post is liked, set liked to error to turn the heart icon red and push post's id to the likedPosts redux store.
If user ID doesn't exist in readers array, leave liked to inherit to leave it gray and remove post's id from the likedPostsredux store.
Like a post; click the heart icon:
Set liked to error to turn the heart icon red.
Push post's id to the likedPostsredux store.
Unlike a post; click the heart icon:
Set liked to inherit to turn the heart icon red.
Remove post's id from the likedPostsredux store.
Now click any link on page to navigate away from the page (client-side routing, no server contact here). Then hit the browser's back button to return to the blog page.
At this point, the component (PostPreview) re-renders and the redux store will be reset in accordance with the original likedBy prop object. This, of course, means that all the changes since the first render are gone. This is where I need help. How would you handle such a situation where user interactions like likes and dislikes have to be persisted across client-side navigations and also honored across re-renders?
I see two ways:
1) Simple: By using local storage you can write array of likes
likes: [likedPostId1, likedPostId2, ...]
And then in PostPreview check if current card id included in likes array
let isLiked = likes.includes(currentPostId);
2) Right: By using Redux, it's the same way, but you'll store likes array in Redux and use for page navigation react-router.
It's essential to understand how navigation works in SPA
For simple SPA implementation:
Simple solution would be to just use Context API with hooks (refresh will reset it)
1a. Another simple solution is to use LocalStorage with hooks (refresh or back button will persist it)
Than you could refactor it into REDUX (a lot of boilerplate)
2a. so maybe you could just use GraphQL as an app state
Here is an article explaining how to do that highlighting Redux vs hooks with Context API:
https://www.sitepoint.com/replace-redux-react-hooks-context-api/
Also it's worth to note that REDUX or GraphQL will not persist it itself between refreshes
4. If user ID exists in readers array, post is liked, set liked to error to turn the heart icon red and push post's id to the likedPosts redux store.
5. If user ID doesn't exist in readers array, leave liked to inherit to leave it gray and remove post's id from the likedPosts redux store.
You should wrap #4. and #5. as one action in redux store.
const mapDispatchToProps = (dispatch) => {
return {
onUpdateLikedPosts: (data) => dispatch(actions.updateLikedPostsAction(data))
};
};
And, Execture the function at when component of PostPreview is mounted.
const componentDidMount() {
this.props.onUpdateLikedPosts(data)
}
I am just so very confused.
I have a workflow where someone can start to fill out a form for a product. It is a long form and I want to save progress to the server as they type (but not until they fill it out for a bit). So we start at a url for creating the form, after they've typed for a bit, we POST to create a resource on the server, and after the request finishes, we update the url to the edit route with the new id.
In other words, you start filling out the form at url /product then after you've filled it out for a bit the url shifts to /product/123. After that, loading that URL gives you your form.
So basically I have
<Route path={`/product`} exact component={CreateProduct} />
and
<Route exact={true} path="/product/:productId" render={({
match: {params: {productId}},
location: {state: {data}={}}
}) => (
<EditProduct productId={productId} initialData={data}
)} />
See that state? That's because the way I do the switch over from create to edit mode is something like this
const id = await apiFetch(`/api/product`, data, {method: `POST`})
this.props.history.push({pathname: `/product/${id}`, state: {data} })
in the constructor of my <EditProduct> component I have
constructor({productId, initialData}) {
this.super()
this.state = {}
if(initialData)
this.setState({data: initialData})
else
getProduct(productId).then(({data}) => this.setState({data}))
}
By doing that, the initial data into the <EditProduct> is seeded from the <CreateProduct> component and I don't need to reload it from the server or anything.
This works, the transition is smooth, the url updates, and everything is hunky dory.
I can now continue editing the <EditProduct> component and it saves properly. I can open a new tab to the same url and it loads everything up and I can continue. This happens because in that situation initialData is undefined so it's loaded from the server. yay!
BUT
If I instead refresh the original tab things get weird. Any changes that have accumulated since the save are lost. Drilling down in the debugger I see the issue is that initialData passed from the location.state.data object is not empty - it is the initial object from when the product was first created.
So where on earth does it come from? I just did a full page refresh (even a "hard" refresh with no cache and devtools open). That data isn't in the URL (and in fact copy pasting the url into another tab in the same window doesn't have this issue).
The only mechanism I'm aware of that can persist data across refreshes but not to new tabs like this is sessionStorage, yet when I check it in the console, I am told
> sessionStorage
< Storage {length: 0}
I've even thought that maybe react-router is manipulating session storage just before the page unloads and just after it loads, but breaking on the first line of my javascript bundle shows the exact same thing.
So how on earth is this persistence happening!?
I believe the asker already resolve this problem, the answer is buried in the comment though.
The question is actually down to this:
Where the state come from when the user reloads the page? And state refers to props.location.state provided by react-router
TLDR; the state is not a plain javascript implementation, it is bound to the browser environment.
The BroswerRouter of react-router use the underlying native broswer history API directly so the history API is bound to the platform, you can not predict its behavior based on the normal rule.
Here is the special part:
The state object can be anything that can be serialized. Because Firefox saves state objects to the user's disk so they can be restored after the user restarts the browser
Most of the users treat the state as a plain javascript, so there is a problem
I had a very similar problem, and the same confusion.
Solved it with window.history.replaceState()
I had a simple search form which redirected to a second page, and used the location state from the router to repopulate the search input on the second page.
In myse case, this happened:
Search for "foo" on the first page -> Get redirected to the second page, and see search+results for "foo".
Search for "bar" on the second page. -> See results for "bar".
Hit refresh. Expectation? Either an empty search bar, or search+results for "bar". -> Instead, see search+results for "foo" (??)
I solved this by making it so that every time the user does a search on the second page, I replace the state using window.history.replaceState with the correct search term. This way a refresh gives the user the expected search. Replacing the state with an empty object on each search from the second page worked fine as well, giving the user an empty search on each refresh.
I am using admin-on-rest in my React app and wonder how is possible to make a custom page containing Show (or another detail component) with specified resource Id (for example: I want my app to fetch only resource with id = 1)
I don't know if this is canonical or the intended way to do things but you can supply both basePath and record as props to the ShowButton and EditButton components.
You can use this to redirect your user basically anywhere.
It should be possible (using some switch case or dependent input addon) to also add these props to the ListButton in the Show and Edit view and redirect your user back to the originating ListView.
Here is documentation to supply actions to the List view.
https://marmelab.com/admin-on-rest/List.html#actions