Should I add async code in container component? - reactjs

I'm making my first React-Redux project.
I wanna get data from getListAPI.
I checked console.log(data) in [GET_LIST_SUCCESS], and there was what I wanted.
But console.log(temp) in container, I expect 'data', it was just action object(only type exists).
How can I get the 'data'?
// container
import React from 'react';
import { useDispatch } from 'react-redux';
import Home from 'presentations/Home';
import * as homeActions from 'modules/home';
const HomeContainer = () => {
const dispatch = useDispatch();
const temp = dispatch(homeActions.getList());
console.log(temp);
return (
<Home />
);
}
export default HomeContainer;
// Redux module
import axios from 'axios';
import { call, put, takeEvery } from 'redux-saga/effects';
import { createAction, handleActions } from 'redux-actions';
function getListAPI() {
return axios.get('http://localhost:8000/');
}
const GET_LIST = 'home/GET_LIST';
const GET_LIST_SUCCESS = 'home/GET_LIST_SUCCESS';
const GET_LIST_FAILURE = 'home/GET_LIST_FAILURE';
export const getList = createAction(GET_LIST);
function* getListSaga() {
try {
const response = yield call(getListAPI);
yield put({ type: GET_LIST_SUCCESS, payload: response });
} catch (e) {
yield put({ type: GET_LIST_FAILURE, payload: e });
}
}
const initialState = {
data: {
id: '',
title: '',
created_at: '',
updated_at: '',
content: '',
view: '',
}
};
export function* homeSaga() {
yield takeEvery('home/GET_LIST', getListSaga);
}
export default handleActions(
{
[GET_LIST_SUCCESS]: (state, action) => {
const data = action.payload.data;
console.log(data);
return {
data
};
}
}, initialState
);
Maybe I need like async/await or Promise.then() or useCallback, etc in container?
Because I thought Redux-Saga handles async, but container isn't in Redux-Saga area.
So shouldn't I inject the container with async processing?
I wrote some code for test.
Expecting to receive other data in a few seconds.
// container
// const temp = dispatch(homeActions.getList());
let temp = dispatch(homeActions.getList());
let timer = setInterval(() => console.log(temp), 1000);
setTimeout(() => { clearInterval(timer); alert('stop');}, 50000);
Nothing changed.
It's just log action object(only type exists).
What am I missing?

