How to get current user to display profile page in React app? - javascript

I want to create a profile page in my React app. The user data is in the state but I want to load the data from the API as I load the page.
I've tried tyo fetch the data with this.props.getUser(this.props.auth._id) in the Constructor or in ComponentDidMount, but it did not load.
The data does comes in through componentWillReceiveProps, but it does not load on the first page load. Although, if I refresh the page, the data comes in.
This is part of my profile.js:
class Profile extends Component {
state = {
_id: '',
name: '',
email: '',
username: '',
errors: {}
};
componentDidMount() {
this.loadCurrentUser();
}
loadCurrentUser = () => {
this.setState(this.props.getUser(this.props.auth._id));
};
// https://hackernoon.com/replacing-componentwillreceiveprops-with-getderivedstatefromprops-c3956f7ce607
UNSAFE_componentWillReceiveProps(nextProps, nextState) {
console.log("received")
const { name, email, username } = nextProps.auth;
this.setState({ name, email, username });
}
// ...
// Some other code
// ...
const mapStateToProps = (state) => ({
auth: state.auth
});
export default connect(mapStateToProps, { getUser, logOut })(Profile);
My question is: How to load the page with the data I get from the API presented in the form fields?
Thank you
EDITED
I have edited my componentDidMount to use promises, but I still can not get it right. Now, My store gets the states right, but my component still does not get updated.
componentDidMount() {
this.props.getUser(this.props.auth.user)
.then(user => this.setState({ name: user.name, email: user.email, username: user.username }))
}
If I add a simple console.log, I still can not get the return from my query (getUser). This console.log is undefined.
componentDidMount() {
this.props.getUser(this.props.auth.user)
.then(user => console.log(user));
}
This is my getUser (src/actions/userActions.js):
export const getUser = _id => async dispatch => {
const res = await axios.get(`${process.env.REACT_APP_USERS_API}/api/v1/users/${_id}`);
dispatch({
type: GET_USER,
payload: res.data.data
});
};

The getUser action does not have return value. Instead, it updates the user data inside the redux store. So you shouldn't reply on the return value and set state from it.
Instead, dispatch the getUser action on page load so that the user data is updated and always access the data from the store (through this.props.auth). If there is an updated version of the user data, React handles the page re-render automatically:
componentDidMount() {
this.props.getUser(this.props.auth.user);
}
If for some reason, you need the user data to be saved in state (for example, you have a form on page where user can update username/password), then use getDerivedStateFromProps method:
static getDerivedStateFromProps(props, state) {
// return an object to update the state.
return props.auth;
}

Related

ReactQuery does not always mark data as changed when refetching

I am currently trying to use react-query to fetch data for use in a react-table. This is what i currently have, i omitted the table stuff for simplicity:
const { data, refetch } = useQuery(['users'], api.user.getAll);
useEffect(() => {
console.log('data changed')
}, [data]);
// this triggers 'data changed'
const createUser = useMutation((user: IUser) => api.user.create(user), {
onSuccess: () => {
refetch();
console.log('refetched')
},
});
// this does not
const updateUser = useMutation((user: IUser) => api.user.update(user), {
onSuccess: () => {
refetch();
console.log('refetched')
},
});
const onCreateClick = () => {
const newUser: IUser = {
id: 0,
userName: 'test',
email: 'test#mail.de'
}
createUser.mutate(newUser);
};
const onEditClick = (user: IUser) => {
user.userName = 'New Name'
updateUser.mutate(user);
};
console.log(data)
// ... render to table
When adding (or removing) a user everything works as expected. However when i update the data of an existing user the useEffect hook that tracks if data changed does not trigger (and for the same reason the react-table does not show the updated values).
The data does get fetched as expected in both cases and the console.log at the end does log the array with the updated values. It almost seems like the data field returned by useQuery does not get marked as changed for arrays if its length doesn't change.
I don't understand this, since this is new data that got fetched from an api and thus should always get treated as changed.
I am using axios under the hood to do the fetching if that is relevant.
What am i doing wrong, any ideas?
I found the issue:
user.userName = 'New Name'
This was a reference to a user inside of data. Never edit the values in data returned by useQuery in place. By doing this the newly fetched data did match the existing one and thus useQuery did not mark it as changed.

How do I fire off a react action when url changes?

So I'm building a website that users can view each others profiles. I have one Profile component and I want to show profiles based on users id. I'm sending those user id's as a url paramater.
My problem is if I switch from another users profile to my profile my getProfileById doesn't fire off (I have a my profile button that directly sends authenticated user's id to url paramater). I want to fire my action off when url change how do I do that?
Here is my getProfileById action code:
export const getAllProfileById = (userId) => async dispatch => {
dispatch({ type: "CLEAR_PROFILE" });
try {
const res = await axios.get(`/api/profile/${userId}`);
dispatch({
type: "GET_PROFILE",
payload: res.data.data
})
} catch (error) {
dispatch({
type: "PROFILE_ERROR",
payload: { msg: error.response.statusText, status: error.response.status }
})
}
}
Here is how I call getProfileById in my Profile component
useEffect(() => {
if (!profile) {
dispatch(getAllProfileById(id));
}
}, [dispatch])
Based on the comment section - you are using react-router-dom - you can use useParams hooks to capture the changes with useEffect combination, see from the documentation:
useParams returns an object of key/value pairs of URL parameters. Use it to access match.params of the current <Route>.
See a possible simplified working idea:
const Profile = () => {
const { id } = useParams() // destructure the id param from the URL
useEffect(() => {
// here you can see a logging step once id is changing
// based on the given dependency array
console.log(id)
// also you can dispatch your action as
dispatch(getAllProfileById(id))
}, [id])
return <> your component's code </>
}
Of course if you have have different parameter name in the URL, just change it from id to the proper one.

