How to get data from async functions in react components, react-redux? - javascript

I am new to redux and react. I have React container and component which gets data from the api request call. My question in basically, what is the best way to handle asyc functons of redux in react. I need help to get the data in react component.
Container.js: (incomplete, here I need help)
class Container extends React.Component {
state = {
userList: ''
}
componentDidMount() {
this.props.loadUserDetails();
}
render() {
return (
<div className="app">
<Component userList={this.state.userList}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
userList: state.auth.userList
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
loadUserDetails
}, dispatch);
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(Container));
Componet.js: (Pure component, here I need to render the data)
class Component extends React.Component {
render() {
return (
<div className="component">
{this.props.userList}
</div>
);
}
}
modules/auth/index.js
export const loadUserDetails = () => {
return dispatch => {
dispatch({
type: types.LOAD_USER_REQUEST
});
request.get('/api/v1/auth', dispatch)
.then(({ data }) => {
if (data.success) {
dispatch({
type: types.LOAD_USER_SUCCESS
payload: data.data
});
} else {
dispatch({
type: types.LOAD_USER_FAILURE,
payload: data.message
});
}
})
.catch((err) => {
dispatch({
type: types.LOAD_USER_FAILURE,
payload: 'Something went wrong, please try again.'
});
});
};
};
modules/auth/actions.js:
export const LOAD_USER_REQUEST = 'auth/LOAD_USER_REQUEST';
export const LOAD_USER_SUCCESS = 'auth/LOAD_USER_SUCCESS';
export const LOAD_USER_FAILURE = 'auth/LOAD_USER_FAILURE';
modules/auth/reducers.js:
state ={
loading: false,
error: null,
userList: null
}
case types.LOAD_USER_REQUEST:
return Object.assign({}, state, {
loading: true
});
case types.LOAD_USER_REQUEST:
return Object.assign({}, state, {
loading: false,
userList: payload,
});
case types.LOAD_USER_REQUEST:
return Object.assign({}, state, {
loading: flase,
error: payload
});
I actually need help to get the userList in Container and pass it to the Component. because it's an asyc function I am not able to get the data in Container before Component renders. How to handle such situations?
As I am passing userList in Child component, for the first time I don't have the userList data. So my problem is with the cycles of Reactjs, Where should I call loadUserList ? In componentDidMount? Or componentDidUpdate? If so, how can I get the data?
I am not able to render the userList it's value is null when the Component mounts. How to solve this?

a good option is to make use of redux-saga, easy and simple to implement:
https://github.com/redux-saga/redux-saga

Related

Fetching genre list from TMDB using useEffect and axios stuck on loading

I'm creating my first real react app and I'm having some problems already.
I want to fetch a list of genres, but the state is stuck on Loading.
Also, any tips on how can I fetch data based on the genre? Let's say If I click on the action genre, a list of genre movies renders.
Here is my code
import React, {useState, useEffect ,useReducer} from 'react';
import MovieCards from './components/MovieCards'
import axios from 'axios';
import './App.css';
const initialState = {
loading: true,
error: '',
genres: {}
}
const reducer = (state, action) => {
switch(action.type) {
case 'FETCH_SUCCESS':
return {
loading: false,
genres: action.payload,
error: ''
}
case 'FETCH_ERROR':
return {
loading: false,
genres: {},
error: 'Error'
}
default:
return state
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState)
useEffect(() => {
axios
.get('https://api.themoviedb.org/3/genre/movie/list?api_key=af1b76109560756a2450b61eff16e738&language=en-US')
.then(response => {
dispatch({
type: "FETCH_SUCCES", payload: response.data
})
})
.catch(error => {
dispatch({
type: "FETCH_ERROR"
})
})
}, [])
return (
<div>
{state.loading ? 'Loading' : state.genres.genres.id}
{state.error ? state.error : null}
</div>
)
}
export default App;
There was a small typo in your code when you dispatch an action due to which the correct state wasn't processed by reducer.
Correct code;
dispatch({
type: "FETCH_SUCCESS", payload: response.data // IT was FETCH_SUCCESS
})
Secondly you can render the array of genres as you like in a list and attach a click listner to it and make an API call
state.genres.genres.map(g => (
<div key={g.id} onClick={() => fetchMovies(g)}>
{g.name}
</div>
));
Working demo

Async redux action to fetch data is causing component to reload and cause react to react max depth in reload

