Redux&React handle data the right way - reactjs

Assume the following situation: As a logged-in user, you're accessing your own profile page. The header menu will show your avatar as a logged-in user, and the rest of the page will show your user public information.
You got a list of users from the api, and you need to handle data from 2 of these users: the logged-in user and the user which profile page is being accessed (in the situation above, it's actually the same user).
Duplicating the data in store.users, store.currentUser and store.myUser would be a very bad idea (need to sync these 3 occurrences of the data everytime it gets modified). But I do not want my components using the current or logged in user data to be re-rendered everytime some lambda user data gets modified in the list of users.
How would you proceed in order to avoid data duplication, speed up data handling time (avoid costly list operations like filter/find), and avoid unnecessary re-rendering ?
After some thinking, I'm thinking of implementing it this way,
Data are stored as follow:
store = {
users: {
1: {
name: 'John',
},
},
currentUser: 1, // user which profile page is being accessed
myUser: 1, // logged in user
};
Now, the UserDetails component can be connected as follow:
#connect(state => {user: state[state.currentUser]})
export default class UserDetails extends Component {
render() {
return <div>User profile: {this.props.user.name}</div>;
}
}
and the page header using the logged in user data can be connected as follow:
#connect(state => {user: state[state.myUser]})
export default class HeaderMenu extends Component {
render() {
return <div>Logged-in user: {this.props.user.name}</div>;
}
}
But I'm not convinced that this is an ideal solution. Will have to test whether the re-rendering is truly avoided or not.
Your thoughts are very welcome!

I would store the details of the logged in user in the Redux store. Whatever data necessary to manage the user's session and render the persistent header. For other user profiles I'd load the data on-the-fly from the backend.
For example, if you had a route /profile/:id and this was the URL foo.com/profile/99
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
loading: true
};
}
componentDidMount() {
api.get('/getUser', {userId: this.props.match.params.id}, response => {
this.setState({
user: response.user,
loading: false
});
});
}
render() {
return (
...
);
}
}

Related

onAuthStateChanged firebase auth creating a user twice React Nextjs

