Using one event to trigger multiple ajax requests in redux - reactjs

I am writing a metrics page using React-Redux, which I haven't used before, and am having trouble structuring it.
The basic structure is something like this:
<input id=start_date />
<input id=end_date />
<button id=submit onClick={ this.props.fetchChartData() }/>
<Chart1 />
<Chart2 />
The store structure is this:
dates
start_date: "2016-09-16"
end_date: "2016-09-16"
charts
Chart1
api_func: "get_supported_events"
fetching: false
fetched: false
data: null
error: null
Chart2
api_func: "get_events_closed"
fetching: false
fetched: false
data: null
error: null
Using thunk, my actions right now include these functions:
function getStateURL(state){
return state.charts.Chart1['api_func'];
}
export function fetchChartData(){
return (dispatch, getState) => {
dispatch(fetchChartDataStart());
return fetch(getStateURL(getState()))
.then((response) => response.json())
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err)));
}
}
The problem is, I don't want to hard code the chart name because I feel like I should be able to write one action since all of the charts need to do the same thing.
The best solution I could guess is to have the button trigger an event that the chart components could listen for so that when the state is requested it is limited to the chart's portion, not the entire state. Is there a way to make a react component trigger an event that can be caught by other components?

The solution you are proposing seems more like old Flux model when store was just an instance of EventEmitter.
Using Flux, you can make <Chart /> like
class Chart extends Component {
componentDidMount() {
store.addEventListener('fetchData', this.fetchData);
}
componentWillUnmount() {
store.removeEventListener('fetchData', this.fetchData);
}
this.fetchData() {
api.fetchChartData(store.get('chart1.url');
}
render() {
...
}
}
With Redux however it is not immediately obvious. But it is possible to do it:
class Chart1 extends Component {
componentWillReceiveProps(nextProps) {
if (!nextProps.fetching && !nextProps.fetched) {
const { fetchData, url } = this.props;
fetchData(url);
}
}
render() {
...
}
}
export default connect(state => ({
fetching: state.Chart1.fetching
fetched: state.Chart1.fetched
url: state.Chart1.url
}), {
fetchData
})(Chart1)
and in /action.js
export function fetchChartData(url){
return (dispatch) => {
dispatch(fetchChartDataStart());
return fetch(url)
.then((response) => response.json())
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err)));
}
}
Considering the similar functionalities in all the <Chart /> components, it's worth implementing Higher order component for this and keep url somewhere as constants rather than in store.
export const fetchData = (url) => (Wrapped) => {
class Wrapper extends Component {
componentWillReceiveProps(nextProps) {
if (!nextProps.fetching && !nextProps.fetched) {
const { fetchData, url } = this.props;
fetchData(url);
}
}
render() {
return <Wrapped {...this.props} />
}
}
return connect(null, { fetchData })(Wrapper);
}
In Chart.jsx use it like:
import { chart1Url } from '.../someconstants';
import { fetchData } from '/hocs/fetchData'
const Chart1 = () => {
return <div>...</div>;
}
export default fetchData(chartUrl)(Chart1);
Although it is possible, I still think the best solution would be to store URLs in a constants file, and put api functions in another module. You can do something like:
./api/fetchData.js
export function fetchData(url) {
return new Promise((resolve, reject) =>
fetch(url)
.then((response) => response.json())
.then((json) => resolve(json))
.catch((err) => reject(err));
}
./actions.js
import { fetchData } from '../api/fetchData';
import { urls } from '.../constants';
export function fetchChartData(){
return (dispatch) => {
dispatch(fetchChartDataStart());
return Promise.all(urls.map((url) =>
fetchData(url)
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err))));
}
}

Related

Reducer not rendering items in reactJS

I have this reducer which shall return all comments on the page :
case actionTypes.GET_COMMENT:
return {
...state,
comments: action.comments
}
export const getComment = (comments : Object[]) => {
return {
type : actionTypes.GET_COMMENT,
comments
}
}
Here is how i call it in component
useEffect(() => {
const getAllCommentsOnCurrentPostFromBE = (id: Number) => {
axios.get(`http://localhost:4000/getComment/${id}`)
.then(res => {
console.log('--------res,get', res.data);
dispatch(actions.getComment(res.data))
console.log('--------posts', posts);
})
.catch(err => {
console.log('--------err', err);
})
}
getAllCommentsOnCurrentPostFromBE(grabIdFromLocation())
},[])
res.data is collection of key value pairs like this {"comment":"123"}
But it is not rendering anything,any suggestions please?
There is no dispatch() function. Downloaded data do not pass to the reducer. You have to use redux-thunk to use async functions with redux.
I recommend using actions in separate files:
export const fetchDataFromDatabase = () => async (
disapatch,
getState,
) => {
const response = await axios.get();
disapatch({
type: TYPE,
data: response.data,
});
};
Then export your component export default connect(yourProps,{fetchDataFromDatabase})(YourComponent)
In your component you can call props.fetchDataFromDatabase()

