I have been trying to display the Username of a Logged in user using Tracker React.
I have removed the auto-publish package.
/Client/components/dashboard/sidebar.jsx
import React, {Component} from 'react'
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class Sidebar extends TrackerReact(Component) {
constructor() {
super();
this.state = {
subscription: {
users: Meteor.subscribe('users')
}
};
}
render() {
return(
<div>
<h1>{Meteor.user().username}</h1>
</div>
);
}
/Server/publications/userPublications
Meteor.publish("users", function () {
return Meteor.users.find();
});
I am getting a null object while console.log(Meteor.user()). However, I can see the current username using the Meteor DevTools for chrome.
What block of puzzle am I missing?
I doubt you can use Meteor.user(). username
Try this instead
<h1>{{currentUser}}</h1>
You can use createContainer in order to manage it.
import { Meteor } from 'meteor/meteor';
import React, { Component, PropTypes } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
export class Sidebar extends Component {
static propTypes = {
loggedIn: PropTypes.bool.isRequired,
loggingIn: PropTypes.bool.isRequired,
currentUser: PropTypes.shape({
username: PropTypes.string
})
}
render() {
const { loggingIn, loggedIn } = this.props;
return (
<div>
{(() => {
if (loggingIn) {
return <Loading />
} else if (loggedIn) {
// whatever you want here.
} else {
<h1>Please Log In</h1>
}
})()}
</div>
)
}
}
export default createContainer(() => ({
user: Meteor.user(),
loggedIn: !Meteor.userId(),
loggingIn: Meteor.loggingIn()
}), Sidebar);
A few details here:
Meteor.loggingIn() is a reactive variable that is run while the user is logging in. When this is happening, there will not be a user object.
You should create a reactive container
Related
My NextJS project has been giving me grief over Graph QL these days. I've been trying to implement an Apollo client solution to retrieve data from a remote GraphQL server into a custom component. But no matter which solution I try, I always end up with this error. Here's my current react-apollo implementation:
// /lib/with-apollo-client.js
import React from "react";
import Head from "next/head";
import { getDataFromTree } from "react-apollo";
import initApollo from "./init-apollo";
export default App => {
return class WithData extends React.Component {
static displayName = `WithData(${App.displayName})`;
static async getInitialProps(ctx) {
const { Component, router } = ctx;
const apollo = initApollo({});
ctx.ctx.apolloClient = apollo;
let appProps = {};
if (App.getInitialProps) {
appProps = await App.getInitialProps(ctx);
}
// Run all GraphQL queries in the component tree
// and extract the resulting data
if (!process.browser) {
try {
// Run all GraphQL queries
await getDataFromTree(
<App
{...appProps}
Component={Component}
router={router}
apolloClient={apollo}
/>
);
} catch (error) {
console.error("Error while running `getDataFromTree`", error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
// Extract query data from the Apollo store
const apolloState = apollo.cache.extract();
return {
...appProps,
apolloState
};
}
constructor(props) {
super(props);
this.apolloClient = initApollo(props.apolloState);
}
render() {
return <App {...this.props} apolloClient={this.apolloClient} />;
}
};
};
// /lib/init-apollo.js
import { ApolloClient, InMemoryCache, HttpLink } from 'apollo-boost'
import fetch from 'isomorphic-unfetch'
let apolloClient = null
// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
global.fetch = fetch
}
function create (initialState) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient({
connectToDevTools: process.browser,
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers`
}),
cache: new InMemoryCache().restore(initialState || {})
})
}
export default function initApollo (initialState) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (!process.browser) {
return create(initialState)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(initialState)
}
return apolloClient
}
The component I'm retrieving data into looks like this:
// /components/PostsList2.jsx
import { Query } from 'react-apollo'
import gql from 'graphql-tag'
export const allUsersQuery = gql`
query allUsers($first: Int!, $skip: Int!) {
allUsers(orderBy: createdAt_DESC, first: $first, skip: $skip) {
id
firstName
createdAt
}
_allUsersMeta {
count
}
}
`
export const allUsersQueryVars = {
skip: 0,
first: 10
}
export default function PostsList2 () {
return (
<Query query={allUsersQuery} variables={allUsersQueryVars}>
{({ loading, error, data: { allUsers, _allUsersMeta }, fetchMore }) => {
if (error) return <aside>Error loading users!</aside>
if (loading) return <div>Loading</div>
const areMorePosts = allUsers.length < _allUsersMeta.count
return (
<section>
<ul>
{allUsers.map((user, index) => (
<li key={user.id}>
<div>
<span>{index + 1}. </span>
<div>{user.firstName}</div>
</div>
</li>
))}
</ul>
{areMorePosts ? (
<button onClick={() => loadMorePosts(allUsers, fetchMore)}>
{' '}
{loading ? 'Loading...' : 'Show More'}{' '}
</button>
) : (
''
)}
</section>
)
}}
</Query>
)
}
function loadMorePosts (allUsers, fetchMore) {
fetchMore({
variables: {
skip: allUsers.length
},
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return previousResult
}
return Object.assign({}, previousResult, {
// Append the new users results to the old one
allUsers: [...previousResult.allUsers, ...fetchMoreResult.allUsers]
})
}
})
}
Since this is a NextJS project, there's also an _app.jsx that I've wrapped in a special provider component:
// /pages._app.jsx
/* eslint-disable max-len */
import '../static/styles/fonts.scss';
import '../static/styles/style.scss';
import '../static/styles/some.css';
import CssBaseline from '#material-ui/core/CssBaseline';
import { ThemeProvider } from '#material-ui/styles';
import jwt from 'jsonwebtoken';
import withRedux from 'next-redux-wrapper';
import App, {
Container,
} from 'next/app';
import Head from 'next/head';
import React from 'react';
import { Provider } from 'react-redux';
import makeStore from '../reducers';
import mainTheme from '../themes/main-theme';
import getSessIDFromCookies from '../utils/get-sessid-from-cookies';
import getLanguageFromCookies from '../utils/get-language-from-cookies';
import getUserTokenFromCookies from '../utils/get-user-token-from-cookies';
import removeFbHash from '../utils/remove-fb-hash';
import withApolloClient from '../lib/with-apollo-client'
import { ApolloProvider } from 'react-apollo'
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let userToken;
let sessID;
let language;
if (ctx.isServer) {
ctx.store.dispatch({ type: 'UPDATEIP', payload: ctx.req.headers['x-real-ip'] });
userToken = getUserTokenFromCookies(ctx.req);
sessID = getSessIDFromCookies(ctx.req);
language = getLanguageFromCookies(ctx.req);
const dictionary = require(`../dictionaries/${language}`);
ctx.store.dispatch({ type: 'SETLANGUAGE', payload: dictionary });
if(ctx.res) {
if(ctx.res.locals) {
if(!ctx.res.locals.authenticated) {
userToken = null;
sessID = null;
}
}
}
if (userToken && sessID) { // TBD: validate integrity of sessID
const userInfo = jwt.verify(userToken, process.env.JWT_SECRET);
ctx.store.dispatch({ type: 'ADDUSERINFO', payload: userInfo });
}
ctx.store.dispatch({ type: 'ADDSESSION', payload: sessID }); // component will be able to read from store's state when rendered
}
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
return { pageProps };
}
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentNode.removeChild(jssStyles);
}
// Register serviceWorker
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceWorker.js'); }
// Handle FB's ugly redirect URL hash
removeFbHash(window, document);
}
render() {
const { Component, pageProps, store, apolloClient } = this.props;
return (
<Container>
<Head>
// redacted for brevity
</Head>
<ThemeProvider theme={mainTheme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<ApolloProvider client={apolloClient}>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</ApolloProvider>
</ThemeProvider>
</Container>
);
}
}
export default withApolloClient(withRedux(makeStore)(MyApp));
So with this setup, when I compile and run my app, it throws the following:
TypeError: Cannot read property 'allUsers' of undefined
I'm really lost! The repo is up at https://github.com/amitschandillia/proost/tree/master/web.
Very basic simple GET example for react-redux
I have a "MockAPI" which simulates a GET request to an API like so:
const dashboards = [
{
"Id":1,
"title":"Overview"
},
{
"Id":2,
"title":"Overview"
},
{
"Id":3,
"title":"Overview"
},
{
"Id":4,
"title":"Overview"
}
];
class DashboardApi {
static getAllDashboards() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(Object.assign([], dashboards));
}, delay);
});
}
}
I am trying to develop in a react-redux flow of dispatching an action via a button click and then updating the component via the redux store.
Here is my component code:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import * as dashboardActions from '../../actions/dashboardActions';
class HomePage extends React.Component {
constructor(props) {
super(props);
this.loadDashboards = this.loadDashboards.bind(this);
}
loadDashboards() {
this.props.dispatch(dashboardActions.loadDashboards());
}
dashboardItem(dashboard, index) {
return <p key={index}>{dashboard.title}</p>;
}
render() {
return (
<div>
<h1>
Hello World!
<button onClick={this.loadDashboards}>load</button>
</h1>
{this.props.dashboards.map(this.dashboardItem)}
</div>
);
}
}
HomePage.propTypes = {
dashboards: PropTypes.array.isRequired,
dispatch: PropTypes.func.isRequired
};
function mapStateToProps(state) {
return {
dashboards: state.dashboards
};
}
export default connect(mapStateToProps)(HomePage);
And here is my dashboardActions.js:
import * as types from './actionTypes';
import dashboardApi from '../mockApi/mockDashboardApi';
export function loadDashboardsSuccess(dashboards) {
return { type: types.LOAD_DASHBOARDS_SUCCESS, dashboards };
}
export function loadDashboards() {
return dispatch => {
return dashboardApi
.getAllDashboards()
.then(dashboards => {
dispatch(loadDashboardsSuccess(dashboards));
});
};
}
And here is my reducer:
import initialState from './initialState';
import * as types from '../actions/actionTypes';
export default function dashboardReducer(state = initialState.dashboards, action) {
switch(action.types) {
case types.LOAD_DASHBOARDS_SUCCESS:
return action.dashboards;
default:
return state;
}
}
I am trying to get the onClick to load in the dashboards array and to render as <p> tags simply displaying the title value. Unfortunately it is not happening.
I see that the LOAD_DASHBOARDS_SUCCESS action is getting loaded, but I see that the dashboards property in the store is still an empty array instead of showing the returned data...
What am I missing here?
You've got a typo in your reducer. switch(action.types) should be switch(action.type) with no 's'
I'm using React Native 0.36 and Firebase. How do I make the homepage of the app change if a user is logged in. I tried an if statement in renderScene but it gives this error:
index.ios.js
import React, { Component } from 'react';
import {
AppRegistry,
Navigator,
} from 'react-native';
import firebase from 'firebase';
import Login from './Login';
import Home from './Home';
function renderScene(route, navigator) {
return <route.component route={route} navigator={navigator} />;
}
export default class Snip extends Component {
renderScene(route, navigator){
return <route.component navigator={navigator} />
}
render() {
var that = this;
firebase.auth().onAuthStateChanged(function(user) {
if(user) {
return (
<Navigator
initialRoute={{component: Home}}
renderScene={that.renderScene}/>
)
} else{
return (
<Navigator
initialRoute={{component: Login}}
renderScene={that.renderScene}/>
)
}
});
}
}
There are two things that are wrong with the code from the question.
You cannot return a react element inside of a firebase callback
Firebase takes a moment to return if the user exists. So I have to create a loading state that get updated. When firebase is done.
index.ios.js
import React, { Component } from 'react';
import {
AppRegistry,
Navigator,
View,
Text,
} from 'react-native';
import firebase from 'firebase';
import Login from './Login';
import Home from './Home';
function renderScene(route, navigator) {
return <route.component route={route} navigator={navigator} />;
}
export default class Snip extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
loading: false
};
}
componentWillMount() {
var that = this;
firebase.auth().onAuthStateChanged(function(user) {
if(user) {
that.setState({
user: user,
loading: false
})
}else{
that.setState({
loading: false
})
}
});
}
renderScene(route, navigator){
return <route.component navigator={navigator} />
}
render() {
if(this.state.loading){
return <View><Text>loading</Text></View>;
}else if(this.state.user){
return(
<Navigator
initialRoute={{component: Home}}
renderScene={this.renderScene}/>
)
}else{
return (
<Navigator
initialRoute={{component: Login}}
renderScene={this.renderScene}/>
)
}
}
}
I'm using Meteor/React and I'm trying to display all the users in the system:
I have: /imports/api/users.js
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
if (Meteor.isServer) {
// This code only runs on the server
// Only publish tasks that are public or belong to the current user
Meteor.publish('allUsers', function () {
return Meteor.users.find({}, {fields: {"emails.address": 1}});
});
}
and /imports/ui/App.jsx
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
class App extends Component {
constructor() {
super();
this.state = {
subscription: {
users: Meteor.subscribe('allUsers')
}
}
}
componentWillUnmount() {
this.state.subscription.users.stop();
}
render() {
let users = this.props.users;
console.log(users);
return (<div>
<h1>GoArc User Manager</h1>
<div>
{users.map((user)=>{
if ('emails' in user ) {
email = user.emails[0].address;
} else {
email = '?'
}
return <div key={user._id}>{user._id} - {email}</div>
})
}
</div>
</div>)
}
}
export default createContainer(() => {
return {
users: Meteor.users.find({ }).fetch(),
};
}, App);
But it still show only the current login user.
If I set autopublish/insecure on then the code is working correct.
What is the correct way to publish all users to the client with Meteor/React?
Another related issue is that even when I set autopublish/insecure on still the email.address field appear only for the current user - even though I published this field:
return Meteor.users.find({}, {fields: {"emails.address": 1}});
The code is correct but I forgot to add import '../imports/api/users.js';
in /server/main.js
Redux action changePictogramsKeyword is not being fired.
This is the file where I define my action and reducer (redux/module/keyword.js):
export const CHANGE_PICTOGRAMS_KEYWORD = 'CHANGE_PICTOGRAMS_KEYWORD'
export function changePictogramsKeyword (keyword) {
return {
type: CHANGE_PICTOGRAMS_KEYWORD,
keyword
}
}
// Updates error message to notify about the failed fetches.
export default function pictogramsKeyword (state = '', action) {
switch (action.type) {
case CHANGE_PICTOGRAMS_KEYWORD:
return action.keyword
default:
return state
}
}
My root reducer:
import { combineReducers } from 'redux'
import { routerReducer as router } from 'react-router-redux'
import locale from './modules/locale'
import errorMessage from './modules/error'
import pictogramsKeyword from './modules/keyword'
export default combineReducers({
locale,
router,
pictogramsKeyword,
errorMessage
})
So with the devTools I can check that my initialState is as I expected from the rootReducer:
locale:"en"
router:{} 1 key
pictogramsKeyword:""
errorMessage:null
This is the code of the view where I connect to Redux Store. Component SearchBox is in charge of firing the action changePictogramsKeyword:
import React, {Component, PropTypes} from 'react'
import SearchBox from 'components/SearchBox.js'
import { connect } from 'react-redux'
import { changePictogramsKeyword } from 'redux/modules/keyword'
class SearchPictogramsView extends Component {
handleDismissClick (e) {
this.props.resetErrorMessage()
e.preventDefault()
}
render () {
const { children, inputValue } = this.props
return (
<div>
<SearchBox value={inputValue} onChange={changePictogramsKeyword} />
{children}
</div>
)
}
}
SearchPictogramsView.propTypes = {
inputValue: PropTypes.string.isRequired,
children: PropTypes.node
}
function mapStateToProps (state, ownProps) {
return {
errorMessage: state.errorMessage,
inputValue: state.pictogramsKeyword
}
}
export default connect(mapStateToProps, {
resetErrorMessage, changePictogramsKeyword
})(SearchPictogramsView)
This is the code of the SearchBox component. AutoComplete is a material-ui component. onUpdateInput method gets fired everytime I press a key, however changePictogramsKeyword is not being fired (i see nothing through the dev tools)
import React, {Component, PropTypes} from 'react'
import AutoComplete from 'material-ui/lib/auto-complete'
import RaisedButton from 'material-ui/lib/raised-button'
class SearchBox extends Component {
constructor (props) {
super(props)
this.handleUpdateInput = this.handleUpdateInput.bind(this)
}
handleUpdateInput = (t) => {
console.log(t)
this.props.onChange(t)
}
render () {
return (
<div>
<AutoComplete onUpdateInput={this.handleUpdateInput} searchText={this.props.value} />
</div>
)
}
}
SearchBox.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
}
export default SearchBox
Right now, your action only gets called, but not dispatched because you're not mapping the actions correctly in the connect() call. (see the official documentation for more information)
In your SearchPictogramsView, change the mapDispatchToProps function of the connect() call to return an object with the wrapped functions:
export default connect(mapStateToProps, (dispatch) => {
return {
resetErrorMessage: () => dispatch(resetErrorMessage()),
changePictogramsKeyword: () => dispatch(changePictogramsKeyword())
};
})(SearchPictogramsView)
You can clean it up by making mapDispatchToProps its own function too:
function mapDispatchToProps(dispatch) {
return {
resetErrorMessage: () => dispatch(resetErrorMessage()),
changePictogramsKeyword: () => dispatch(changePictogramsKeyword())
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchPictogramsView)
Let me know if that works!
It was really in the docs:
If an object is passed, each function inside it will be assumed to be
a Redux action creator. An object with the same function names, but
with every action creator wrapped into a dispatch call so they may be
invoked directly, will be merged into the component’s props
When I wrote:
<SearchBox value={inputValue} onChange={changePictogramsKeyword} />
Now is:
<SearchBox value={inputValue} onChange={this.props.changePictogramsKeyword} />
So I really call the dispatch of the action and not just the action!