dispatch() returns the action dispatched to the store (that's why the console.log(temp) shows the action itself).
You need to create a selector to fetch the data from the store and use the useSelector() hook:
// container
import React from 'react';
import { useDispatch } from 'react-redux';
import Home from 'presentations/Home';
import * as homeActions from 'modules/home';
const selectData = (state) => state.data
const HomeContainer = () => {
const dispatch = useDispatch();
const temp = useSelector(selectData)
dispatch(homeActions.getList());
// Do something with temp
return (
<Home />
);
}
export default HomeContainer;

Related

Calling API in redux

//Store
import { configureStore } from "#reduxjs/toolkit";
import { currencyListSlice } from "./Reducers/CurrencyListReducer";
export const store = configureStore({
reducer: {
currencyList: currencyListSlice.reducer,
}
}
)
export default store
//CurrencyListReducer
import { createSlice } from "#reduxjs/toolkit"
export const loadCurrencyList = () => {
return async (dispatch, getState) => {
const data = await fetch(API-Key)
const payload = await data.json()
dispatch({
type: 'currencyList/setCurrencyList',
payload: payload
})
}
}
const options = {
name: 'currencyList',
initialState: [],
reducers: {
setCurrencyList(state, action) {
return action.payload
}
}
}
export const currencyListSlice = createSlice(options)
//CurrencyList Component
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { currencyListSlice } from '../../Reducers/CurrencyListReducer'
const selectCurrencyList = state => state.CurrencyList
export const CurrencyList = () => {
const dispatch = useDispatch()
const currencyList = useSelector(selectCurrencyList)
const { loadCurrencyList } = currencyListSlice.actions
useEffect(() => {
dispatch(loadCurrencyList())
}, [dispatch, loadCurrencyList])
console.log(currencyList)
return (
<div>
/*Some elements here*/
</div>
)
}
I'm working with redux for the first time and having some real problem in calling API and storing data in store. The problem is I'm not getting anything from API but the console.log(currencyList) just gives me undefined. I tried calling API directly in reducer but that too didn't work out. I'm a newbie to redux and calling the API in redux is being a difficult task for me. Forgive any silly mistake(if present).
try reading this: createAsyncThunk

Dispatch is not a function error with createSlice()

My objective is to get the customer details from a fake API through redux. I have migrated to using createSlice() to reduce the boilerplate, but I keep having that error. I have tried and researched as much as I can, but to no avail.
Here is a self contained code: https://stackblitz.com/edit/react-gbhdd9?file=src/App.js
App.js
import React, { useCallback, useState, useEffect } from 'react';
import { customerApi } from './__fakeAPI__/customerApi';
import { getCustomer as getCustomerSlice } from './slices/customers';
export default function App() {
const [customer, setCustomer] = useState(null);
const getCustomer = useCallback(async () => {
try {
//const data = await customerApi.getCustomer(); //works but not what I wanted
const data = getCustomerSlice();
setCustomer(data);
} catch (err) {
console.error(err);
}
}, []);
useEffect(() => {
getCustomer();
}, [getCustomer]);
return <div>{console.log(customer)}</div>;
}
slices/customers.js
import { createSlice } from '#reduxjs/toolkit';
import { customerApi } from '../__fakeAPI__/customerApi';
const initialState = {
customer: {}
};
const slice = createSlice({
name: 'customers',
initialState,
reducers: {
getCustomer(state, action) {
state.customer = action.payload;
}
}
});
export const { reducer } = slice;
export const getCustomer = () => async dispatch => {
const data = await customerApi.getCustomer();
//dispatch seems to not be working
dispatch(slice.actions.getCustomer(data));
};
export default slice;
You needed to dispatch your action to make it work, to access customer you can use useSelector to get access to your state :
https://stackblitz.com/edit/react-v4u5ft?file=src/App.js
You need to make a few changes to App.js to make this work.
The getCustomerSlice() method is an action creator and in your case, it returns a thunk, and to get access to the dispatch method in a thunk, you must first dispatch the action/function returned from that action creator i.e in App.js
export default function App() {
...
const getCustomer = useCallback(async () => {
try {
const data = dispatch(getCustomerSlice());
...
}, []);
...
}
To get access to the dispatch method in App.js, import the useDispatch from react-redux, useDispatch is a function that returns the dispatch method i.e
...
import { useDispatch } from "react-redux"
...
export default function App() {
const dispatch = useDispatch()
...
const getCustomer = useCallback(async () => {
try {
const data = dispatch(getCustomerSlice());
...
}, []);
...
}
When you do this redux-thunk will automatically supply dispatch, getState as parameters to any function that is returned from an action creator. This will cause your implementation to update your store properly.
Since you are expecting a return value from your dispatch method, make this modification to your customers.js file
...
export const getCustomer = () => async dispatch => {
const data = await customerApi.getCustomer();
//dispatch seems to not be working
dispatch(slice.actions.getCustomer(data));
// return the data
return data;
};
...
then in your App.js file await the dispatch function since you defined it as async
...
import { useDispatch } from "react-redux"
...
export default function App() {
const dispatch = useDispatch()
...
const getCustomer = useCallback(async () => {
try {
const data = await dispatch(getCustomerSlice());
...
}, []);
...
}
TLDR
Your App.js should now look like this.
import React, { useCallback, useState, useEffect } from 'react';
import { customerApi } from './__fakeAPI__/customerApi';
import { useSelector, useDispatch} from "react-redux"
import { getCustomer as getCustomerSlice } from './slices/customers';
export default function App() {
const dispatch = useDispatch()
const [customer, setCustomer] = useState(null);
const getCustomer = useCallback(async () => {
try {
//const data = await customerApi.getCustomer(); //works but not what I wanted
const data = await dispatch(getCustomerSlice());
setCustomer(data);
} catch (err) {
console.error(err);
}
}, []);
useEffect(() => {
getCustomer();
}, [getCustomer]);
return <div>{console.log("customers",customer)}</div>;
}
And your customers.js like this
import { createSlice } from '#reduxjs/toolkit';
import { customerApi } from '../__fakeAPI__/customerApi';
const initialState = {
customer: {}
};
const slice = createSlice({
name: 'customers',
initialState,
reducers: {
getCustomer(state, action) {
state.customer = action.payload;
}
}
});
export const { reducer } = slice;
export const getCustomer = () => async dispatch => {
const data = await customerApi.getCustomer();
//dispatch seems to not be working
dispatch(slice.actions.getCustomer(data));
return data;
};
export default slice;
it is important to note that your component has not subscribed to the store yet. so if the customers object in the store is modified by another component then this component won't re-render, to fix this look up the useSelector hook from react-redux

Unable to display information after fetching data from useSelector

I'm trying to access data from my API using Redux but when redux tool kit is showing me its an empty array. The api I've populated using postman and the post method seem to work perfectly fine, but attempting to use the get method to access that data it shows an empty array. My DB has the data though. My Data is an array of Object i.e. [ {...} , {...} , {...} ]
API
import axios from "axios";
const url = "http://localhost:5000/info"
export const fetchInfo = () => axios.get(url);
export const createInfo = (newInfo) => axios.post(url, newInfo);
ACTIONS
import * as api from "../api/index.js";
//constants
import { FETCH_ALL, CREATE } from "../constants/actiontypes";
export const getInfo = () => async (dispatch) => {
try {
const { data } = await api.fetchInfo();
console.log(data);
dispatch({ type: FETCH_ALL, payload: data });
} catch (error) {
console.log(error);
}
};
export const createInfo = (info) => async (dispatch) => {
try {
const { data } = await api.createInfo(info);
dispatch({ type: CREATE, payload: data });
} catch (error) {
console.log(error);
}
};
REDUCER
import { FETCH_ALL, CREATE } from "../constants/actiontypes";
export default (infos = [], action) => {
switch (action.type) {
case FETCH_ALL:
return action.payload;
case CREATE:
return [...infos, action.payload];
default:
return infos;
}
};
COMBINE REDUCERS
import {combineReducers} from "redux";
import infos from "./info"
export default combineReducers({infos})
Component I'm trying to to display it in
import React from "react";
//redux
import { useSelector } from "react-redux";
//component
import MovieDetail from "./MovieDetail"
const MovieTitles = () => {
const infos = useSelector((state) => state.infos);
console.log(infos) // shows me empty array
return (
<div>
{infos.map((i) => (
<MovieDetail info={i} />
))}
</div>
);
};
export default MovieTitles;
Is there something else I'm missing which allows to me to access the data?
thanks

how to return data in reducer file using reactsj and typescript?

I have a problem when load my data in redux, reducer lost my data when call in my file, please help me.
my main src/index.tsx
import React from 'react'
import {Provider} from 'react-redux'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/stores'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
my file get data CampaingHeader
export class CampaingHeader {
private token: any
private resp_campaing_header: any
constructor(token: any){
this.token = token
}
/*
get last campaing for the current user
*/
getLastCampaingHeader = async() => {
this.resp_campaing_header = await API.get(`campaing-header-last`,{
headers: {Authorization: `Bearer ${this.token}`}
})
return this.resp_campaing_header
}
}
my files in redux
the types
/redux/types/campaing.types.tsx
export const SET_CAMPAING = 'SET_CAMPAING'
export const LOADING_CAMPAING = 'LOADING_CAMPAING'
export const SET_DEFAULT_CAMPAING = 'SET_DEFAULT_CAMPAING'
export const SET_ERRORS = 'SET_ERRORS'
the reducer
/redux/reducers/campaing.reducers.tsx
my problem is this file
import {
SET_DEFAULT_CAMPAING,
SET_CAMPAING,
SET_ERRORS
} from '../types/campaing.types'
const InitCampaingState = {
answer: false,
campaing: {},
errors: {}
}
export default function(state = InitCampaingState, action: any) {
// HERE LOAD MY DATA
console.info(action.payload)
switch (action.type) {
// BUT HERE NOT LOADED
case SET_CAMPAING:
return {
...state,
answer: true,
campaing: action.payload // ALWAYS payload is undefined
}
default:
return state // AND RETURN STATE
}
}
my file actions
/redux/actions/campaing.actions.tsx
import {
SET_CAMPAING,
SET_ERRORS,
} from '../types/campaing.types'
import {CampaingBody} from '../../userCampaings'
let token = window.sessionStorage.getItem('token')
let CampHeader = new CampaingBody(token)
export const RetrieveCampaing = (campaing_id: number) => (dispatch: any) => {
CampHeader.getRetrieveCBody(campaing_id)
.then(resp =>{
console.info(resp.data.data)// DATA IS LOADED HERE
dispatch({
type: SET_CAMPAING,
payload: resp.data.data,// HERE LOADED DATA TOO
})
}).catch(err =>{
dispatch({
type: SET_ERRORS,
errors: err
})
})
}
file when get the ID of campaing, the code below working well, pass the ID campaing to my code above in /redux/actions/campaing.actions.tsx
import React from 'react'
import {connect} from 'react-redux'
// call my actions
import {RetrieveCampaing} from '../../../../redux/actions/campaing.actions'
const UpdateCampaing: React.FC = (props: any) => {
React.useEffect(()=>{
// HERE PASS DATA CORRECTLY
let ID = GetCampID()
props.RetrieveCampaing(ID)
},[])
return(....)
}
const mapStateToProps = (state: any) => ({
campaing: state.campaing
})
const mapActionToProps = {
RetrieveCampaing
}
export default connect(mapStateToProps, mapActionToProps)(UpdateCampaing)
and my problem is here when I try to share data from actions, using connect not working.
import React from 'react'
import {connect} from 'react-redux'
type FormData = {
profile: Iuser
first_name: string
last_name: string
email: string
cinit: string
}
interface Icampaing {
campaing: FormData
}
const Personal: React.FC<Icampaing> = ({campaing})=>{
React.useEffect(()=>{
//MY PROBLEM IS HERE NOT LOAD DATA
console.info('from redux')
console.info(campaing) // RETURN DATA InitCampaingState, from my type files
},[])
}
return(<div>{campaing.cinit}</div>)
}
const mapStateToProps = (state: any) =>({
campaing: state.campaing
})
export default connect(mapStateToProps)(Personal)
my problem is in the REDUCER file, because the data is loaded, but don't return the data.
please help me, I don't know where is my error, aparentlly all working well, I means, the load data into de actions file working, but when I call the action into de PERSONAL file not working
best words.
Your UseEffect with empty array [] works like componentDidMount, it is called only once.
You need to add campaing to array and your useEffect hook will be called on every campaing prop change:
const Personal: React.FC<Icampaing> = ({campaing})=>{
React.useEffect(()=>{
//MY PROBLEM IS HERE NOT LOAD DATA
console.info('from redux')
console.info(campaing) // RETURN DATA InitCampaingState, from my type files
},[campaing])
}

React UseState hook causing infinite loop

I am using ReactJs to grab an RSS news feed every 5 seconds to convert it into a JSON string to render it on the webpage. I am using both useEffect and useState hook for this purpose as I am passing the JSON string in the useState hook variable, however. It kind of works but it produces an infinite loop. I have searched through the fixes provided in stack overflow but I couldn't find the exact problem. Here is my code snippet.'
import React, {useEffect, useState} from 'react';
import Carousel from 'react-bootstrap/Carousel';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {getNews} from "../../actions/news";
import Parser from 'rss-parser';
const NewsCarousel = ({getNews, news: {news, loading} }) => {
const [getFeed, setFeed] = useState({
feed: ''
});
useEffect(() => {
const interval = setInterval(() => {
getNews();
}, 5000);
return () => clearInterval(interval);
}, [getNews]);
const { feed } = getFeed;
const newsFeed = feed => setFeed({ ...getFeed, feed: feed });
let parser = new Parser();
parser.parseString(news, function(err, feed){
if (!err) {
newsFeed(feed);
} else {
console.log(err);
}
});
console.log(feed);
return (
<div className="dark-overlay">
</div>
);
};
NewsCarousel.propTypes = {
getNews: PropTypes.func.isRequired,
news: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
news: state.news
});
export default connect(mapStateToProps, {getNews}) (NewsCarousel);
Its when I console.log my feed variable that's when I see in the console the infinite logs.
Below is my getNews Action
import axios from 'axios';
import { GET_NEWS, NEWS_FAIL } from "./types";
export const getNews = () => async dispatch => {
try{
const res = await axios.get('https://www.cbc.ca/cmlink/rss-
topstories');
dispatch({
type: GET_NEWS,
payload: res.data
})
} catch(err) {
dispatch({
type: NEWS_FAIL,
payload: { msg: err}
})
}
};
You need to parse your news only when there is a change in new props. Add another useEffect with news as a dependency so it will be called when the news changes and then update your state there.
import React, {useEffect, useState} from 'react';
import Carousel from 'react-bootstrap/Carousel';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {getNews} from "../../actions/news";
import Parser from 'rss-parser';
const NewsCarousel = ({getNews, news: {news, loading} }) => {
const [getFeed, setFeed] = useState({
feed: ''
});
useEffect(() => {
const interval = setInterval(() => {
getNews();
}, 5000);
return () => clearInterval(interval);
}, [getNews]);
useEffect(() => {
const newsFeed = feed => setFeed({ ...getFeed, feed: feed });
const parser = new Parser();
parser.parseString(news, function(err, feed){
if (!err) {
newsFeed(feed);
} else {
console.log(err);
}
});
}, [news]);
return (
<div className="dark-overlay">
</div>
);
};
NewsCarousel.propTypes = {
getNews: PropTypes.func.isRequired,
news: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
news: state.news
});
export default connect(mapStateToProps, {getNews}) (NewsCarousel);

Resources