How to make only a get request on compoment click - reactjs

I have decided to use Typescript for all my react applications but I am having a bit of a problem because of the learning curve. I have a problem when I click to users, it should make a get request on component did mount, but it continues endlessly, this is mostly a code from a template from the dotnet create react redux app and I took most of the code for granted.
import * as React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
import { ApplicationState } from '../../store';
import * as UsersStore from '../../store/Users';
type UsersProps =
UsersStore.UsersState &
typeof UsersStore.actionCreators &
RouteComponentProps<{}>;
class Users extends React.PureComponent<UsersProps> {
public componentDidMount() {
this.ensureDataFetched();
}
public componentDidUpdate() {
this.ensureDataFetched();
}
public render() {
return (
<React.Fragment>
<h1>Users</h1>
{** // render users **}
</React.Fragment>
);
}
private ensureDataFetched() {
const token = "web_token";
this.props.requestUsers(token);
}
};
export default connect(
(state: ApplicationState) => state.users,
UsersStore.actionCreators
)(Users as any);
And my: store, action, reducer
import { Action, Reducer } from 'redux';
import { AppThunkAction } from './';
import userService from '../services/userService';
import { GET_USERS, GET_USER } from '../constants';
// STATE
export interface UsersState {
isLoading: boolean;
users: User[];
user: User;
}
export interface User {
id: string;
name: string;
}
// ACTIONS
interface GetUserAction {
type: 'GET_USER';
payload: User;
}
interface GetUsersAction {
type: 'GET_USERS';
payload: User[];
}
type KnownAction = GetUserAction | GetUsersAction;
// ACTION CREATORS
export const actionCreators = {
requestUsers: (token: string): AppThunkAction<KnownAction> => async (dispatch, getState) => {
// Only load data if it's something we don't already have (and are not already loading)
const appState = getState();
if (appState && appState.users) {
try {
const users = await userService.getUsers(token);
dispatch({ type: GET_USERS, payload: users })
} catch (err) {
console.log('Bad request, please try loading again.')
}
}
}
};
// REDUCER
const unloadedState: UsersState = { users: [], isLoading: false, user: { id: "0", name: "" } };
export const reducer: Reducer<UsersState> = (state: UsersState | undefined, incomingAction: Action): UsersState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case GET_USERS:
if (state.users !== action.payload) {
return {
...state,
users: action.payload
};
}
break;
case GET_USER:
return {
users: state.users,
isLoading: false,
user: action.payload,
};
}
return state;
};
UPDATE: Added this check but now it does not update users state, my idea is to check whether the current state is not the same as the payload then it updates otherwise it will skip and break.
if (state.users !== action.payload) {
return {
...state,
users: action.payload
};
}
break;

Your componentDidUpdate() is the one causing infinite rendering issue
I can see that you already fetch the info in your componentDidMount(), so it's not necessary to fetch them over again.
First, after your component is rendered componentDidMount is invoked
Then your ensureDataFetched is fetched.
Your redux state is changed
Then your componentDidUpdate invoked due to that re-rendering
Your redux state is changed again.
Then your componentDidUpdate invoke all over again.
Infinite loop...
Just remove this block will end that endlessly rendering:
public componentDidUpdate() {
this.ensureDataFetched();
}

Related

How can i use this action function to get the user information?

