I have a react component in a Redux enabled application that starts by loading a list of ID's in a 2D array. (Each "page" is represented by an element of the outer array [1rst dimension])
Here is the component:
import React, { Component, Fragment } from "react";
import { loadInsiderPage, loadInsiderInfo } from "../../actions/insider";
import { connect } from "react-redux";
import IndividualInsider from "./individual";
import Paginate from "../common/paginate";
class InsiderList extends Component {
componentDidMount() {
if (this.props.insiderIds.length > 0) {
this.props.loadInsiderPage(this.props.insiderIds[0]);
} else {
this.props.loadInsiderInfo();
}
}
render() {
let { insiderIds, insiders } = this.props;
let insiderFormat = insiders.map(x => {
return <IndividualInsider key={x._id} insider={x} />;
});
return (
<Fragment>
<div className="container">
<Paginate
pages={insiderIds}
changePage={this.props.loadInsiderPage}
/>
{insiderFormat}
</div>
</Fragment>
);
}
}
export default connect(
null,
{ loadInsiderPage, loadInsiderInfo }
)(InsiderList);
This component will load the ID list if it's not filled by running the loadInsiderInfo() action, and if the ID list is not empty, it will trigger the page to be populated by running the loadInsiderPage() action which takes in a page from the ID list.
How can I have this trigger properly after the ID list has been loaded?
I was thinking I could do it in componentWillReceiveProps() but I'm not sure where to go with the nextProps property.
My actions are as follows:
export const loadInsiderInfo = () => dispatch => {
Axios.get("insider/list/pages/25")
.then(list => {
dispatch({ type: LOAD_INSIDER_LIST, payload: list.data });
})
.catch(err => dispatch({ type: GET_ERRORS, payload: err }));
};
export const loadInsiderPage = page => dispatch => {
console.log(page);
Axios.post("insider/page", { page })
.then(res => dispatch({ type: LOAD_INSIDER_PAGE, payload: res.data }))
.catch(err => dispatch({ type: GET_ERRORS, payload: err }));
};
Both simply grab data from the API and load it into the reducer.
The big issue that I'm coming across is that the Component will sometimes have props passed that keep the loadInsiderPage action from being called with a page object passed in.
In your action creator loadInsiderInfo() you can accept a param for the current page ID. Now when the Info is loaded, within this action creator you can dispatch another action by calling loadInsiderPage(id) in it. This way your page info is loaded for the first time by the insider info action creator itself.
Something like this:
export const loadInsiderInfo = (id) => dispatch => {
Axios.get("insider/list/pages/25")
.then(list => {
dispatch({ type: LOAD_INSIDER_LIST, payload: list.data });
if(<your-data-loaded>){
loadInsiderPage(id)(dispatch);
}
})
.catch(err => dispatch({ type: GET_ERRORS, payload: err }));
};
Now only call loadInsiderInfo(id) once, when there is no info loaded yet. For every other time, directly dispatch the loadInsiderPage(id) action instead. This way you handle every case, after the insider info data has been loaded.
Related
I'm working on a React project and I reuse a fetchAPIcall action since I make 3 different initial API calls, and further, I plan on using more to add and edit my Items.
So to have control over the correct order of the API call I tried using a flag at the end, being a state of the component.
And since I am using many API calls, to add some Items to favorites and be removed quickly by a like button, I'd like to know what is the best practice when using many async functions or API calls?
I can think of only 1) using flags, and 2) having the API call-actions separate for each, but in my case that would be a lot of code (get user)(get, add, delete FavList)(get Items)(add, edit remove Item).
By the way, the API is mine, made it in rails.
Here are the main code&files for my issue:
This is from my GetItemsNFavlist Component, this is where I load all the info of items and favList items. I made it into a component that I call because I thought it was a good idea so when I add an Item to the Favorites List I can just call this component to update my FavoritesList (but that 'updating' part isn't working great just yet, I'm having to go back to the User and again to the Fav List to see the update or even logout and in again to see the change).
Here I call the action "fetchAPIcall" and I check the status and response data with the "fetchCall" store object. Also here I do 2 API calls, 1) to get all the Items and 2) to get the FavoritesList for the User:
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import * as MyActions from '../actions';
const GetItemsNFavlist = props => {
const {
actions, items, fetchCall, favList, user,
} = props;
const [apiFlag, setApiFlag] = useState({ itm: false, fvl: false });
const itemsUrl = 'https://findmyitem-api.herokuapp.com/items';
const favListUrl = `https://findmyitem-api.herokuapp.com/users/${user.id}/favorites_lists`;
useEffect(() => { // #1
if (!apiFlag.itm && !apiFlag.fvl) actions.fetchAPIcall(itemsUrl, 'get', {});
}, []);
useEffect(() => {
if (!fetchCall.apiData && items[0]) {
actions.fetchAPIcall(favListUrl, 'get', {});
setApiFlag({ itm: true, fvl: false });
}
}, [items]);
useEffect(() => {
if (fetchCall.apiData && !items[0] && !favList[0]) {
actions.setItems(fetchCall.apiData);
actions.fetchAPIreset();
}
if (apiFlag.itm && fetchCall.apiData && !favList[0]) actions.setFavList(fetchCall.apiData);
});
useEffect(() => {
if (favList[0]) {
actions.fetchAPIreset();
setApiFlag({ itm: true, fvl: true });
}
}, [favList]);
return (<> </>);
};
GetItemsNFavlist.propTypes = {
user: PropTypes.objectOf(PropTypes.any).isRequired,
actions: PropTypes.objectOf(PropTypes.any).isRequired,
items: PropTypes.arrayOf(PropTypes.any).isRequired,
favList: PropTypes.arrayOf(PropTypes.any).isRequired,
fetchCall: PropTypes.objectOf(PropTypes.any).isRequired,
};
const mapStateToProps = ({
user, items, fetchCall, favList,
}) => ({
user, items, fetchCall, favList,
});
function mapActionsToProps(dispatch) {
return {
actions: bindActionCreators({ ...MyActions }, dispatch),
};
}
export default connect(mapStateToProps, mapActionsToProps)(GetItemsNFavlist);
And these are my actions (actions/index.js), where I have the API call function:
import axios from 'axios';
const addUsername = username => ({
type: 'SET_NAME',
username,
});
const setUserInfo = user => ({
type: 'SET_USER',
user,
});
const setItems = items => ({
type: 'SET_ITEMS',
items,
});
const setFavList = favList => ({
type: 'SET_FAVLIST',
favList,
});
const fetchAPIbegin = callHeader => ({
type: 'FETCH_API_BEGIN',
callHeader,
});
const fetchAPIsuccess = payload => ({
type: 'FETCH_API_SUCCESS',
payload,
});
const fetchAPIfailure = error => ({
type: 'FETCH_API_FAILURE',
payload: error,
});
const fetchAPIsuccesResp = payload => ({
type: 'FETCH_API_SUCCESS_RESP',
payload,
});
function handleErrors(response) {
if (!response.ok && response.error) { throw Error(JSON.stringify(response)); }
return response;
}
function fetchAPIcall(url, restAct, options) {
return dispatch => {
dispatch(fetchAPIbegin(url, options));
setTimeout(() => axios[restAct](url, options)
.then(handleErrors)
.then(rsp => {
dispatch(fetchAPIsuccesResp(rsp));
return rsp;
})
.then(resp => resp.data)
.then(jsonResp => dispatch(fetchAPIsuccess(jsonResp)))
.catch(err => dispatch(fetchAPIfailure(`${err}`))), 1000);
};
}
const fetchAPIreset = () => ({ type: 'FETCH_API_RESET' });
export {
addUsername,
setUserInfo,
setItems,
setFavList,
fetchAPIcall,
fetchAPIbegin,
fetchAPIsuccess,
fetchAPIfailure,
fetchAPIreset,
fetchAPIsuccesResp,
};
And Just in case, this is the link to my repo: find-my-item repo.
Thanks in advance!!
Best regards
I'm learning React and Redux. And I may have a really basic question.
I want to get a single story from my backend using the Redux function mapStateToProps (#1). So I wrote the function getSingleStory which takes the id as argument and returns the story data (#2). When I log the response data of the getSingleStory in the console, it shows me the correct story fetched from the backend (#3):
However, if the console logs the story array in my component (#4), it outputs all stories from my database, not just the single story I wanted to fetch (see picture). If I want to display 'Story.title', in my render function of course it does not work.
If someone could explain to me why in the response data the single story is included and in the const story = this.props.story; all stories suddenly appear, that would help me a lot.
export class StoryDetails extends Component {
componentDidMount() { // #2
this.props.getSingleStory(this.props.match.params.id);
}
render() {
const story = this.props.story;
console.log (story); // #4
return (
<div>
<h2>{story.title}</h2>
</div>
);
}
}
const mapStateToProps = state => ({story: state.story}); //#1
export default connect(
mapStateToProps,
{ getSingleStory, deleteStory}
)(StoryDetails);
Action
// GET SINGLE STORY
export const getSingleStory = id => (dispatch, getState) => {
return new Promise((resolve, reject) => {
axios.get( apiBase + `/story/${id}/`, tokenConfig(getState))
.then(res => {
dispatch({
type: GET_SINGLE_STORY,
story: res.data
}, console.log (res.data)); //#3
resolve(res);
})
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status));
reject(err);
});
});
};
Reducer
import { GET_SINGLE_STORY } from "../actions/types.js";
export default function (state = {}, action) {
switch (action.type) {
case GET_SINGLE_STORY:
return action.story;
default:
return state;
}
};
Many Thanks in advance!
I am trying to print out user related items only.
So i am try to get items by requesting data to user id /api/items/:userid
I am using redux store
my server side code is like this
router.get("/:userid",(req, res) => {
// Item.find({ "owner.ownerName": `${req.params.userid}`})
Item.find({ "owner.id": `${req.params.userid}`})
.sort({
date: -1,
})
.then((items) => res.json(items));
console.log(req.user)
});
The problem is my front end request.
I don't know how to get user id inside ITEMACTION.
import {
GET_ITEMS,
ADD_ITEM,
DELETE_ITEM,
ITEMS_LOADING,
UPDATE_ITEM,
SUBSTRACT_ITEM,
} from "../actions/types";
import { tokenConfig } from "../actions/authActions";
import { returnErrors } from "../actions/errorActions";
import Axios from "axios";
export const getItems = () => (dispatch) => {
// will hit reducer
dispatch(setItemsLoading());
Axios.get("/api/items/")
.then((res) =>
dispatch({
type: GET_ITEMS,
payload: res.data,
})
)
.catch((err) => {
dispatch(returnErrors(err.response.data, err.response.status));
});
};
I actually tried to get user id from the redux store.
import store from '../store';
and inside getItems
store.getState().auth.user._id
the problem is that when i console.log in getItems the user id is always return null except first time after login. But when i look in redux dev tool. The user id is available
how can i get the userid
Hey you can get the getState as a second argument in the inner function along with the dispatch, using that you can access the updated state in an action.
Fixed Code:
export const getItems = () => (dispatch, getState) => {
// will hit reducer
const userId = getState().auth.user._id;
console.log(userId) // should output the updated data
dispatch(setItemsLoading());
Axios.get("/api/items/")
.then((res) =>
dispatch({
type: GET_ITEMS,
payload: res.data,
})
)
.catch((err) => {
dispatch(returnErrors(err.response.data, err.response.status));
});
};
store.getState doesn't return updated state, in order to get the updated state using store.getState() you need to subscribe to the state change.
const unsubscribe = store.subscribe(() => {
// logs the state data everytime an action is dispatched.
console.log("from listener: ", store.getState());
})
Details here
I'm trying to pass data from my database to a page in my react project. The database stores the user data and the data is called with validateCookie() function. I'm getting data from the validateCookie function but I can't seem to get the data out of the function to the main page so I can use it to update the user's state and calendar and return that to update their information in the database.
The setState is not sending data to the page state. I've tried so much but I'm still new to react so I'm a bit out of my league
import ScheduleSelector from 'react-schedule-selector'
import React, { Component } from 'react';
import Moment from 'moment';
import { Row, Col, Button } from 'react-bootstrap';
import API from '../../utils/API';
class Availability extends Component {
constructor(props) {
super(props);
this.state = {
user: [],
email: "",
calendar: [],
schedule: [],
}
// this.handleInputChange = this.handleInputChange.bind(this);
// this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.validateCookie();
console.log(this.state.user); // coming back empty because validate cookie is not passing data upstream
}
handleSubmit = (event) => {
event.preventDefault();
// let schedule = this.state.schedule;
// // alert("Your availability has been submitted successfully!");
// let ISOschedule = this.state.schedule.map(date => Moment(date).toISOString());
// let newCalendar = this.state.schedule
console.log(this.state.user);
API.updateAvailability(
this.state.user.email,
this.state.user.calendar,
this.state.user.schedule)
.then(r => {
console.log(r);
}).catch(e => {
console.log(e);
})
}
handleChange = newSchedule => {
this.setState({ schedule: newSchedule.map(date => Moment(date).toISOString()) })
}
validateCookie() {
API.validateCookie()
.then(res => res.json())
.then(res => {this.setState({ user: res})})
.then(res => {
console.log(this.state) // coming back with loading data aka empty
console.log(this.state.user) // coming back with all appropriate data
})
.catch(err => console.log(err));
console.log(this.state.user) // coming back empty
}
render() {
return (
<div>
<form ref="form" onSubmit={this.handleSubmit}>
<ScheduleSelector
selection={this.state.schedule}
numDays={7}
minTime={0}
maxTime={23}
onChange={this.handleChange}
/>
<Row>
<Col>
<Button type="submit" className="float-right">Submit Availability</Button>
</Col>
</Row>
</form>
</div>
)
}
}
export default Availability;
I think the problem is that in your validateCookie method, you are expecting the state to change as soon as you call the setState function. It is important to know that setState() does not immediately mutate this.state but creates a pending state transition.
Refer to this answer for more information.
One solution could be to check when this.state actually gets updated before you render anything in your render function.
Just like Swanky said, the setState() doesn't update immediately and you can listen for state change and re-render the UI. I have done some cleaning up to your setState below;
validateCookie = () => {
API.validateCookie()
.then(res => res.json())
.then(res => {
this.setState({...this.state, user: res.user})
console.log(this.state.user);
})
.catch(err => console.log(err));
}
How to properly redirect user using the response of the api call in redux? I need the resp after axios's then but I got undefined, although I've returned the thunk in my action
//jobForm.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createJob } from '~/actions/jobAction'
import { getUserId } from '~/utils'
import moment from 'moment'
#connect(state=>state.job,{createJob})
class Form extends Component {
handleSubmitForm = () => {
this.props.createJob({formData})
.then(resp => console.log(resp)) //undefined?)
}
//etc..
}
export default Form
//action
export function createJob(params) {
return dispatch=>{
dispatch({type: CREATING_JOB})
return axios.post(`/job/create`, {...params})
.then(res=>{
if(res.status===200 && res.data.status===1){
dispatch({
type: CREATE_JOB,
payload: res.data.data
})
}
})
.catch(res => {
dispatch(errorMsg(res.data.msg))
})
}
}
I can pass my payload to reducer but I need a response's id to redirect the user to a created job page.
You're not returning anything after processing the API call, which is why the promise resolves to "undefined". For the promise to resolve with data, you'll need to return the id after dispatching the action. See below.
export function createJob(params) {
return dispatch=>{
dispatch({type: CREATING_JOB})
return axios.post(`/job/create`, {...params})
.then(res=>{
if(res.status===200 && res.data.status===1){
dispatch({
type: CREATE_JOB,
payload: res.data.data
});
// RETURN ID AFTER DISPATCHING ACTION
return res.data.data
}
})
.catch(res => {
dispatch(errorMsg(res.data.msg))
})
}
}
An alternative approach, that is arguably more inline with the flux one-way data flow paradigm would be to perform the redirect based on a change in the redux state rather than completion of the action.
You could use componentWillReceiveProps to determine if the new job has been created, if so, redirect
componentWillReceiveProps(nextProps) {
// use nextProps to determine if the new job has been added
// to the job state
// ...
const isNewJobAdded = nextProps.job.includes(...)
if (isNewJobAdded) {
// perform redirect
...
}
}