React Hook useReducer always running twice

I am loading data from a public API after my component is mounted. When the data is loaded I am passing it to the reducer, but it always fires twice. This is what I have:
function MyComponent(props) {
function reducer(data, action) {
switch (action.type) {
case 'INITIALIZE':
return action.payload;
case 'ADD_NEW':
const newData = {...data};
newData.info.push({});
return newData;
}
}
const [data, dispatch] = React.useReducer(reducer, null);
useEffect(() => {
fetch(URL)
.then(response => {
dispatch({
type: 'INITIALIZE',
payload: response
});
})
.catch(error => {
console.log(error);
});
}, []);
const addNew = () => {
dispatch({ type: 'ADD_NEW' });
}
return(
<>data ? data.info.length : 'No Data Yet'</>
);
}
As you can see the component awaits for the data to populate the reducer, which, when INITIALIZE is also called twice, but I didn't care about it until I needed to call ADD_NEW, because in that case it adds two blank objects into the array instead of only one. I wen't into the documentation for side effects, but I was unable to solve it.
What is the best way to deal with this?
Here's how I would deal with the issue.
The main reason why it was re-running the action effect was because you had the reducer in the component's function. I also went ahead and fixed several other issues.
The fetch code was a little off due to how fetch works. You have to get the data type off of the response which gives another promise instead of the data directly.
You also needed to make the rendering use {} to indicate that you were using javascript rather than text.
import React, { useReducer, useState, useEffect } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import "./style.css";
const url = `https://picsum.photos/v2/list?page=3&limit=1`;
function App(props) {
const [data, dispatch] = React.useReducer(reducer, null);
useEffect(() => {
fetch(url)
.then(async response => {
dispatch({
type: "INITIALIZE",
payload: (await response.json())
});
})
.catch(error => {
console.log(error);
});
}, []);
const addNew = () => {
dispatch({ type: "ADD_NEW" });
};
console.log("here");
return (
<>
<div>{data ? JSON.stringify(data) : "No Data Yet"}</div>
<button onClick={addNew}>Test</button>
</>
);
}
render(<App />, document.getElementById("root"));
function reducer(data, action) {
switch (action.type) {
case "INITIALIZE":
console.log(action.payload, "Initialize");
return action.payload;
case "ADD_NEW":
const newData = { ...data };
newData.info = newData.info || [];
newData.info.push({});
console.log(newData);
return newData;
}
}

How to run my dispatch method firstly and stop to change redux state and then call componentDidUpdate?

Here is my component:
class PreCreate extends Component {
handleStartBtn = e => {
this.props.createSurvey(name);
};
componentDidUpdate(prevProps, prevState) {
if (prevProps.currentSurvey !== this.props.currentSurvey) {
const cookies = new Cookies();
cookies.set("assignments", `EHS_${this.props.currentSurvey.uuid}`, {
path: "/"
});
}
this.props.history.push("/polling");
}
render() {
return (
<Button type="primary" onClick={this.handleStartBtn}>
Start
</Button>
);
}
}
const mapStateToProps = state => {
return {
currentSurvey: state.survey.currentSurvey
};
};
const mapDispatchToProps = dispatch => {
return {
createSurvey: name => dispatch(createSurvey(name))
};
};
export default withRouter(
connect(
mapStateToProps ,
mapDispatchToProps
)(PreCreate)
);
createSurvey method is a dispatcher located in my Redux actions its look like below:
export const surveySuccess = survey => {
return {
type: actionType.SURVEY_SUCCESS,
currentSurvey: survey
};
};
export const createSurvey = name => {
return dispatch => {
dispatch(surveyStart());
axios({
method: "post",
data: {
name: name
},
url: `http://127.0.0.1:8000/survey/`,
headers: {
"Content-Type": "application/json"
}
})
.then(res => {
const survey = res.data;
dispatch(surveySuccess(survey));
})
.catch(err => {
console.log(err);
dispatch(surveyFail(err));
});
};
};
My problem is when handleStartBtn method starts to run and createSurvey method is called it goes to createSurvey method which is located in my Redux actions successfully but it seems to the operation related to sending my data by Axis will be postponed until calling componentDidUpdate have been finished. it goes to componentDidUpdate without changing state that i expected to change.It's ridiculous after doing operations located in componentDidUpdate it runs the server side operations and hits Redux states.