firestore valueChanges() is not updating userdata instantly

I am using Angular and cloud firestore for backend. I have a user profile, a logged in user and other users with follow or unfollow action button, based on the logged in user's current following list. I want the button text and the list of followers and following to get updated in the front end as soon as the click event is successfully completed. But, I am able to see the updated values only after there is a change in route, or I click the button twice.
Is there any way that, as soon as follow or unfollow is successful, data of loggedUser and selectedUser gets updated and the same updated data is reflected in my component.
userDetails.component.ts
ngOnInit(): void {
this.loggedUser = this.userService.getLoggedUserData();
//--------Get displayed user data
this.userService.SelectedUserChanged.pipe(take(1))
.subscribe(
(user:User)=>{
this.user=user;
if(this.user){
this.btnToDisplay(); //.....To show follow or unfollow depending on logged user's data
}}
);
}
I have a UserService, where I have subscribed to logged user's valueChanges()
fetchLoggedUser(uid: string) { //Subscribed in the appcomponent using authenticated user's id.
return this.db.collection('Users').doc(uid)
.valueChanges()
.pipe(
tap((user: User) => {
this.loggeduser = user;
this.loggeduser.id = user.userid;
this.fetchAllUsers();
})
);
}
fetchAllUsers() {
this.userSubs.push(
this.db
.collection('Users')
.valueChanges({ idField: 'id' })
.subscribe((users: User[]) => {
this.allUsers = users;
this.usersChanged.next([...this.allUsers]);
})
);
}
selectUser(uid: string) {
this.selectedUser = this.allUsers.find((user) => user.id === uid);
this.SelectedUserChanged.next({ ...this.selectedUser });
}
getLoggedUserData() {
return ({...this.loggeduser});
}
followUser(uid: string, email: string) {
this.db.collection('Users').doc(this.loggeduser.userid)
.update({
following: firebase.firestore.FieldValue.arrayUnion({
uid: uid,
email: email,
}),
});
this.db.collection('Users').doc(uid)
.update({
followers: firebase.firestore.FieldValue.arrayUnion({
uid: this.loggeduser.userid,
email: this.loggeduser.email,
}),
});
}
According to this post, valueChanges() and onSnapshot() automatically return the changes taking place to the document or collection that they are listening to. get() is used to get the data only once.
To achieve what you would like to, you'll need to follow the instructions on
Get real time updates with Cloud Firestore.
Based on this documentation, I have tested this code sample and when I am updating a value in the database the new document with the updated data are returned.
async function monitorUser(uid){
const doc = db.collection('users').doc(uid);
const observer = doc.onSnapshot(docSnapshot => {
console.log(`Received doc snapshot:`, docSnapshot.data());
}, err => {
console.log(`Encountered error: ${err}`);
});
}
Then you can update the public variable that corresponds to your user's data with the new values and the view should be updated.

