I am trying to replace my old react app with only functional component and i'm stuck at making api call with 'axios' in redux.
This is my api call function
return new Promise((resolve, reject) => {
return axios[method.toLowerCase()](path, data)
.then(res => {
return resolve(res.data);
})
.catch(err => {
return reject(err.response.data.error);
});
});
}
this is for redux
export const loadCategories = categories => ({
type: LOAD_CATEGORIES,
categories
});
export const fetchCategories = () => {
return dispatch => {
return apiCall("get", "/api/categories/getall")
.then(function(res) {
dispatch(loadCategories(res));
})
.catch(function(err) {
addError(err.message);
});
};
};
import { LOAD_CATEGORIES } from "../actionTypes";
const category = (state = [], action) => {
switch (action.type) {
case LOAD_CATEGORIES:
return [...action.categories];
default:
return state;
}
};
export default category;
What I'm trying to do is to just execute fetchCategories() function
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { fetchCategories } from "../store/actions/categories";
import { Link } from "react-router-dom";
const Nav = ({ categories }) => {
useEffect(() => {
fetchCategories();
}, []);
return <div/>;
};
function mapStateToProps(state) {
return {
categories: state.categories
};
}
export default connect(mapStateToProps, { fetchCategories })(Nav);
but it does not making api call at all...
It seems that you are connecting the action creator fetchCategories but you are actually calling the unconnected version.
Try calling fetchCategories from the received props:
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { fetchCategories } from "../store/actions/categories";
import { Link } from "react-router-dom";
// Note that we are calling now the connected version of fetchCategories
const Nav = ({ categories, fetchCategories }) => {
useEffect(() => {
fetchCategories();
}, []);
return <div/>;
};
function mapStateToProps(state) {
return {
categories: state.categories
};
}
export default connect(mapStateToProps, { fetchCategories })(Nav);
Because you need fetchCategories from props, not from ../store/actions/categories
const Nav = ({ categories, fetchCategories }) => {
useEffect(() => {
fetchCategories();
}, []);
return <div/>;
};
I would suggest to go with redux-thunk or redux-saga to handle async flows in react.
Checkout how to handle asyncflows using redux-thunk -> https://github.com/reduxjs/redux-thunk.
Checkout how to handle asyncflows using redux-saga -> https://redux-saga.js.org/
Related
Currently, I'm using functional components with hooks but still dispatching my actions with the connect HOC.
I read through the documentation with useDispatch but I'm unsure how to incorporate it in my code. From the examples, they are passing the the action types and payloads inside the component. Would I have to move myOfferActions functions back to the component in order to useDispatch?
MyOffers component
import React, { useEffect } from "react";
import { connect, useSelector } from "react-redux";
import "./MyOffers.scss";
import MyOfferCard from "../../components/MyOfferCard/MyOfferCard";
import { fetchMyOffers } from "../../store/actions/myOffersActions";
const MyOffers = (props) => {
const myOffers = useSelector((state) => state.myOffers.myOffers);
useEffect(() => {
props.fetchMyOffers();
}, []);
return (
<div className="my-offers-main">
<h1>My offers</h1>
{myOffers && (
<div className="my-offer-container">
{myOffers.map((offer) => (
<MyOfferCard key={offer.id} offer={offer} />
))}
</div>
)}
</div>
);
};
export default connect(null, { fetchMyOffers })(MyOffers);
offerActions
export const fetchMyOffers = () => async (dispatch) => {
const userId = localStorage.getItem("userId");
try {
const result = await axiosWithAuth().get(`/offers/${userId}`);
let updatedData = result.data.map((offer) => {
//doing some stuff
};
});
dispatch(updateAction(FETCH_MY_OFFERS, updatedData));
} catch (error) {
console.log(error);
}
};
offerReducer
import * as types from "../actions/myOffersActions";
const initialState = {
offerForm: {},
myOffers: [],
};
function myOffersReducer(state = initialState, action) {
switch (action.type) {
case types.FETCH_MY_OFFERS:
return {
...state,
myOffers: action.payload,
};
default:
return state;
}
}
export default myOffersReducer;
I don't think you need connect when using the redux hooks.
You just need to call useDispatch like:
const dispatch = useDispatch();
And use the function by providing the object identifying the action:
dispatch({ type: 'SOME_ACTION', payload: 'my payload'});
It should be working with redux-thunk too (I guess this is what you're using): dispatch(fetchMyOffers())
I have an actions file called menu that uses axios to get JSON data from a backend API. I then have reducers which are called in a MainMenu.js file which gets the data. All of the data is shown in the Redux devtools. However I am only able to access the data I have in intitialState. There should be an array of three items but it only shows the first one in intitialState. The data from the reducers does not show up when I try to console.log it. So obviously I would not be able to render it to the DOM.
actions/menu.js
import { ADD_PRODUCT, GET_PRODUCTS, GET_PRODUCT } from './types';
import axios from 'axios';
export const getProducts = () => (dispatch) => {
axios
.get('http://localhost:8080/menu')
.then((response) => {
// console.log(response);
const data = response.data;
dispatch({
type: GET_PRODUCTS,
payload: { data },
});
})
.catch((err) => {
console.log(err);
});
};
export const getProduct = (product) => (dispatch) => {
axios
.get(`http://localhost:8080/menu/${product}`)
.then((response) => {
// console.log(response);
const data = response.data;
dispatch({
type: GET_PRODUCT,
payload: { data },
});
})
.catch((err) => {
console.log(err);
});
};
reducers/index.js
import { combineReducers } from 'redux';
import menu from './menu';
export default combineReducers({ menu });
reducers/menu.js
import { GET_PRODUCTS, GET_PRODUCT } from '../actions/types';
const intitialState = [
{
test: 'Test',
},
];
export default function (state = intitialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PRODUCTS:
return [...state, payload];
case GET_PRODUCT:
return [...state, payload];
default:
return [...state];
}
}
components/MainMenu.js
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import { getProducts, getProduct } from '../actions/menu';
const MainMenu = ({ getProducts, getProduct, menu }) => {
useEffect(() => {
getProducts();
getProduct('b4bc2e28-21a3-47ea-ba3b-6bad40b35504');
console.log(menu);
}, []);
return <Fragment></Fragment>;
};
const mapStateToProps = (state) => ({
menu: state,
});
export default connect(mapStateToProps, { getProducts, getProduct })(MainMenu);
I created a working solution using the ternary operator. First a check is made to see if all of the elements in the array have loaded. If they have not all loaded then a data not loaded div is displayed. Otherwise the data is rendered to the DOM.
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import { getProducts, getProduct } from '../actions/menu';
const MainMenu = ({ getProducts, getProduct, menu }) => {
useEffect(() => {
getProducts();
getProduct('b4bc2e28-21a3-47ea-ba3b-6bad40b35504');
}, []);
if (menu.length <= 2) {
console.log('Data Not Loaded');
} else {
console.log('Data', menu[2].data.name);
}
return (
<Fragment>
<div>{menu.length <= 2 ? <div>Data not loaded</div> : <div>{menu[2].data.name}</div>}</div>
<div>
{menu.length <= 2 ? (
<div>Data not loaded</div>
) : (
<div>
{menu[1].data.map((food) => (
<div>{food.name}</div>
))}
</div>
)}
</div>
</Fragment>
);
};
const mapStateToProps = (state) => ({
menu: state.menu,
});
export default connect(mapStateToProps, { getProducts, getProduct })(MainMenu);
I have the following code implemented (PLEASE keep in mind, I don't want to use redux)
My global store hook:
// store.tsx - custom store hook
import { useState, useEffect } from "react";
let globalState = {};
let listeners = [];
let actions = {};
export const useStore = () => {
const setState = useState(globalState)[1];
const dispatch = async (actionIdentifier, payload) => { // needed to add async/await in order to wait the async/await in the custom hooks for actions, i.e. for fetching
const newState = await actions[actionIdentifier](globalState, payload);
globalState = { ...globalState, ...newState };
for (const listener of listeners) {
listener(globalState);
}
};
useEffect(() => {
listeners.push(setState);
return () => {
listeners = listeners.filter((li) => li !== setState);
};
}, [setState, shouldListen]);
return [globalState, dispatch];
};
export const initStore = (userActions, initialState) => {
if (initialState) {
globalState = { ...globalState, ...initialState };
}
actions = { ...actions, ...userActions };
};
import { initStore } from "./store";
the file responsible for loading actions
// loading-store.tsx
const configureStore = () => {
const actions = {
SET_LOADING_START: (currentState) => {
return { loading: true };
},
SET_LOADING_END: (currentState) => {
return { loading: false };
},
};
initStore(actions, {
loading: false,
});
};
export default configureStore;
and the file responsible for fetching:
// fetching-store.tsx
import { initStore } from "./store";
import { getData } from "src/api/ResourceService";
const configureStore = () => {
function fetchData() {
const fetch = async () => {
try {
const res = await getData();
console.log("[FETCH] Data:", res.data);
return res.data;
} catch (err) {
console.error("[FETCH] Error", err);
return err;
}
};
return fetch();
}
const actions = {
FETCH: async (currentState) => {
const newData = await fetchData();
return { myFetchedData: newData };
},
};
initStore(actions, {
myFetchedData: [],
});
};
export default configureStore;
the following index.tsx file:
// index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "./assets/css/simpl-newton-bootstrap.css";
import "../node_modules/react-grid-layout/css/styles.css";
import "../node_modules/react-resizable/css/styles.css";
import { BrowserRouter } from "react-router-dom";
import * as serviceWorker from "./serviceWorker";
import App from "./App";
import { ThemeProvider } from "src/context/ThemeContext";
import fetchingStore from "src/hooks-store/fetching-store";
import loadingStore from "src/hooks-store/loading-store";
fetchingStore();
loadingStore();
ReactDOM.render(
<ThemeProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</ThemeProvider>,
document.getElementById("root")
);
serviceWorker.unregister();
The QUESTION is how can I set loading to true/false during the fetch which is done in fetchiing-store.tsx
This is the implementation in the Component where I call the fetch:
// myComponent.tsx
import React, { useEffect } from "react";
import { useStore } from "src/hooks-store/store";
const myComponent = () => {
const [state, dispatch] = useStore();
const data = state.myFetchedData;
useEffect(() => {
dispatch("FETCH");
}, []);
return (
<>
// some code where data will be used ...
</>
);
}
export default myComponent;
I did try to place it in the useEffect of myComponent.tsx as:
...
useEffect(() => {
dispatch("SET_LOADING_START");
dispatch("FETCH");
}, []);
...
and than manually in fetching-store.tsx
...
const actions = {
FETCH: async (currentState) => {
const newData = await fetchData();
return { myFetchedData: newData, loading: false };
},
};
initStore(actions, {
myFetchedData: [],
loading: false,
});
...
But it is a bit dirty. If anyone has a better solution or an option?
Another question is, is it at all a good idea to use async/await call in the actions store?
I am trying to fetch data from an api, but I am getting an error: TypeError: this.props.getPeople is not a function, while everything looks good through the code, as below:
people-component.js
import React, { Component } from 'react';
import './people-component-styles.css';
import { Container, Row, Col, Card } from 'react-bootstrap';
import { connect } from 'react-redux';
import { getPeople } from '../../actions/index'
import 'react-lazy-load-image-component/src/effects/blur.css';
import 'animate.css/animate.min.css';
export class People extends Component {
componentDidMount() {
// console.log(this.props);
this.props.getPeople();
}
render() {
// console.log(this.props);
return (
<Row className='main'>
hello!
</Row>
);
}
}
const mapStateToProps = state => ({
people: state.people
})
const mapDispatchToProps = dispatch => ({
getPeople: () => dispatch(getPeople())
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(People);
actions/index.js
export const getPeople = () => {
console.log("yes");
return async dispatch => {
const response = await fetch('https://dma.com.eg/api.php?action=getPeople', {
method: 'GET'
})
const json = await response.json();
dispatch({ type: "GET_PEOPLE", payload: json });
}
}
reducers/index.js
const INITIAL_STATE = {
people: []
}
const rootReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case "GET_PEOPLE":
return ({
...state,
people: state.people.concat(action.payload)
})
default:
return state;
}
};
export default rootReducer
I was importing People component as name while it's exported as default, thanks to #Brian Thompson .. it's fixed.
I want to use redux hook useSelector to access the store and get rid of connect(), so I need to create a way to export my actions, and I'm thinking on a class with static methods, here is an example
export default class AuthActions {
static async login(userData) {
try {
const user = await axios.post('http://localhost:5000', userData);
dispatch({
type: AUTH.LOGIN,
payload: user.data
})
} catch (error) {
dispatch({
type: SET_ERROR,
payload: error
})
}
}
static setUser() {
console.log("SET USER")
}
static logout() {
console.log("Logout")
}
}
And then I use the action methods as follows:
import React from 'react';
import AuthActions from '../../redux/actions/AuthActions';
import { useSelector } from 'react-redux';
export default const Login = () => {
//More logic....
const { isAuth } = useSelector((state) => state.auth);
const submitHandler = e => {
e.preventDefault();
AuthActions.login(userData)
}
return (
<form onSubmit={submitHandler}>
My Login form ....
</form>
);
};
But I'm wondering if there is a disadvantage or performance issues to use redux in this way, or should I avoid the usage of a class and use a simple object instead?
Thank you in advance
this is my format of a reducer, inspired by ducks-modular-redux
for example, check out this darkMode reducer:
export const constants = {
TOGGLE: "darkMode/TOGGLE"
};
export const actions = {
toggleDarkMode: () => {
return {
type: constants.TOGGLE
};
}
};
export const thunks = {
toggleDarkMode: () => {
return (dispatch, getState) => {
dispatch(actions.toggleDarkMode());
const isDark = getState().darkMode.isDark;
localStorage.setItem("isDark", isDark);
};
}
};
const initialState = { isDark: localStorage.getItem("isDark") === "true" };
export default (state = initialState, action) => {
switch (action.type) {
case constants.TOGGLE:
return {
isDark: !state.isDark
};
default:
return state;
}
};