Reputation: 481
I am trying to fetch some stocks data from a stocks API. i can fetch it using normal react hooks , but i am facing issues when doing it with redux and redux saga. In the first render, it doesn't update the redux store. It updates the redux store in the second render. but even if the redux store is updated, my component doesnt get the updated state??
Can anyone please Help?
this is my component :
import React, { useState,useEffect } from 'react'
import { connect } from "react-redux";
import { Link } from 'react-router-dom'
function Dashboard(props) {
useEffect(()=>{
console.log(props)
props.getStockData()
},[])
useEffect(()=>{
props.getStockData()
console.log(props)
},[props.stocks.length])
return (
<React.Fragment>
{props.stocks.length ?
<div>
<h1>hello</h1>
</div>
: <button class=" p-4 m-4 spinner"></button>
}
</React.Fragment>
)
}
const mapStateToProps = state => {
return {
stocks: state
};
};
const mapDispachToProps = dispatch => {
return {
getStockData: () => dispatch({ type: "FETCH_STOCKS_ASYNC" }),
};
};
export default connect(mapStateToProps,mapDispachToProps)(Dashboard)
This is my reducer file:
import axios from 'axios'
import swal from 'sweetalert'
const initialState = {
stocks:[],
};
const reducer = (state = initialState, action) => {
const newState = { ...state };
switch (action.type) {
case 'FETCH_STOCKS_ASYNC':
console.log('reducer called')
const getLatestPrice = async()=>{
await axios.get(`http://api.marketstack.com/v1/eod/latest?access_key=8c21347ee7c5907b59d3cf0c8712e587&symbols=TCS.XBOM`)
.then(res=>{
console.log(res.data)
newState.stocks = res.data
})
.catch(err=>{
console.log(err)
})
}
getLatestPrice();
break;
}
return newState;
};
export default reducer;
this is my redux saga file:
import { delay } from "redux-saga/effects";
import { takeLatest, takeEvery, put } from "redux-saga/effects";
function* fetchStockData() {
yield delay(4000);
yield put({ type: "FETCH_STOCKS_ASYNC" });
}
export function* watchfetchStockData() {
yield takeLatest("FETCH_STOCKS_ASYNC", fetchStockData);
}
this is my store file:
import { createStore,applyMiddleware,compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { watchfetchStockData } from './sagas'
import reducers from './reducers'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducers,
compose(
applyMiddleware(sagaMiddleware),
window.devToolsExtension && window.devToolsExtension()
),
)
console.log(store.getState())
// only run those sagas whose actions require continuous calling.
//sagaMiddleware.run(watchfetchStockData);
export default store;
this is my index file which connects provider to all the components:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import './assets/main.css'
import App from './App';
import { Provider } from 'react-redux'
import { BrowserRouter as Router } from 'react-router-dom'
import store from './store'
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
Upvotes: 0
Views: 3003
Reputation: 42188
You misunderstand how the pieces are supposed to fit together here. I highly recommend that you read the redux best practices and learn more about what a reducer does and does not do. A redux reducer should not perform any actions. It merely takes the current state and returns the next state based on the data in an action object.
Luckily you are already using redux-saga, which is designed to handle side-effects such as API calls. The fetch needs to be executed in your saga, not in the reducer.
An API call typically involves dispatching three actions: start, success, and failure. From your component, you would only ever dispatch the "FETCH_STOCKS_START" action. The saga "takes" this action via takeLatest
and uses it to execute the fetch with its call
effect. When the fetch is finished, the saga uses put
to dispatch one of two result actions. It sends either type "FETCH_STOCKS_SUCCESS" with a property payload
containing an array of stocks, or type "FETCH_STOCKS_ERROR" with a property error
containing the error.
function* fetchStockData() {
yield delay(4000);
try {
const res = yield call( axios.get, `http://api.marketstack.com/v1/eod/latest?access_key=8c21347ee7c5907b59d3cf0c8712e587&symbols=TCS.XBOM`);
yield put({ type: "FETCH_STOCKS_SUCCESS", payload: res.data });
}
catch (error) {
yield put({ type: "FETCH_STOCKS_ERROR", error });
}
}
function* watchfetchStockData() {
yield takeLatest("FETCH_STOCKS_START", fetchStockData);
}
Relevant saga docs links: Error Handling and Dispatching Actions
I have added an isLoading
property to the state, which you can select to show a different UI when true
. We will update this property with all three actions.
const initialState = {
stocks: [],
isLoading: false
};
Your reducer serves only to update the raw data in the state based on these actions.
const reducer = (state = initialState, action) => {
switch (action.type) {
case "FETCH_STOCKS_START":
return {
...state,
isLoading: true
};
case "FETCH_STOCKS_ERROR":
console.error(action.error); // only temporary since you aren't doing anything else with it
return {
...state,
isLoading: false
};
case "FETCH_STOCKS_SUCCESS":
return {
...state,
stocks: action.payload,
isLoading: false
};
default:
return state;
}
};
Upvotes: 2
Reputation: 1156
You need to make a deep copy of the state in your reducer. Try this: const newState = JSON.parse(JSON.stringify(state))
Also, your reducer should not have any side-effects, it should just be responsible for updating the store. So your API call should happen in an asynchronous action creator, and then once the data is received back the action should dispatch another action which triggers the reducer to store the data in redux.
Upvotes: 1