Update React Native Screen when Firebase variable changes

Hi I'm new to firebase and don't know how to make a screen in my react native app update when a certain firebase variable changes.
In my app's homescreen, a user's posts are fetched from firebase in componentDidMount, and then rendered:
componentDidMount = () => {
this.setup();
};
setup = async () => {
const { currentUser } = await firebase.auth();
this.setState({ currentUser });
await firebase
.database()
.ref("users/" + currentUser.uid + "/posts")
.on("value", snapshot => {
this.setState({posts: snapshot.val()})
});
}
// render posts
In a separate screen, the user can choose to add a post and the firebase database is updated:
addPost = async () => {
const { currentUser } = firebase.auth();
await firebase
.database()
.ref("users/" + currentUser.uid + "/posts")
.push({
post: // data for post
});
However, though the database is successfully changed, the homescreen doesn't update and show the newly added post till it is manually reloaded. How do I add a listener to the homescreen, so that when the posts database variable changes, the screen automatically updates.
If you want to share the same data between multiple screens, the best is to use Redux.
Using Redux, all your renders function (which needs a specific data from the store) will automatically refresh if a data is updated. That way, you can only handle one firebase listener to update your redux store.
I would create a "Post" reducer and dispatch an update action every time I got something new from the firebase listener.
// Actions
const ADD = 'post/ADD';
const UPDATE = 'post/UPDATE';
// Initial state
const initialState = {
posts: [],
};
// Reducer
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case ADD:
return {
...state,
posts: [...state.posts, action.post]
};
case UPDATE:
return {
...state,
posts: action.posts
};
default:
return state;
}
}
// Action Creators
export function addPost(post) {
return {
type: ADD,
post
};
}
export function updatePosts(posts) {
return {
type: UPDATE,
posts
};
}
And the listener would be like :
import { update } from 'PostRedux.js'
firebase
.database()
.ref("users/" + currentUser.uid + "/posts")
.on("value", snapshot => {
Store.dispatch(update(snapshot.val()));
});

Redux: form submission callback

Basically I want to show message to user after he successfully submitted form. Like Thanks. Product added. And after few seconds I want this message to disappear.
Currently my code is pretty straightforward and contains 3 action types for AJAX request (ADD_PRODUCT_REQUEST, ADD_PRODUCT_SUCCESS, ADD_PRODUCT_FAILURE).
My component containing form connected to redux via mapDispatchToProps:
import {addProduct} from '../actions/product';
const mapDispatchToProps = dispatch => ({
onSubmit(productName) {
dispatch(addProduct(productName))
}
});
class AddProduct extends React.Component {
addProduct() {
const {onSubmit} = this.props;
onSubmit(this.productNameInput.val);
}
render() {
return (
<form onSubmit={::this.addProduct}>...</form>
)
}
}
And my action creator is also pretty straightforward (pseudocode):
export const addProduct = (name) => dispatch => {
dispatch(addProductRequest())
fetch(...).then(addProductSuccess()).catch(e => addProductFailure(error))
}
How I can using this "standard" react-redux architecture know that AJAX request executed successfully on component side?
I have only 1 idea - add some value to state informing that product was added, like added:true, but I think it's bad idea.
You must return fetch result in actions, then you need to wrap up then and catch statements to catch result of action, something like this:
addProduct() {
this.setState({ error: {}, isLoading: true });
this.props.onSubmit(this.productNameInput.val)
.then(res => this.setState({ isLoading: false, success: { title: "Success!", msg: "Product added!" } }))
.catch(err => this.setState({ isLoading: false, error: err.response.data.error} }));
}
You can bind in future this example handling to your form validation or to your frontend notification system.

Categories