Connecting React Component with Redux Store - redux

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'

Related

React Native Firebase update information for all users in real time based on other users actions

Hi I'm currently trying to add users to a page using react native, redux, and firebase. When User 1 clicks join, they get added to the feed and likewise for other users. However, a problem I'm facing is when user 2 clicks join, they get added to the feed but don't get displayed on user 1's page unless the user 1 refocuses on the page after going away.
Here is my code for the page itself in react native
import React, { Component } from 'react';
import { Text, View, Button, TouchableOpacity, SafeAreaView, ScrollView, Image } from 'react-native';
import styles from '../styles.js'
import { connect } from 'react-redux'
import { FlatList } from 'react-native-gesture-handler';
import { FontAwesome5 } from '#expo/vector-icons';
import { Octicons } from '#expo/vector-icons';
import { FontAwesome } from '#expo/vector-icons';
import { addUser, removeUser, getLivingRoomUsers } from '../actions/livingRoomUser.js'
import { bindActionCreators } from 'redux'
class LivingRoom extends React.Component {
constructor(props) {
super(props);
this.state = {
inRoom: false,
isMuted: false
};
}
componentDidMount(){
this._unsubscribe = this.props.navigation.addListener('focus', () => {
this.props.getLivingRoomUsers()
});
}
joinRoom = () => {
this.props.addUser()
this.setState({ inRoom: true });
}
leaveRoom = () => {
this.props.removeUser(this.props.livingRoomUser)
this.setState({ inRoom: false });
}
...
render(){
return (
<View>
<SafeAreaView style={styles.livingRoomUserContainer}>
<FlatList
data={this.props.livingRoomUser.feed}
Here is my actions code for the redux portion:
export const addUser = () => {
return async (dispatch, getState) => {
try {
const { user } = getState()
const id = uuid.v4()
const livingRoomUser = {
id: id,
avatar: user.avatar,
username: user.username,
isMuted: false,
date: new Date().getTime(),
}
db.collection('livingroom').doc(id).set(livingRoomUser)
dispatch({
type: 'ADD_USER', payload: livingRoomUser
})
dispatch(getLivingRoomUsers())
} catch (e) {
alert(e)
}
}
}
export const removeUser = (livingRoomUser) => {
return async (dispatch, getState) => {
try {
db.collection('livingroom').doc(livingRoomUser.id).delete();
dispatch(getLivingRoomUsers())
//get living room users
} catch (e) {
alert(e)
}
}
}
export const getLivingRoomUsers = () => {
return async (dispatch, getState) => {
try {
const livingRoomUsers = await db.collection('livingroom').get()
let array = []
livingRoomUsers.forEach((livingRoomUser) => {
array.push(livingRoomUser.data())
})
dispatch({
type: 'GET_LIVING_ROOM_USERS', payload: orderBy(array, 'date', 'asc')
})
} catch (e) {
alert(e)
}
}
}
To summarize. I want the getUsers to be updated anytime someone adds/removes themself from the page. However, from my implementation currently actions only get updated for the current user and the feed only gets updated when the page is focused. How do I go about this?
use onSnapshot listener on the firestore then you can get the latest updates as the store change
export const getLivingRoomUsers = () => {
return async (dispatch, getState) => {
try {
db.collection('livingroom').onSnapshot(snapshot => {
let array = snapshot.docs.map(d => d.data());
dispatch({
type: 'GET_LIVING_ROOM_USERS',
payload: orderBy(array, 'date', 'asc'),
});
});
} catch (e) {
alert(e);
}
};
};

"Cannot read property of undefined" being thrown by react-apollo used with React-NextJS

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.

React Navigation Preventing Going back to loading screen, reset not working

I have a React Native application which I have implemented. Currently the app opens up on a loading screen which after mounting checks the firebase.auth().onAuthStateChanged(...) feature.
The app basically decides whether or not to got to the login screen or to main screen depending on whether or not the user is already authenticated.
It is implemented like this:
Main Navigator:
const MainNavigator = TabNavigator({
auth: {
screen: TabNavigator({
login: { screen: LoginScreen },
signup: { screen: SignupScreen }
}, {
initialRouteName: 'login',
tabBarPosition: 'top',
lazy: true,
animationEnabled: true,
swipeEnabled: true,
tabBarOptions: {
labelStyle: { fontSize: 12 },
showIcon: true,
iconStyle: { width: 30, height: 30 }
}
})
},
main: {
screen: StackNavigator({
notes: { screen: NotesScreen }
}, {
initialRouteName: 'notes'
})
},
loading: { screen: LoadingScreen }
}, {
initialRouteName: 'loading',
lazy: true,
swipeEnabled: false,
animationEnabled: false,
navigationOptions: {
tabBarVisible: false
}
});
Loading Screen:
class LoadingScreen extends Component {
componentDidMount() {
const { navigate } = this.props.navigation;
firebase.auth().onAuthStateChanged(user => {
if (user) {
navigate('main');
} else {
navigate('auth');
}
});
}
render() {
return (
<View style={styles.spinnerStyle}>
<Spinner size="large" />
</View>
);
}
}
const styles = {
spinnerStyle: {
flexDirection: 'row',
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
};
This works well except for one issue.
When I press the hardware back button for Android, it goes to the application loading screen which obvious is undesired. How do I prevent that?
EDIT:
I've tried the following and it didn't work either:
const resetAction = (routeName) => NavigationActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName })],
key: null
});
class LoadingScreen extends Component {
componentDidMount() {
const { dispatch } = this.props.navigation;
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.props.setUser(user);
dispatch(resetAction('main'));
} else {
dispatch(resetAction('auth'));
}
});
}
render() {
return (
<View style={styles.spinnerStyle}>
<Spinner size="large" />
</View>
);
}
}
use a switch navigator until the user logs in(loading and login page ) successsfully after that use a stack navigator(user homepage and otherpages which follow).
switchNavigator(loading, login, stackNavigator)
stackNavigator(user homepage,....)
Try a custom navigation component with custom back button support. Dont forget to add the reducer to yoru combine reducers function.
Create a navigation component:
import React, { Component } from 'react';
import { BackHandler } from 'react-native';
import { connect } from 'react-redux';
import { addNavigationHelpers } from 'react-navigation';
import MainNavigator from './MainNavigator';
class AppWithNavigationState extends Component {
componentDidMount () {
BackHandler.addEventListener('hardwareBackPress', () => {
this.props.dispatch({
type: 'Navigation/BACK'
});
return true;
});
}
componentWillUnmount () {
BackHandler.removeEventListener('hardwareBackPress');
}
render () {
return (
<MainNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}/>
);
}
}
export default connect((state) => ({ nav: state.nav }))(AppWithNavigationState);
Create a navigation reducer:
import { NavigationActions } from 'react-navigation';
import MainNavigator from './MainNavigator';
import { NAVIGATION_ON_SIGN_IN } from '../redux/actions/ActionTypes';
import { BackHandler } from 'react-native';
const initialState = MainNavigator.router.getStateForAction(MainNavigator.router.getActionForPathAndParams('loading'));
function appShouldClose (nextState) {
const { index, routes } = nextState;
return index === 0 || routes[1].routeName === 'auth';
}
export default (state = initialState, action) => {
const { router } = MainNavigator;
let nextState;
switch (action.type) {
case NavigationActions.BACK:
nextState = router.getStateForAction(action, state);
appShouldClose(nextState) && BackHandler.exitApp();
break;
default:
nextState = router.getStateForAction(action, state);
}
return nextState || state;
};
it is my solution :)
I have StageArea page. it is bridge between from login to timeline . User is not login then go to LoginPage. User is login then go to Timeline. User press back button then again go to TimeLine page not go to login page .( Sory for my english)
import React, { Component } from 'react';
import { View } from 'react-native';
import LoginForm from './LoginForm';
import Timeline from './Timeline';
import firebase from 'firebase';
import InitialPage from './InitialPage'
class StageArea extends Component {
state = {isLoggin:''};
componentWillMount(){
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ isLoggin:true})
}else {
this.setState({ isLoggin:false})
}
})
}
render() {
if(this.state.isLoggin)
{
return(<Timeline/>);
}
else if (this.state.isLoggin===false) {
return(<LoginForm/>);
}
}
}
export default StageArea;
Write the code below ,
static navigationOptions = {
header:null
};
Just before
render() {
return (
on the NotesScreen,There will not be any back button.

Async React-select with redux

I am trying to make an async react-select component with redux but somehow not able to get search results in the dropdown. Very new to this. Please help :)
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import Select from 'react-select';
import { fetchInstitutionsIfNeeded } from '../../actions/institutions';
class Signup extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null
};
this.getInstitutions = this.getInstitutions.bind(this);
this.onChange = this.onChange.bind(this);
}
onChange(input) {
this.setState({
value: input
});
}
getInstitutions(input) {
const { dispatch } = this.props;
if (!input) {
return Promise.resolve({ options: [] });
}
dispatch(fetchInstitutionsIfNeeded(input));
}
render() {
let options = this.props.options;
return (
<div>
<Select.Async
name="institute"
value={this.state.value}
autoload={false}
onChange={this.onChange}
loadOptions={this.getInstitutions}
/>
</div>
);
}
}
const mapStateToProps = (state) => ({
options: state.institutions.options
});
export default connect(mapStateToProps)(Signup);
Also options object is also properly formatted and is getting updated properly in redux store but not reflecting back in select async's dropdown.
Try this, we can also return from action but it breaks the whole idea of reducers.
// actions.js
export const getProducts = (input = '') => async (dispatch) => {
dispatch({
type: GET_PRODUCTS_PENDING,
payload: {},
});
try {
const response = await axios.get(`/api/v1/products/`);
dispatch({
type: GET_PRODUCTS_SUCCESS,
payload: response.data,
});
} catch (error) {
// handle errors
}
};
// components.jsx
class Signup extends PureComponent {
async getProductsForSelect(input) {
await this.props.getProducts(input);
return this.props.product.options;
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit}>
<AsyncSelect
isMulti
cacheOptions
defaultOptions
loadOptions={(e) => this.getProductsForSelect(e)}
/>
</form>
);
}
}

Displaying Meteor.user().username using Tracker React

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

Resources