How to increase axios speed?

Because I'm new to using axios so I usually have a trouble in using it. Specifically, I'm making a react-infinite-scroll feature now, but when I compare its speed with other site, my post(react-infinite-scroll feature) is gonna be shown slowly a little. Then I'm thinking this problem is caused by 2 reasons
1. I'm not using axios properly
2. There is a thing makes axios speed urgrade, but I'm not using it
Here's my code, please give me some advice to increase my http request speed.
Thank you for reading my question!
class MainPage extends Component {
constructor(props) {
super(props)
axios.get("http://127.0.0.1:8000/api/question")
.then(res => {
this.setState({
AnswerPostMultiList: res.data
})
}
)
.catch(err => {
console.log(err)
})
}
state = {
AnswerPostMultiList : []
}
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.handleScroll);
}
handleScroll = () => {
console.log("scroll is executing")
const { innerHeight } = window;
const { scrollHeight } = document.body;
const scrollTop =
(document.documentElement && document.documentElement.scrollTop) ||
document.body.scrollTop;
if (scrollHeight - innerHeight - scrollTop < 1000 && !this.props.isLoading["isLoading"]) {
this.props.onIsLoading() #To prevent this code from calling back continuously, change the value of this.props.isLoading["isLoading"] to false
axios.get("http://127.0.0.1:8000/api/question")
.then(res => {
this.setState({
AnswerPostMultiList: this.state.AnswerPostMultiList.concat(res.data)
})
this.props.onIsLoading() #change the value of this.props.isLoading["isLoading"] to true
}
)
.catch(err => {
console.log(err)
})
}
};
render() {
return(
<>
<PageHeader />
<div className="find_members">
{ this.state.AnswerPostMultiList.map((answerpost,index) => {
return <AnswerPostMulti question={answerpost.question_text} q_owner={answerpost.question_owner} answer={answerpost.answer_image} a_owner={answerpost.answer_owner} key={index} />
})
}
</div>
</>
)
}
}
const mapDispatchToProps = (dispatch) => ({
onIsLoading: () => {
dispatch(isLoadingActions.isLoading())
}
})
const mapStateToProps = state => ({
isLoading: state.isLoading
})
export default connect(mapStateToProps, mapDispatchToProps)(MainPage)
The best place to call a axios API calls is at componentDidMount(){}.
The Application loading process will be in this order skeleton->Initial rendering->later componentDidMount method is called. So here your app skeleton will be loaded and after that you can fetch data and use it to your app skeleton.
componentDidMount() {
axios.get("http://127.0.0.1:8000/api/question")
.then(res => {
this.setState({
AnswerPostMultiList: res.data
})
}
)
.catch(err => {
console.log(err)
});
}

Where to call action methods

Well, for example I have container and api:
container:
...
const mapDispatchToProps = dispatch => ({
handleSubmitForm: (signin_data) => {
dispatch(getUserInfo(signin_data))
}
})
...
api:
...
export function logIn( data ){
return dispatch => {
dispatch(startFetching('signin'))
return axios.post(domain + '/signin', {user: data})
.then(response => {
setTokensToLocalStorage( response.data )
dispatch(fetchingSuccess('signin'))
dispatch(getCurrentUser())
hashHistory.push('/')
dispatch(callFlash('success', 'signIn'))})
.catch(error => {
dispatch(fetchingFailed('signin'))
dispatch(callFlash('error', 'signInFail'))})
}}
/some simple functions for logout, signup etc/
...
It seems, that it should be like that:
...
export function logIn( data ){
return dispatch => {
dispatch(startFetching('signin'))
return axios.post(domain + '/signin', {user: data})
.then(response => {
dispatch(fetchingSuccess('signin'))})
.catch(error => {
dispatch(fetchingFailed('signin'))})
}}
/some simple functions for logout, signup etc/
...
But in which place should I call other methods? May be in container, but it will be strange to import this methods in every container, when I can import them just once in api
dispatch(getCurrentUser())
hashHistory.push('/')
dispatch(callFlash('success', 'signIn'))
dispatch(callFlash('error', 'signInFail'))

Resources