MeteorJS: Get user profile on client side - reactjs

So, this problem has been well documented, yet here I am, stuck again...
On server side, I have publish:
Meteor.publish('userData', function(){ if (!this.userId) { return null; }
return Meteor.users.find({}, {fields: {'profile': 1}}); });
On client side router subscriptions, I have:
this.register('userData', Meteor.subscribe('userData'));
And then in my client code, I have:
if (Meteor.userId()) {
var profile = Meteor.users.find(Meteor.userId()).profile;
console.log(profile); // I keep getting undefined...
I am not using the autopublish or insecure package.
My data in the mongodb collection looks like:
{"_id" : "...", profile: { "password" : { "network" : "...", "picture" : "none" } }
My error says:
Uncaught TypeError: Cannot read property 'password' of undefined
Thoughts?

The user profile is automatically published to the client, you don't need to write a custom publication to send it. That's why you should never store sensitive user information in the profile. You can get rid of that publication.
To access the profile in a component, all you need to do is wrap your component (or entire app) in a data container that sends the profile to the component. The reason you do this is that the container is reactive, so the component will load until the meteor user object is ready, then you can immediately access stuff stored in the profile.
Create container:
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
import { Navigation } from '/imports/ui/components/user-navigation/Navigation';
export default createContainer(() => {
const loading = !Meteor.user();
const user = Meteor.user();
return { loading, user };
}, Navigation);
Access profile in component:
import React, { Component } from 'react';
export class Navigation extends React.Component {
constructor(props) {
super(props);
}
render() {
const { user, loading } = this.props;
return (
loading ? <Loading /> :
<div className="navigation">
User's name is: { user.profile.firstname }
</div>
)
}
};

So, as it turns out, most of my code was correct, but as ghybs pointed out above, I still needed to wait for the user data. So, here is my working code:
// in publish.js on the server
Meteor.publish('userData', function(){ if (!this.userId) { return null; }
return Meteor.users.find({_id: this.userId}, {fields: {'profile': 1}}); });
// in routes.jsx using FlowRouter
// after importing all the module external data
FlowRouter.route('/',{subscriptions:function(){
this.register('getUser', Meteor.subscribe('userData'));
}, action() {
mount(...etc...
// Keep in mind that I am using TrackerReact NPM to handle re-renders...
// in my component
if (FlowRouter.subsReady("getUser")) {
// this returns an array with one object
var userObject = Meteor.users.find({_id: Meteor.userId()}).fetch()[0];

Try replacing
var profile = Meteor.users.find({_id: Meteor.userId()}).profile;
with
var profile = Meteor.users.findOne({_id: Meteor.userId()}).profile;

I think the issue is with the query
var profile = Meteor.users.find({_id: Meteor.userId()}).profile;
or this should work too
var profile = Meteor.user().profile;

Related

React - Redux: Store Data is not rendering?

I am trying to render a simple profile from my redux store. Actually, the data is successfully provided and mapped to my props. I can log the profile information in the console. But when I render the profile data, it tells me that the data is not defined.
When I console log the profile , it says undefined first and then displays the data (see down). I wonder if react tries to render in the moment the data is undefined.
Console Log
undefined profile.js:28
Object profile.js:28
bio: "Test"
id: 2
image: "http://localhost:8000/media/default.jpg"
Component
export class ProfileDetail extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.getProfile(this.props.match.params.id);
}
static propTypes = {
profile: PropTypes.array.isRequired,
getProfile: PropTypes.func.isRequired,
};
render() {
const profile = this.props.SingleProfile;
console.log (profile)
return (
<Fragment>
<div className="card card-body mt-4 mb-4">
<h2>{profile.bio}</h2> // if i use here just a string its successfully rendering
</div>
</Fragment>
);
}
}
function mapStateToProps(state, ownProps) {
const { profile} = state.profile
const id = parseInt(ownProps.match.params.id, 10)
const SingleProfile = profile.find(function(e) {
return e.id == id;
});
return { profile, SingleProfile}
}
export default connect(
mapStateToProps,
{ getProfile}
)(ProfileDetail);
Im a little bit lost and happy for any clarification.
You need to check if profile is loaded.
It is normal that request takes some time - it is not instant - to load any data.
Check that profile exists and display its property
<h2>{profile && profile.bio}</h2>
As an alternative, you could display a message:
<h2>{profile ? profile.bio : "Profile not loaded yet"}</h2>

ReactJS:setState could not work in Syncfusion React Toast

I using Syncfusion React Toast with singnalr for showing popup notification to user. but after fetch data from server with signalr and set message state with fetched value, toast content shows initial value which it`s assigned in constructor function.
import React, { Component } from 'react';
import { ToastComponent } from '#syncfusion/ej2-react-notifications';
import * as signalR from '#aspnet/signalr';
class Notification extends ToastComponent {
constructor() {
super(...arguments);
this.position = { X: 'Right', Y: 'Top' }
this.state = { message: '555'}
}
toastCreated() {
this.toastInstance.show({ timeOut: 0 })
}
componentDidMount() {
const notifConn = new signalR.HubConnectionBuilder().withUrl("/notif").build();
notifConn.on("ReceiveMessage",(msg) => {
this.setState({ message: msg });
});
notifConn.start().then(function () {
notifConn.invoke("SendNotification");
}).catch(function (er) {
console.log(er.toString());
});
}
render() {
return (
<div>
<ToastComponent
id='toast_target'
ref={toast => this.toastInstance = toast}
title={this.state.message}//it shows 555!!!
content={this.state.message} //it shows 555!!!
position={this.position}
showCloseButton
created={this.toastCreated = this.toastCreated.bind(this)}
/>
</div>
);
}
}
export default Notification;
Greetings from Syncfusion support.
By default, we couldn’t modify the content dynamically for existing displaying toasts. Dynamically changed content will be shown on notify the next toasts.
So, we suggest you to hide the previous toast and show the next toast after setState.
Please find the below code for your reference.
Chat.js
constructor(props) {
super(props);
this.state = {
message: '',
};
componentDidMount = () => {
const nick = window.prompt('Your name:', 'John');
const hubConnection = new HubConnection('http://localhost:5000/chat');
this.setState({ hubConnection, nick }, () => {
…………..
this.state.hubConnection.on('sendToAll', (nick, receivedMessage) => {
this.setState({ message: receivedMessage }); // Change the toast content using state
this.toastObj.hide(); // You can hide the toast
this.toastObj.show(); // You can show the toast
});
});
};
create = () => {
this.toastObj.show({ timeOut: 0 });
}
<ToastComponent ref={ (toast) => { this.toastObj = toast; } } content = { this.state.message } id = 'toast_default' created = { this.create.bind(this) } > </ToastComponent>
We have created sample for an ASP.NET core signalr with the react toast component.
Sample: https://www.syncfusion.com/downloads/support/directtrac/general/ze/AspNetCoreSignalRToastReact1983063835
Please find the below steps to run the above sample.
Navigate inside of AspNetCoreSignalR_React.Client folder and enter
‘npm install’.
Start the client app by entering ‘npm start’.
Run the AspNetCoreSignalR_React.Server project.
If the above solution doesn’t meet your requirement, kindly send the below details.
Have any reason on using static toasts with dynamic content update?
Regards,
Narayanasamy P.

Meteor - how to give tracker autorun a callback

I have a little piece of code that renders data from the database according to the path name. My only problem is that when I try to retrieve that data, using this.state.note._id it returns an error that says it cannot find _id of undefined. How would I access my object that is put into a state? It only gives the error when I try to access the items inside the object such as _id
import React from "react";
import { Tracker } from "meteor/tracker";
import { Notes } from "../methods/methods";
export default class fullSize extends React.Component{
constructor(props){
super(props);
this.state = {
note: [],
document: (<div></div>)
};
}
componentWillMount() {
this.tracker = Tracker.autorun(() => {
Meteor.subscribe('notes');
let note = Notes.find({_id: this.props.match.params.noteId}).fetch()
this.setState({ note: note[0] });
});
}
renderDocument(){
console.log(this.state.note);
return <p>Hi</p>
}
componentWillUnmount() {
this.tracker.stop();
}
render(){
return <div>{this.renderDocument()}</div>
}
}
I know that the reason it is returning undefined is because (correct me if I am wrong) the page is rendering the function before the the tracker could refresh the data. How would I get like some sort of callback when the tracker receives some data it will call the renderDocument function?
You're initializing your note state as an array but then you're setting it to a scalar later. You're also not checking to see if the subscription is ready which means that you end up trying to get the state when it is still empty. The tracker will run anytime a reactive data source inside it changes. This means you don't need a callback, you just add any code you want to run inside the tracker itself.
You also don't need a state variable for the document contents itself, your render function can just return a <div /> until the subscription becomes ready.
Note also that .findOne() is equivalent to .find().fetch()[0] - it returns a single document.
When you're searching on _id you can shorthand your query to .findOne(id) instead of .findOne({_id: id})
import React from "react";
import { Tracker } from "meteor/tracker";
import { Notes } from "../methods/methods";
export default class fullSize extends React.Component{
constructor(props){
super(props);
this.state = {
note: null
};
}
componentWillMount() {
const sub = Meteor.subscribe('notes');
this.tracker = Tracker.autorun(() => {
if (sub.ready) this.setState({ note: Notes.findOne(this.props.match.params.noteId) });
});
}
renderDocument(){
return this.state.note ? <p>Hi</p> : <div />;
}
componentWillUnmount() {
this.tracker.stop();
}
render(){
return <div>{this.renderDocument()}</div>
}
}

setState in reactjs inside success function not setting state

I am using a ParsePlatform as backend storage and reactjs as front end. I am able to get the parse data using Parse.Query but unable to use the returned values as I do not know how to set the state from the successfull fetching of results. I tried like this way inside componentDidMount()
import React from 'react'
import Parse from 'parse'
class ConferenceInfo extends React.Component {
state={
someData:null
}
componentDidMount(){
this.getConferenceInfo()
}
getConferenceInfo(){
var ConferenceListing = Parse.Object.extend("ConferenceListing");
var cl = new Parse.Query(ConferenceListing);
cl.get("8glBIjeRrC", {
success: function(cl) {
// The object was retrieved successfully.
alert(cl.get("someData")) //it works
//this.setState({someData:cl.get("someData")}) not working
},
error: function(object, error) {
// The object was not retrieved successfully.
// error is a Parse.Error with an error code and message.
}
});
}
render() {
return (
<div>
{this.state.someData} //no result
</div>
)
}
}
export default ConferenceInfo
This is because this is not in the same scope. You are calling it inside success property, function callback.
To get it working we need to bind this.
First Create constructor method.
constructor(props) {
super(props);
// Our new bind method ref
this.change = this.change.bind(this);
}
Then add new method change
change(obj) {
this.setState(obj);
}
Finally your success callback
success: function(cl) {
// ...
this.change({ someData: cl.get("someData") })
},

How to tell relay to update a dataset?

TL;DR:
After reading through various resources about React and Relay and by building a prototype app with react-relay, I came across a problem that I cannot solve without help. It's about how to tell a mutation which data to save.
I have a component that shows details of a User entity. It's loaded via a relay query. This component has an <input /> field with the name of the user and a save button that triggers a relay.commitUpdate(...) call. Whenever I change the name of the user in the input field and click on save, I can see that not the changed but the original name is send to the backend.
I am building an app with react-relay and I am working on a component that should display details about a user entity with the possibility to change these data and save it. It`s the detail part of the Master-Detail-Pattern.
Loading and changing of data works fine. But changes are not commited to the backend. For data updates I am trying to use relay mutations.
After clicking on save, I can see in the developer console in my chrome browser, that unaltered data is send to my backend. What do I miss? Here is what I have so far (Typescript code).
(Update: when placing break points into the onSaveClick() method of the component and the getVariables() method in the mutation, I see that in onSaveClick the property this.props.user contains made changes while this.props.user in getVariables() returns the initial data.)
export interface IUserDetailsProps { user: User }
export interface IUserDetailsStates { user: User }
class UserDetails extends React.Component<IUserDetailsProps, IUserDetailsStates> {
constructor(props: IUserDetailsProps) {
super(props);
this.state = { user: this.props.user };
}
public onFirstNameChange(event: any) {
let user = this.state.user;
this.state.user.firstName = event.target.value;
this.props.user.firstName = event.target.value; // is this needed?
this.setState({ user: user });
}
public onSaveClick() {
this.props.relay.commitUpdate(new UserMutation({ user: this.props.user }));
}
public render(): JSX.Element {
let user: User = this.props.user;
return <div>
<input type="text" value={user.firstName} onChange={this.onFirstNameChange.bind(this)} />
<button onClick={this.onSaveClick}>Save</button>
</div>;
}
}
class UserMutation extends Relay.Mutation {
public getMutation() {
return Relay.QL`mutation {saveUser}`;
}
public getVariables() {
return this.props.user;
}
public getFatQuery() {
return Relay.QL`
fragment on UserPayload {
user {
id,
firstName
}
}
`;
}
public getConfigs() {
return [{
type: "FIELDS_CHANGE",
fieldIDs: {
user: this.props.user.id
}
}];
}
static fragments = {
user: () => Relay.QL`
fragment on User {
id,
firstName
}
`
}
}
export default Relay.createContainer(UserDetails, {
fragments: {
user: () => Relay.QL`
fragment on User {
id,
firstName,
${UserMutation.getFragment("user")}
}
`
}
});
Solution attempt:
I changed the line for the input field of the user's first name to this:
<input type="text"
ref={(ref) => {
this.myTextBox = ref;
ref.value = this.props.user.firstName;
}} />
and removed the change event handler and removed all state code.
My click handler now looks like this:
public onSaveClick() {
let user = Object.assign({}, this.props.user);
user.firstName = this.myTextBox.value;
this.props.relay.commitUpdate(new UserMutation({user: user}));
}
and I can see, that the user object passed to the mutation now has the new value for firstName that I have changed in the text input. But again, relay sends the unchanged user to my backend. Here is a screenshot that shows my problem:

Resources