I'm actually working on a small react app, i have an action to check if the current user exist in on the firestore collection 'users' based on the uid, anad then get the user’s profile information.
It works actually this action, but i can't use it in my profile component to display it !
That's the action file:
import 'firebase/firestore'
import firebase from 'firebase/app'
const getUser =()=>{
return (dispatch)=>{
firebase.auth().onAuthStateChanged(firebaseUser => {
if(firebaseUser){
firebase.firestore().collection("users").doc(firebaseUser.uid).get().then( doc => {
const { displayName } = doc.data()
//it works and it shows me on console the name i want
console.log("display name in action: ",displayName)
const currentUser = {
uid: firebaseUser.uid,
displayName
}
dispatch({
type:'GET_USER',
currentUser,
})
})
}
})
}
}
export default getUser ;
when i try to console log it in my profile file, it shows this error "typeError: undefined is not an object (evaluating 'this.props.getUser().currentUser')":
console.log("getting current user: ", this.props.getUser().currentUser )
I expect to display me the displayName but i got that error!
You actually looking for reducer. Action handler is not designed to return data to your component. Action idea is to store data to reducer.
Code below assumes that you have properly connected react-redux with your application.
src/actions/userAction.js
import 'firebase/firestore'
import firebase from 'firebase/app'
export const getUser = () => {
return (dispatch) => {
firebase.auth().onAuthStateChanged(firebaseUser => {
if (firebaseUser) {
firebase.firestore().collection("users").doc(firebaseUser.uid).get().then(doc => {
const {displayName} = doc.data();
const currentUser = {
uid: firebaseUser.uid,
displayName
};
dispatch({
type: 'GET_USER',
payload: currentUser
});
})
}
})
}
};
src/reducers/userReducer.js
const INITIAL_STATE = {
data: {},
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'GET_USER':
return {
...state,
data: action.payload
};
default:
return state;
}
};
src/reducers/index.js
import userReducer from "./userReducer";
export default {
user: userReducer
};
src/components/Example.js
import React from 'react';
import connect from "react-redux/es/connect/connect";
import {getUser} from "../actions/userAction";
class Example extends React.Component {
componentDidMount() {
this.props.getUser();
}
render() {
if (!Object.keys(this.props.user.data).length)
return <div>Loading user's data</div>;
return (
<div>
{ JSON.stringify(this.props.user.data) }
</div>
);
}
}
const mapStateToProps = (state) => {
return {
user: state.user
};
};
export default connect(mapStateToProps, {
getUser,
})(Example);

React Redux initialState fetch from Api

