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
Related
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();
}
I am in the process of cleaning up my fetching flags. By following the best practice, I am using a separate reducer to store all isFetching flags. In doing so I do not have to maintain multiple isFetchingFlags in my reducers.
Although I followed the explanation exactly, my isFetching flag does not jump from IsFetching: true (data currently being fetched) to IsFetching: false (data successfully fetched) in this new configuration. My fetching flag remains at IsFetching: false all the time. I have checked my code several times, but I cannot find my error.
Story Action:
// GET STORY
export const getStory = () => (dispatch, getState) => {
dispatch ({type: GET_STORY_REQUEST});
dispatch(showLoading());
axios.get( apiBase + "/story/retrieve/", tokenConfig(getState))
.then(res => {
dispatch({
type: GET_STORY_SUCCESS,
payload: res.data
});
dispatch(hideLoading());
})
.catch(err =>{
dispatch({
payload: returnErrors(err.response.data, err.response.status),
type: GET_STORY_FAILURE });
dispatch(hideLoading());
})
};
Loading Reducer
import {GET_STORY_SUCCESS,GET_STORY_REQUEST, GET_STORY_FAILURE} from "../actions/types.js";
const loadingReducer = (state = {}, action) => {
const { type } = action;
const matches = /(.*)_(REQUEST|SUCCESS|FAILURE)/.exec(type);
// not a *_REQUEST / *_SUCCESS / *_FAILURE actions, so we ignore them
if (!matches) return state;
const [, requestName, requestState] = matches;
return {
...state,
// Store whether a request is happening at the moment or not
// e.g. will be true when receiving GET_STORY_REQUEST
// and false when receiving GET_STORY_SUCCESS / GET_STORY_FAILURE
[requestName]: requestState === 'REQUEST',
};
Loading Selector
import _ from 'lodash';
export const createLoadingSelector = (actions) => (state) => {
// returns true only when all actions is not loading
return _(actions)
.some((action) => _.get(state, `api.loading.${action}`));
};
Story Component
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getStory} from '../../actions/story';
import { createLoadingSelector } from '../common/loading';
export class Story extends Component {
static propTypes = {
story: PropTypes.array.isRequired,
getStory: PropTypes.func.isRequired,
};
componentDidMount() {
this.props.getStory();
}
render() {
const { story } = this.props.story;
return (
<Fragment>
<h2>Stories</h2>
</Fragment>
);
}
}
const loadingSelector = createLoadingSelector(['GET_STORY']);
function mapStateToProps(state, ownProps) {
const story = state.story
const isFetching = loadingSelector(state)
console.log (isFetching)
console.log (story)
return { story, isFetching}
};
export default connect(
mapStateToProps,
{ getStory}
)(Story);
I'm happy for every clarification.
Are you using a middleware?
Remember that redux does not support asynchronous actions by default.
If not try to configure the redux-thunk middleware.
https://github.com/reduxjs/redux-thunk
I'm struggling to get get data from Firebase to load into a form wizard.
The basic example I am trying right now is just to display some of the firebase data given the collection id.
The current error I am receiving is that there isn't the correct workflow id being parsed into the redux action.
This is the react component
import React from "react";
import PropTypes from "prop-types";
//Redux
import { connect } from "react-redux";
import { getWorkflow } from "../../redux/actions/dataActions";
const example = '3ejAQxPoJ6Wsqsby01S6';
class EoT extends React.Component {
componentDidMount() {
this.props.getWorkflow('3ejAQxPoJ6Wsqsby01S6');
}
render() {
const { Workflow} = this.props.data;
return (<div>
<p>Crazy</p>
<h4>{Workflow.createdAt}</h4>
</div>
);
}
}
EoT.propTypes = {
getWorkflow: PropTypes.func.isRequired,
data: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
data: state.data
});
export default connect(mapStateToProps, {getWorkflow})(EoT);
I have another workflow piece of code that I am using but this is just to test I can load it at all,
this is my api query which works using postman.
// Get Workflow
export const getWorkflow = (WorkflowId) => dispatch => {
dispatch({ type: LOADING_DATA });
axios
.get(`/Workflow/${WorkflowId}`)
.then(res => {
dispatch({
type: SET_WORKFLOW,
payload: res.data
});
})
.catch(err => {
dispatch({
type: SET_WORKFLOW,
payload: []
});
});
};
This is the redux action I am using with that query
import {
SET_PROJECTS,
LOADING_DATA,
DELETE_PROJECT,
POST_PROJECT,
SET_PROJECT,
SET_CLAIMS,
SET_CLAIM,
DELETE_CLAIM,
POST_CLAIM,
SUBMIT_COMMENT,
SET_WORKFLOWS,
SET_WORKFLOW
} from "../types";
const initialStateProject = {
Projects: [],
Project: {},
Claims: [],
Claim: {},
Workflows: {},
Workflow: {},
loading: false
};
export default function(state = initialStateProject, action) {
switch (action.type) {
case LOADING_DATA:
return {
...state,
loading: true
};
case SET_WORKFLOWS:
return {
...state,
Workflows: action.payload,
loading: false
};
case SET_WORKFLOW:
return {
...state,
Workflow: action.payload
};
default:
return state;
}
}
Hope that helps explain the problem - I am currently wondering if I can place the id inside it somehow?
export const getWorkflow = () => dispatch => {
dispatch({ type: LOADING_DATA });
axios
.get("/Workflow/:WorkflowId")
:WorkflowId is not a valid reference here, you need to pass the WorkflowId in as a parameter to this thunk.
Ended up being that I had to call workflows and not workflow :D
I am working on a Redux project where I am trying to retrieve the values from an API server using axios library.After retrieving the values from the server,I am trying to save it in the application state.I am doing the API call in my Actions.The Actions.js file is as shown below:
import axios from 'axios';
export const FETCH_POSTS = 'fetch_posts';
let token = localStorage.token
if(!token)
token = localStorage.token = Math.random().toString(36).substr(-8)
const API = 'http://localhost:3001';
const headers = {
'Accept' : 'application/json',
'Authorization' :'token'
}
export function fetchPosts() {
const URL = `${API}/posts`;
const request = axios.get(URL,{headers});
return dispatch => {
request.then(({data}) => {
dispatch({
type : FETCH_POSTS,
payload : data
})
})
}
}
After retrieving the data,I am trying to console.log the object returned in my Component.My Component looks like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions';
import _ from 'lodash';
class PostsIndex extends Component {
componentDidMount() {
this.props.fetchPosts();
}
render() {
console.log(this.props.posts); //returns an empty object
return(
<div>
Posts
</div>
);
}
}
function mapStateToProps(state) {
return { posts: state.posts };
}
export default connect(mapStateToProps, { fetchPosts })(PostsIndex);
The object that I am trying to retrieve from the API server is given below:
const defaultData = {
"8xf0y6ziyjabvozdd253nd": {
id: '8xf0y6ziyjabvozdd253nd',
timestamp: 1467166872634,
title: 'Udacity is the best place to learn React',
body: 'Everyone says so after all.',
author: 'thingtwo',
category: 'react',
voteScore: 6,
deleted: false,
commentCount: 2
},
"6ni6ok3ym7mf1p33lnez": {
id: '6ni6ok3ym7mf1p33lnez',
timestamp: 1468479767190,
title: 'Learn Redux in 10 minutes!',
body: 'Just kidding. It takes more than 10 minutes to learn technology.',
author: 'thingone',
category: 'redux',
voteScore: -5,
deleted: false,
commentCount: 0
}
}
Now,I can see the object with the 2 values returned from the API server in my network response.But,if I try to console.log the value of the same posts(The result of the api call is saved as "posts" state in the store), it returns an empty object.What am I doing wrong, can anybody please help me with this?
Reducer files
index.js
import { combineReducers } from 'redux';
import PostReducer from './PostsReducer';
const rootReducer = combineReducers({
loading: false,
posts: PostReducer
});
export default rootReducer;
PostReducer.js
import _ from 'lodash';
import { FETCH_POSTS } from '../actions';
export default function(state = {}, action) {
switch (action.type) {
case FETCH_POSTS:
return _.mapKeys(action.payload.data, 'id');
default:
return state;
}
}
The thing is, you aren't rerendering PostsIndex, after fetchPosts finishes so you're always seeing an empty object. You cannot see the updated Redux store value unless you rerender your component. React Redux does not do this for you. Use state to trigger a rerender, such as a loading indicator:
componentDidMount() {
this.props.dispatch(fetchPosts())
.then(() => {
this.setState({
loading: false
});
});
}
render() {
return (
<div>
this.state.loading ?
/* loading UI could go here */
:
/* you can access this.props.posts here */
<div>
);
}
And I wouldn't use mapDispatchToProps in connect. Change your connect line to this:
export default connect(mapStateToProps)(PostsIndex);
This is because dispatch allows for promise chaining with Redux Thunk.
Once your post fetching finishes, the promise will resolve and state will be set (assuming Redux Thunk). This state setting will rerender the component and display the fetched posts.
I am new to react native + redux. I have an react native application where user first screen is login and after login am showing page of list of categories from server. To fetch list of categories need to pass authentication token, which we gets from login screen or either if he logged in previously then from AsyncStorage.
So before redering any component, I am creating store and manully dispatching fetchProfile() Action like this.
const store = createStore(reducer);
store.dispatch(fetchProfile());
So fetchProfile() try to reads profile data from AsyncStorage and dispatch action with data.
export function fetchProfile() {
return dispatch => {
AsyncStorage.getItem('#myapp:profile')
.then((profileString) => {
dispatch({
type: 'FETCH_PROFILE',
profile: profileString ? JSON.parse(profileString) : {}
})
})
}
}
so before store get populated, login page get rendered. So using react-redux's connect method I am subscribing to store changes and loading login page conditionally.
class MyApp extends React.Component {
render() {
if(this.props.profile)
if(this.props.profile.authentication_token)
retunr (<Home />);
else
return (<Login />);
else
return (<Loading />);
}
}
import { connect } from 'react-redux';
const mapStateToProps = (state) => {
return {
profile: state.profile
}
}
module.exports = connect(mapStateToProps, null)(MyApp);
So first 'Loading' component get rendered and when store is populated then either 'Login' or 'Home' component get rendered. So is it a correct flow? Or is there a way where I can get store populated first before any compnent render and instead of rendering 'Loading' component I can directly render 'Login' or 'Home' Component.
Verry common approach is to have 3 actions for an async operation
types.js
export const FETCH_PROFILE_REQUEST = 'FETCH_PROFILE_REQUEST';
export const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS';
export const FETCH_PROFILE_FAIL = 'FETCH_PROFILE_FAIL';
actions.js
import * as types from './types';
export function fetchProfile() {
return dispatch => {
dispatch({
type: types.FETCH_PROFILE_REQUEST
});
AsyncStorage.getItem('#myapp:profile')
.then((profileString) => {
dispatch({
type: types.FETCH_PROFILE_SUCCESS,
data: profileString ? JSON.parse(profileString) : {}
});
})
.catch(error => {
dispatch({
type: types.FETCH_PROFILE_ERROR,
error
});
});
};
}
reducer.js
import {combineReducers} from 'redux';
import * as types from './types';
const isFetching = (state = false, action) => {
switch (action.type) {
case types.FETCH_PROFILE_REQUEST:
return true;
case types.FETCH_PROFILE_SUCCESS:
case types.FETCH_PROFILE_FAIL:
return false;
default:
return state;
}
};
const data = (state = {}, action) => {
switch (action.type) {
case types.FETCH_PROFILE_SUCCESS:
return action.data;
}
return state;
};
export default combineReducers({
isFetching,
data
});
So you can get isFetching prop in your component and show/hide Loader component
You can load all your data during the splash screen and then load the others screens after that. I did it like this. Hope it helps
class Root extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
store: configureStore( async () => {
const user = this.state.store.getState().user || null;
if (categories && categories.list.length < 1) {
this.state.store.dispatch(categoriesAction());
}
this.setState({
isLoading: false
});
}, initialState)
};
}
render() {
if (this.state.isLoading) {
return <SplashScreen/>;
}
return (
<Provider store={this.state.store}>
<AppWithNavigationState />
</Provider>
);
}
}
Redux and Redux Persist (https://github.com/rt2zz/redux-persist) will solve your problem.
Don't make them complex.