I have an app that I do not need a scenario where there is no user. Once the user lands on the main page a user is created for them anonymously. If they are a user with credentials and they are signed in when they sign out a user anonymous should be created again till they sign in again.
I took a HOC component that was working until I was ready to go into production then I noticed a bug in code where a user is created twice when they land on the index page.
This is my HOC that I wrap my index page and every other page with
const AuthHoc = (Component) => {
return class extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("mounted") /////this logs twice for some reason I dont know why
auth.onAuthStateChanged((user) => {
if (user) {
console.log(user.uid);
} else {
console.log("no user");
var user = auth.currentUser;
if (user) {
// User is signed in.
console.log("there is already a suer")
} else {
// No user is signed in.
auth.signInAnonymously().then(async (cred) => {
uid=cred.user.uid
console.log("user created with id", cred.user.uid);
});
}
}
});
render() {
return <Component {...this.props} />;
}
};
};
export default AuthHoc;
and wrapping my index page as
export default connect(mapStateToProps)(AuthHoc(Index));
Every log in this code is called twice. The component mounts... twice (I don't know why) it finds no user, as it should, creates a user and logs the new user id. The onAuthStateChanged callback fires and logs the uid in the if user block of code. Then the problem comes where it reruns the whole process a second time, it creates a user again ignoring the conditionals.
How best can I improve this code. I need to reuse it across the entire app

Retrieveing data from firestore based on url

I am trying to render the profile page using data I get from firestore based on URL content.
For example when a user types mywebsite/profile/someusername I would like to retrieve profile info from firestore database and render the Profile component. I have a redux store that keeps the state of the App and I have postReducer and authReducer and respective actions. When I use componentDidMount() lifecycle method for the first render when the users go their own profile page it shows info about them and their own data. But upon typing someone else's username on the URL I get errors like cannot read property 'props' of undefined. What would be a good strategy for me to go with? Any help would be appreciated.
My code for Profile component:
class Profile extends Component {
//component state and render method are removed to make some space
componentDidMount() {
this.props.getUserPosts(this.props.profile.username);
}
}
const mapStateToProps = (state) => {
return {
auth: state.firebase.auth,
profile: state.auth.profile,
profileError: state.auth.profileError,
userPosts: state.post.userPosts,
userError: state.post.userError
}
};
const mapDispatchToProps = (dispatch) => {
return {
getUserPosts: (username) => {
dispatch(getUserPosts(username));
},
getProfile: (username) => {
dispatch(getProfile(username));
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
It very much depends on how you setup firebase in your project, but you would just access firestore according to their documentation. I have written a small (as small as it could be I belive) example to another question: Firebase listener with React Hooks
You are more than welcome to grap some inspiration from it.

How can I get role before render()

I want to do the following:
send a request to the server, verify JSON web-token and get user details (name, email and role);
then the page appears with the specific menu items (if the user is admin it shows him Main, Admin and Logout items; if not - Main and Logout).
I thought about just getting the token from localStorage, then decoding it and taking a role from it. But what should I do if I change the role in database (for example from admin to user)? Decoded token on the client-side will contain the "admin" role. So this user will be able to see the admin page.
You can use a variable in store called isFetchingUserDetails and set it to true when you make that async call to the server to get the details.
Till isFetchingUserDetails is set true, you can have an if statement in the render() to return a spinner or other component which shows the user that the page is loading.
Once you get the response from the server, isFetchingUserDetails will be set to false and the rest of the render() will be executed.
Without Store
import React, { Component } from "react";
import ReactDOM from "react-dom";
class App extends Component {
constructor() {
super();
this.state = { isFetchingUserDetails: true };
}
componentDidMount() {
fetch(`https://api.makethecall.com/users/1`)
.then(res => res.json())
.then(() => this.setState({ isFetchingUserDetails: false }));
}
render() {
if (this.state.isFetchingUserDetails) {
return <Spinner />
}
return (
<Home />
);
}
}

React: Creating different user profiles

I'm making an app in React and I want to users have the option to create an account using their email/password as auth factors. However, I want the option for two different user types. For example, a client and physician. So that when a user logs in they are taken to unique profiles (ie: clientProfile.js or physicianProfile.js) to that user type. Is this a db item? A firebase Auth token? Any advice would be great!
When a user registers they'll select their role from a select dropdown (or whatever UI element you choose): Client or Physician. You'll save this to the database.
When the user logs in you'll return their role along with any other relevant user data the front-end needs to handle the user.
When you render the profile you'll check their role and return the appropriate component: clientProfile.js or physicianProfile.js
Your component may look something like this:
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
user: null
};
}
componentDidMount() {
/* fetch user from database */
this.setState({user: response.user});
}
render() {
const { user } = this.state;
if (user === null) {
return <Loading/>;
}
if (user.role === 'physician') {
return <PhysicianProfile user={user}/>;
}
return <ClientProfile user={user}/>;
}
}

Update route and redux state data at same time

I have an app that has user profiles. On the user profile there are a list of friends, and when clicking on a friend it should take you to that other user profile.
Currently, when I click to navigate to the other profile (through redux-router Link) it updates the URL but does not update the profile or render the new route.
Here is a simplified code snippet, I've taken out a lot of code for simplicity sake. There are some more layers underneath but the problem happens at the top layer in my Profile Container. If I can get the userId prop to update for ProfileSections then everything will propagate through.
class Profile extends Component {
componentWillMount() {
const { userId } = this.props.params
if (userId) { this.props.getUser(userId) }
}
render() {
return <ProfileSections userId={user.id} />
}
}
const mapStateToProps = ({ user }) => {
return { user }
}
export default connect(mapStateToProps, { getUser })(Profile);
As you can see, what happens is that I am running the getUser action on componentWillMount, which will happen only once and is the reason the route changes but the profile data does not update.
When I change it to another lifecycle hook like componentWillUpdate to run the getUser action, I get in an endless loop of requests because it will keep updating the state and then update component.
I've also tried using the onEnter hook supplied by react-router on Route component but it doesn't fire when navigating from one profile to another since it's the same route, so that won't work.
I believe I'm thinking about this in the wrong way and am looking for some guidance on how I could handle this situation of navigating from one profile to another while the data is stored in the redux store.
So I would suggest you approach this in the following way:
class Profile extends Component {
componentWillMount() {
const { userId } = this.props.params
if (userId) {
// This is the initial fetch for your first user.
this.fetchUserData(userId)
}
}
componentWillReceiveProps(nextProps) {
const { userId } = this.props.params
const { userId: nextUserId } = nextProps.params
if (nextUserId && nextUserId !== userId) {
// This will refetch if the user ID changes.
this.fetchUserData(nextUserId)
}
}
fetchUserData(userId) {
this.props.getUser(userId)
}
render() {
const { user } = this.props
return <ProfileSections userId={user.id} />
}
}
const mapStateToProps = ({ user }) => {
return { user }
}
export default connect(mapStateToProps, { getUser })(Profile);
Note that I have it set up so in the componentWillMount lifecycle method, you make the request for the initial userId. The code in the componentWillReceiveProps method checks to see if a new user ID has been received (which will happen when you navigate to a different profile) and re-fetches the data if so.
You may consider using componentDidMount and componentDidUpdate instead of componentWillMount and componentWillReceiveProps respectively for the fetchUserData calls, but it could depend on your use case.

Resources