I am using JWT auth, when the user log in I store the token in the localstorage. How do I fetch the api with that token, so I can get the user details when the page
loads for the first time.
I'm already using React Thunk for the async requests but I don't know how to set the initialState with an async request. However is it okay to set the localstorage in the reducers?
You would want to do something like this in your action:
import axios from 'axios';
export const LOADING = "LOADING";
export const SUCCESS = "SUCCESS";
export const FAILURE = "FAILURE";
export const UPDATE = "UPDATE";
export const SUCCESSFUL_UPDATE = "SUCCESSFUL_UPDATE";
export const getSmurfs = () => dispatch => {
dispatch({ type: LOADING })
axios.get('http://localhost:3333/smurfs')
.then(res => dispatch({ type: SUCCESS, payload: res.data}))
.catch(err => dispatch({ type: FAILURE, payload: err}))
}
So you would start with a state of Loading which would change to Success or Failure depending on the response. Then in your reducer you would want to do something like:
import { LOADING, SUCCESS, FAILURE, UPDATE, SUCCESSFUL_UPDATE } from '../actions/index';
const initialState = {
smurfs: [],
loading: false,
error: "",
updateID: "",
clicked: false,
update: []
}
export default function reducer(state= initialState, action) {
switch(action.type) {
case LOADING:
return {
...state,
smurfs: [],
loading: true,
err: ''
}
case SUCCESS:
return {
...state,
smurfs: action.payload,
loading: false,
err: ''
}
Basically when it is successful it will turn off the loading and display your returned data
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getSmurfs, deleteSmurf, update } from '../actions/index';
import Smurfs from './smurfs';
import Form from './form';
import UpdateForm from './updateForm';
class SmurfsViewer extends Component {
componentDidMount() {
this.props.getSmurfs()
}
render() {
console.log(this.props.smurfs)
// if loading returns true then display loading smurfs..
if(this.props.loading) {
return (<h1>LOADING SMURFS....</h1>)
}
//if clicked resolves true then display the form to allow updating of the smurf that had its edit button clicked
let form;
if(this.props.clicked) {
form = <UpdateForm />
} else {
form = <Form />
}
return(
<div>
<Smurfs smurfs={this.props.smurfs} deleteSmurf={this.props.deleteSmurf} update={this.props.update}/>
{form}
</div>
)
}
}
const mstp = state => {
console.log("FROM VIEWER:", state)
return {
smurfs: state.smurfs,
loading: state.loading,
clicked: state.clicked
}
}
export default connect(mstp, { getSmurfs, deleteSmurf, update })(SmurfsViewer);
So you need to send the state from Redux through the mapStateToProps(mstp) and connect methods. Then you can use them in the component and it will update your redux state as needed. Then just refer to them as this.props.getSmurfs or something along those lines

Redux - mapDispatchToProps - TypeError: _this.props.setCurrentUserHandle is not a function

I am trying to get a simple react-redux app to work and I am running into a weird error that I can't figure out. I am trying to simply set my current user's first name and handle the store and one set function works and the other doesn't.
setCurrentUserFirstName - works
setCurrentUserHandle - doesn't
import React, { Component } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import store from '../../store';
var Utilities = require('../../../common/commonutilities.js');
var RestClient = require('../../../common/restClient.js');
//actions
import { setCurrentUserHandle, setCurrentUserFirstName } from '../../actions/userActions';
class Header extends Component {
constructor(props) {
super(props);
this.state = {};
RestClient.api.fetchGet('/getcurrentuser', (response) => {
if(response.success) {
this.setState({
isAuthenticated: true,
currentUser: response.currentUser
});
store.dispatch({
type: 'USER_DID_LOGIN',
userLoggedIn: true
});
//works fine
this.props.setCurrentUserFirstName(response.currentUser.firstName);
//doesn't work and throws the error: "TypeError: _this.props.setCurrentUserHandle is not a function"
this.props.setCurrentUserHandle(response.currentUser.handle);
}
},
(err) => {
console.log(err);
});
}
render() {
return (
{this.props.user.currentUserFirstName}, {this.props.user.currentUserHandle}
);
}
}
const mapStateToProps = function(store) {
return {
//user properties
user: store.userState
};
};
const mapDispatchToProps = (dispatch) => {
return{
setCurrentUserFirstName: (currentUserFirstName) =>{
dispatch( setCurrentUserFirstName(currentUserFirstName));
}
}
return{
setCurrentUserHandle: (currentUserHandle) =>{
dispatch( setCurrentUserHandle(currentUserHandle));
}
}
};
//connect it all
export default connect(mapStateToProps, mapDispatchToProps)(Header);
I have them as actions in the userActions.js file
export function setCurrentUserFirstName(currentUserFirstName){
return{
type: 'SET_CURRENT_USER_FIRST_NAME',
payload: currentUserFirstName
};
}
export function setCurrentUserHandle(currentUserHandle){
return{
type: 'SET_CURRENT_USER_HANDLE',
payload: currentUserHandle
};
}
And in the reducer
const initialUserState = {
user: {},
currentUserFirstName:[],
currentUserHandle:[]
};
// The User reducer
const userReducer = (state = initialUserState, action) => {
//using newState object to be immutable
let newState = state;
switch (action.type) {
case 'SET_CURRENT_USER_FIRST_NAME':
newState = {
...state,
currentUserFirstName: action.payload
};
break;
case 'SET_CURRENT_USER_HANDLE':
newState = {
...state,
currentUserHandle: action.payload
};
break;
break;
default:
break;
}
return newState;
};
export default userReducer;
What do I have incorrect?
You have 2 return statements in your mapDispatchToProps - the second one will never be reached. You can return a single object as follows:
const mapDispatchToProps = (dispatch) => {
return{
setCurrentUserFirstName: (currentUserFirstName) =>{
dispatch( setCurrentUserFirstName(currentUserFirstName));
},
setCurrentUserHandle: (currentUserHandle) =>{
dispatch( setCurrentUserHandle(currentUserHandle));
}
}
};
In addition to Tony's correct answer, I highly encourage that you use the "object shorthand" form of mapDispatch instead. You can pass an object full of action creators as the second argument to connect(), instead of an actual mapDispatch function. In your case, it'd look like:
import { setCurrentUserHandle, setCurrentUserFirstName } from '../../actions/userActions';
const actionCreators = { setCurrentUserHandle, setCurrentUserFirstName };
class Header extends Component {}
export default connect(mapStateToProps, actionCreators)(Header);

React Native: componentWillUpdate not called

I'm creating a React Native app using React Navigation and Redux.
class LoginScreen extends Component {
state = {
email: '',
password: '',
errors: {
email: '',
password: ''
}
}
onPressLogin() {
this.props.signIn(this.state.email, this.state.password);
}
componentWillUpdate(nextProps, nextState) {
console.log("component will update");
if (nextProps.signedIn) {
this.props.navigation.navigate('LoggedIn');
}
}
render() {
if (this.props.signedIn) {
this.props.navigation.navigate('LoggedIn');
}
return(<View>...</View);
this.props.signIn() is a Redux action, which for now just updates the state as such: { signedIn: true }. The following code is where I pass the Redux actions and state as props.
function mapStateToProps(state, props) {
return {
signedIn: state.authReducer.signedIn,
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
When the action is fired, the state updates as I would expect and render() is called. If I put the navigation code in the render() function everything works fine. To make the code cleaner, I want to move it into componentWillUpdate() but this function is not firing. The console log never gets printed to the console.
Here is my action and my reducer.
Action:
export const SIGN_IN_SUCCESS = 'SIGN_IN_SUCCESS';
export const SIGN_IN_FAIL = 'SIGN_IN_FAIL';
export function signIn(email, password) {
return (dispatch) => {
dispatch({ type: SIGN_IN_SUCCESS });
}
}
Reducer:
import { combineReducers } from 'redux';
import {
SIGN_IN_FAIL,
SIGN_IN_SUCCESS
} from '../actions/';
let authState = { signedIn: false, error: '' }
const authReducer = (state = authState, action) => {
switch(action.type) {
case SIGN_IN_SUCCESS:
return {...state, signedIn: true }
case SIGN_IN_FAIL:
return {...state, signedIn: false, error: action.error }
default:
return state;
}
}
const rootReducer = combineReducers({
authReducer
});
export default rootReducer;
Because you executed navigation right inside render() function:
render() {
if (this.props.signedIn) {
this.props.navigation.navigate('LoggedIn');
}
return(<View>...</View);
}
It must be:
render() {
return !this.props.signedIn && (<View>...</View);
}

React Redux Reducer triggered but not changing state

I am trying to set up app state with authenticated: true and user data.
Reducer gets to be triggered (I can see console.log) but it is returning initial state (isAuthenticated: false, user: {})
Thunk is working fine as far as I know
Props I am getting in component is {isAuthenticated: false, user{}}
I have done stuff like this before just like this so I am not sure why is this happening
import { AUTHENTICATED } from '../actions/types'
const initialState = {
isAuthenticated: false,
user: {}
}
export default function(state = initialState, action) {
switch (action.type) {
case AUTHENTICATED:
console.log(action.payload)
return {
...state,
isAuthenticated: true,
user: action.payload.user
}
default:
return state
}
}
action creator user.js
import axios from 'axios';
import history from '../history';
import config from '../config'
import { AUTHENTICATED } from './types';
export function authUser(token){
return function(dispatch){
const data = {"token": token}
axios.post(`${config.api_url}/authuser`, data)
.then((res) => {
dispatch({type: AUTHENTICATED, payload: res.data})
})
.catch((err) => console.log(err))
}
}
component dashboard.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import history from '../history';
import * as actions from '../actions/memberActions';
class Dashboard extends Component {
componentWillMount(){
const token = window.localStorage.getItem('token');
if(!token){
history.push('/')
}else{
this.props.authUser(token);
console.log(this.props.user);
}
};
render() {
return (
<div>
<h2>This will be a dashboard page</h2>
<p>There should be something here:{ this.props.authenticated }</p>
<h1>OK</h1>
</div>
)
}
}
function mapStateToProps(state){
return {
user: state.user
}
}
export default connect(mapStateToProps, actions)(Dashboard);
You're checking props.user in componentWillMount, which doesn't show your updates.
Instead check the state change in your render method, or in another life-cycle handler method like componentWillReceiveProps.
Your code should be something like this
export default function(state = initialState, action) {
switch (action.type) {
case AUTHENTICATED:
console.log(action.payload)
return state = {
...state,
isAuthenticated: true,
user: action.payload.user
}
default:
return state
}
}
From the looks of it, seems like your res.data object, in dispatch({type: AUTHENTICATED, payload: res.data}) does not have an user property.
So when you do user: action.payload.user you're basically saying user: undefined.
Please post your console.log(res.data) to see if this is the problem.

Resources