I am trying to create a component that allows detecting changes in the redux store. Once the shouldUpdateData flag is set in the store, the component responsible for updating should fetch the data by using an async action creator. In my case, either the error "Maximum updates have reached" occurs or the update never happens.
Depending on the dispatch function stopFetching() (turns off the shouldUpdateData flag), the error or outcome changes. If I do the dispatch inside the action creator there are endless updates. If the code is used as it is below, no update occurs.
The reason I used the useSelector() hook from 'react-redux' is to detect a change in the store for the loading attribute.
Thank you in advance.
Here is the action creator:
export function updateDataAsync(id) {
return function (dispatch) {
// dispatch(fetchDataRequest());
return fetch(`/api/user/${id}/data`, {
method: "GET",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(
(result) => {
let {projects, notes} = result;
// New data and dispatch function
dispatch(fetchDataSuccess({projects, notes}));
},
(error) => { dispatch(fetchDataFailure(error)) }
)
}
}
Here is the reducer for this action creator:
export function savedData(state = DATA_INITIAL_STATE, action) {
switch(action.type) {
case FETCH_STATES.FETCH_DATA_REQUEST:
return {
...state,
loading: true
}
case FETCH_STATES.FETCH_DATA_SUCCESS:
return {
loading: false,
data: action.data,
error: ''
}
case FETCH_STATES.FETCH_DATA_FAILURE:
return {
loading: false,
data: {},
error: action.error.message
}
default:
return state;
}
}
The React component that is doing the update:
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
let reqSent = useRef(false);
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
})
return loading ? <LoadingAnimation /> : children;
}
const mapStateToProps = (state) => {
return {
userId: state.user.id,
shouldUpdate: state.shouldUpdateData // The flag that should trigger the update
}
}
const mapDispatchToProps = (dispatch) => {
return {
stopFetch: () => { dispatch(setShouldFetchData(false)) },
update: (id) => { dispatch(updateDataAsync(id)) },
startFetch: () => dispatch(fetchDataRequest()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(StoreUpdater);
You dint pass any dependency to useEffect so it will be called on every render which is causing infinite renders
change useEffect to
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
},[])
For complete information regarding useEffect refer this link
The reference I created inside the component responsible of the updates, was causing the problem. The reference was preventing the update dispatch to occur due to the if statement being false.
mapStateToProps and mapDispatchToProps were react-redux higher order functions to connect classes components into the store. there equalants at functional components are useSelector and useDispatch. re-write your HOC redux adaption into hooks, and add [ dependency ] at useEffect usage
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
const userId = useSelector(state => state.user.id);
const shouldUpdate = useSelector(state => state.shouldUpdateData);
let reqSent = useRef(false);
const dispatch = useDispatch() // import from 'react-redux'
useEffect(()=>{
if(!reqSent && shouldUpdate) {
dispatch(startFetch());
dispatch(update(userId));
reqSent.context = true;
}
}, [reqSent, shouldUpdate, startFetch, dispatch, update, userId])
return loading ? <LoadingAnimation /> : children;
}
export default StoreUpdater ;

.map Returning its not function

I have a mern application using redux for state management.
For some reason when I try to map through it, it tells me it's not a function.
It is weird because when I see my props through the console, it shows me it's an array and react knows that I have data in my state. And it also shows the data in my redux dev tools. But when I try to render it gives me that error. Also when i do this.props.products.products it tells me cannot read property of Null.
Here's the github repo
https://github.com/bryanb213/seller
Can anyone explain why
stuff.jsx
import React, { Component } from 'react'
import './stuff.stle.css'
import { getProducts } from '../redux/actions/productActions';
import { connect } from 'react-redux';
class Stuff extends Component {
componentDidMount() {
this.props.getProducts();
}
render() {
console.log('Products from props', this.props)
if (this.props.loading === true) {
return (
<div>Loading...</div >
)
} else {
return(
<div>
{ this.props.products.map(p => (
<h1>{p.name}</h1>
))
}
</div>
)
}
}
}
const mapStateToProps = state => ({
//products from root reducer
products: state.products,
})
export default connect(mapStateToProps, { getProducts })(Stuff);
Action
// Get all products
export const getProducts = () => dispatch => {
axios
.get('http://localhost:5000/api/products/all')
.then(res =>
dispatch({
type: GET_PRODUCTS,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_PRODUCTS,
payload: null
})
);
};
Reducer
import { GET_PRODUCTS } from '../actions/types';
const initialState = {
products: null,
loading: true
}
export default function(state= initialState, action){
switch(action.type){
case GET_PRODUCTS:
console.log('hitting GET_PRODUCTS', action.payload)
return {
...state,
products: action.payload,
loading: false
}
default:
return state
}
}
Server route
router.get('/all', (req, res) => {
Product.find()
.exec()
.then(stuff => {
res.status(200).json(stuff);
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
});
postman result
render() {
console.log("Products from props", this.props);
const { loading, products } = this.props;
if (loading === true) {
return <div>Loading...</div>;
} else {
return (
<div>{products && products.products.map(p => <h1>{p.name}</h1>)}</div>
);
}
}
this.props.products value is { products : [..] }, so you have to access it by this.props.products.products, in cases like this it will be easier if you use destructring assignment syntax to get the respected values to avoid some confusion.

React Warning: Can't call setState (or forceUpdate) on an unmounted component

I have 2 components:
Orders - fetch some data and display it.
ErrorHandler - In case some error happen on the server, a modal will show and display a message.
The ErrorHandler component is warping the order component
I'm using the axios package to load the data in the Orders component, and I use axios interceptors to setState about the error, and eject once the component unmounted.
When I navigate to the orders components back and forward i sometimes get an error in the console:
Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in Orders (at ErrorHandler.jsx:40)
in Auxiliary (at ErrorHandler.jsx:34)
in _class2 (created by Route)
I tried to solve it by my previous case React Warning: Can only update a mounted or mounting component but here I can't make an axios token by the inspectors. Has anyone solved this issue before?
Here are my components:
Orders:
import React, { Component } from 'react';
import api from '../../api/api';
import Order from '../../components/Order/Order/Order';
import ErrorHandler from '../../hoc/ErrorHandler/ErrorHandler';
class Orders extends Component {
state = {
orders: [],
loading: true
}
componentDidMount() {
api.get('/orders.json')
.then(response => {
const fetchedOrders = [];
if (response && response.data) {
for (let key in response.data) {
fetchedOrders.push({
id: key,
...response.data[key]
});
}
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch(error => {
this.setState({ loading: false });
});
}
render() {
return (
<div>
{this.state.orders.map(order => {
return (<Order
key={order.id}
ingrediencies={order.ingrediencies}
price={order.price} />);
})}
</div>
);
}
}
export default ErrorHandler(Orders, api);
ErrorHandler:
import React, { Component } from 'react';
import Auxiliary from '../Auxiliary/Auxiliary';
import Modal from '../../components/UI/Modal/Modal';
const ErrorHandler = (WrappedComponent, api) => {
return class extends Component {
requestInterceptors = null;
responseInterceptors = null;
state = {
error: null
};
componentWillMount() {
this.requestInterceptors = api.interceptors.request.use(request => {
this.setState({ error: null });
return request;
});
this.responseInterceptors = api.interceptors.response.use(response => response, error => {
this.setState({ error: error });
});
}
componentWillUnmount() {
api.interceptors.request.eject(this.requestInterceptors);
api.interceptors.response.eject(this.responseInterceptors);
}
errorConfirmedHandler = () => {
this.setState({ error: null });
}
render() {
return (
<Auxiliary>
<Modal
show={this.state.error}
modalClosed={this.errorConfirmedHandler}>
{this.state.error ? this.state.error.message : null}
</Modal>
<WrappedComponent {...this.props} />
</Auxiliary>
);
}
};
};
export default ErrorHandler;
I think that's due to asynchronous call which triggers the setState, it can happen even when the component isn't mounted. To prevent this from happening you can use some kind of flags :
state = {
isMounted: false
}
componentDidMount() {
this.setState({isMounted: true})
}
componentWillUnmount(){
this.state.isMounted = false
}
And later wrap your setState calls with if:
if (this.state.isMounted) {
this.setState({ loading: false, orders: fetchedOrders });
}
Edit - adding functional component example:
function Component() {
const [isMounted, setIsMounted] = React.useState(false);
useEffect(() => {
setIsMounted(true);
return () => {
setIsMounted(false);
}
}, []);
return <div></div>;
}
export default Component;
You can't set state in componentWillMount method. Try to reconsider your application logic and move it into another lifecycle method.
I think rootcause is the same as what I answered yesterday, you need to "cancel" the request on unmount, I do not see if you are doing it for the api.get() call in Orders component.
A note on the Error Handling, It looks overly complicated, I would definitely encourage looking at ErrorBoundaries provided by React. There is no need for you to have interceptors or a higher order component.
For ErrorBoundaries, React introduced a lifecycle method called: componentDidCatch.
You can use it to simplify your ErrorHandler code to:
class ErrorHandler extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true, errorMessage : error.message });
}
render() {
if (this.state.hasError) {
return <Modal
modalClosed={() => console.log('What do you want user to do? Retry or go back? Use appropriate method logic as per your need.')}>
{this.state.errorMessage ? this.state.errorMessage : null}
</Modal>
}
return this.props.children;
}
}
Then in your Orders Component:
class Orders extends Component {
let cancel;
state = {
orders: [],
loading: true
}
componentDidMount() {
this.asyncRequest = api.get('/orders.json', {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
})
})
.then(response => {
const fetchedOrders = [];
if (response && response.data) {
for (let key in response.data) {
fetchedOrders.push({
id: key,
...response.data[key]
});
}
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch(error => {
this.setState({ loading: false });
// please check the syntax, I don't remember if it is throw or throw new
throw error;
});
}
componentWillUnmount() {
if (this.asyncRequest) {
cancel();
}
}
render() {
return (
<div>
{this.state.orders.map(order => {
return (<Order
key={order.id}
ingrediencies={order.ingrediencies}
price={order.price} />);
})}
</div>
);
}
}
And use it in your code as:
<ErrorHandler>
<Orders />
</ErrorHandler>

React (react-native) / Redux - How to filter data coming from redux store?

Im trying to accomplish the following: I want to show data, then filter it / showing filtered results.
Im new to redux and have been reading a lot, I saw that its quite common to use the library reselect, but as I only need the filter option in one place, I didnt do that. I tried to implement what I read but somehow its not really working. Also Im not sure if the best way to do it would be inside action or inside mapStateToProps as I did (I read that data shouldnt be mutated inside reducer in case it would be needed somewhere else so thats why I tried to do it inside mapStateToProps). If anyone would have a look at my code and tell me what I am doing wrong, it would be great! Thanks!
The error Im getting is 'cannot read property filter of null, even though "state.allData.data" is an array of objects so I dont understand it..also Im not sure at all about the rest.
Ps. Sorry for so much code to read, but I tried to remove unnecessary parts
Action:
import axios from "axios";
import {FETCHING_DATA, FETCH_DATA_SUCESS, FETCH__DATA_ERR, FILTER__DATA} from "...";
export const FetchData = () => {
return dispatch => {
dispatch({type: FETCHING_DATA})
return axios.get("https://example.com")
.then(res => {
dispatch({type: FETCH_DATA_SUCESS, payload: res.data})
})
.catch(err => {
dispatch({type: FETCH_DATA_ERR, payload: err.data})
})
}
},
receiveSearchInput = (input) => {
return dispatch => {
dispatch({type: FILTER_DATA, input: input})
}
}
Reducer:
import {FETCHING_DATA, FETCH_DATA_SUCESS, FETCH_DATA_ERR, FILTER_DATA} from "...";
const initialState = {
isFetching: null,
data: [],
hasError: false,
errorMsg: null,
seachInput: ""
}
export default function (state = initialState, action) {
switch (action.type) {
case FETCHING_DATA:
return {
...state, isFetching: true, data: null, hasError: false, errorMsg: null
}
case FETCH_DATA_SUCESS:
return {
...state, isFetching: false, data: action.payload, hasError: false, errorMsg: null
}
case FETCH_DATA_ERR:
return {
...state, isFetching: false, data: action.payload, haserror: true, errorMsg: action.err
}
case FILTER_DATA:
return {
...state,
seachInput: action.input
}
default:
return state;
}
}
index.js for reducer:
const rootReducer = combineReducers({
allData: DataReducer
});
Container:
import React from "react";
import {connect} from "react-redux";
import {FetchCoinData, receiveSearchInput} from "..";
import { SearchBar } from 'react-native-elements'
class ItemContainer extends React.Component {
componentDidMount() {
this.props.FetchData()
}
filterData = (e) => {
this.props.receiveSearchInput(e)
}
renderItems() {
return this.props.allData.data.map((item, index) =>
<Item
key={index}
name={item.name}
symbol={item.symbol}
price={item.price}
/>
)
}
render () {
if (this.props.allData.isFetching) {
return (
<View>
...
</View>
)
}
return (
<View>
<SearchBar
onChangeText={this.filterData}
/>
{this.renderItems()}
</View>
)
}
}
function mapStateToProps(state) {
return {
allData: state.allData,
filteredItems: state.allData.data.filter((item) => item.symbol.toLowerCase().includes(state.allData.seachInput) || item.name.toLowerCase().includes(state.allData.seachInput))
}
}
export default connect(mapStateToProps, { FetchData, receiveSearchInput })(ItemContainer)
I'm assuming here that FECHTING_DATA is FETCHING_COIN_DATA in your reducers file.
What is happening is when you call dispatch({type: FETCHING_DATA}), that reducer sets the data to null, changing your props and re-rendering the component. When that happens, redux calls your mapStateToProps method, in which time allData.data is null, giving your the error. To avoid this behavior you should set data: [].
About where to filter your data. The problem with mapStateToProps is that is called whenever there is a change in that component's props. So if you have a more complex component with many different props mapped to it, you would be re-filtering your list many times even though the filter parameters might not had have changed. If it's a big list this could cause some lag in you application.
The way I do it is to use the componentWillReceiveProps lifecycle method and check if the filter has changed and them store the filtered list in the state.
componentWIllMount(nextProps) {
if(this.props.allData.searchInput != nextProps.allData.searchInput || // check if input has changed
this.props.allData.data != nextProps.allData.data) { // check if data has changed
// filter your data
// ...
this.setState({ filteredData })
}
// And change your renderItems method to
renderItems() {
return this.state.filteredData.map(() => { /* ... */ });
}
Hope that helped!

Categories

Resources