I have a function in actions which makes an api call.
index.js of actions
export const GET_QUERY_LIST='GET_QUERY_LIST';
export const loadData=()=>{
return(dispatch)=>{
axios({
method:'GET',
url:Constants.URLConst+"/Query,
headers:Constants.headers
}).then((response)=>{
return dispatch({
type:GET_QUERY_LIST,
response
});
}).catch((e)=>{
console.log(e);
})
}
}
The same function I am using in Reducers like this-
index.js of reducers
function getQueryData(state=[],action){
switch(action.type){
case GET_QUERY_LIST:
return Object.assign({},state,{
result_get_query:action.response
})
default:
return state
}
}
const data=combineReducers({
getQueryData
})
export default data;
I am using this reducer function in my js file, say home.js as follows
import React,{Component} from 'react';
import './App.css';
import {loadData} from './actions';
import {connect} from 'react-redux';
import Header from './Header.js';
// import './Home.css';
export class Home extends Component{
constructor(props){
super(props);
this.state={
querylist:[]
}
this.handleChange=this.handleChange.bind(this);
}
componentDidMount(){
this.props.loadData();
}
handleChange(){
this.setState({
querylist:this.props.resultCame
})
}
render(){
console.log("home.js",this.state.querylist);
//this.props.resultCame.resultMeta.data.ProfileData.UserId
if(this.props.resultCame.resultMeta){
return(
<div>
<Header/>
<div>{this.handleChange()}</div>
</div>
)
}else{
return null;
}
}
}
const mapStateToProps=(state)=>{
return{
resultCame:state.getQueryData
}
}
export default connect(mapStateToProps,{
loadData:loadData
})(Home);
I am storing the data in resultCame variable. In the render function if I do,
console.log(this.props.resultCame)
then the result comes which means the api is getting called properly, but I want to store the result in a state variable.So in componentDidMount() after calling the loadData(), I am setting the state in querylist.
But this.state.querylist is coming empty which means the data is not getting set.
How to set the data properly?
You are using axios which works asynchronously. This means that it waits for the response to arrive from the API before doing anything. When you use ComponentDidMount() and call the action and then immediately call setState, the probable cause of this.state.queryset being empty is because it is assigned before axios has received anything from API call. You will have to setState after receiving response from axios, not just running it.
You should use ComponentWillReceiveProps(nextProps) to update the state as the reply of the axios success call will come in the updated next props as you will be receiving them as the redux store on dispatch of
type:GET_QUERY_LIST, payload.
Related
I am learning and trying to understand Redux with React
I have following test code in index.js and App.js
Where it reads API URL I make a request to API to get some data.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import './index.css';
import App from './components/App';
import { createStore } from 'redux';
const initialState = {
events: [],
isViewingEvent : false,
eventIndex : 0,
eventProducts : []
}
//STORE
async function reducer(state = initialState, action) {
switch(action.type) {
case "GETEVENTS":
{
const response = await fetch("API URL", {
method: 'get',
headers: {'Content-Type':'application/json','Accept': 'application/json'}
})
.then(function(response) {
return response.json();
})
console.log(response);
return state.events = response;
};
default:
return state;
}
}
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Now App.js
import { Component } from 'react';
import { connect } from 'react-redux'
class App extends Component {
handleOnClick() {
this.props.dispatch({ type : "GETEVENTS" })
}
render() {
return <div>
<header>
<button onClick={() => this.handleOnClick()}>TEST</button>
</header>
</div>
}
}
const mapStateToProps = (state) => {
console.log(state.events);
return {
events: state.events
}
}
export default connect(mapStateToProps)(App);
I simply wish to test if I can update state with Clicking a button, by calling the dispatch function.
dispatch function makes a API get request and should update state with its response.
I have console.log output in dispatch function to see what the response is, and it will show it correctly. I will get valid data from API.
Another console.log is at mapStateToProps function in App.js, where I'd like to get that events data as a prop from state. This will always show me undefined when I click the button. I can see I get valid response from API but I can never get that data as prop into App.js
What am I doing wrong here? I can think of that mapStateToProps happens before I get async response from API. So its mapping props before state update? But then if I click it the second time I should see data. But I dont, so Im missing something here which I do not understand.
this is my console.log output
You are doing an API request in the reducer here, which is not only conceptually something you should not do in a reducer (side effects are not allowed to happen there), but in this case also something that is just plain impossible, because it turns the whole state slice into a promise.
You need to handle API requests & co outside the reducer in a middleware, for example a thunk. I recommend looking into the Async Logic and Data Fetching chapter of the official redux tutorial.
You are changing the state in your reducer function directly which is the wrong way to do it as it is immutable. Read about it here: Immutable patterns redux
async function reducer(state = initialState, action) {
...
console.log(response);
return state.events = response; // here is the problem
};
...
}
Try one of the suggested approaches on the link provided.
I am using redux-thunk to create an asynchronous action so that I can call an API method using 'fetch'. I have attached my redux store to my root App component like this:
import React, { Component } from 'react';
import {Provider} from 'react-redux';
import Grid from '../src/Components/Grid/Grid';
import rootReducer from './Reducer';
import './App.css';
import { createStore,applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import logger from 'redux-logger';
class App extends Component {
render() {
return (
<Provider store={createStore(rootReducer,{},applyMiddleware(ReduxThunk))}>
<div className="App">
<Grid></Grid>
</div>
</Provider>
);
}
}
export default App;
I have defined my asynchronous action creator as shown here:
import config from 'react-global-configuration';
import fetch from 'cross-fetch';
export default function textChangeActionCreator(data){
console.log('in action createor'+data)
return async function(dispatch){
console.log('fetching....')
return await fetch(data).
then((data)=>{
console.log(data);
dispatch({type:'text_change',payload:data})
}).
catch((err)=>{console.log(err)})
}
}
My problem is, when I call the above action creator from any component it gets called properly however, the second console.log('fetching....') is not showing up and the fetch() call seems to not be happening.
What am I doing wrong?
Update
Here is the component code which is calling the action creator:
class Grid extends React.PureComponent{
constructor(props)
{
super(props);
this.state={dynamicButtonText:'',seachText:''}
this.updateDynamicText=this.updateDynamicText.bind(this);
this.onTextChanged=this.onTextChanged.bind(this);
this.updateSearchText=this.updateSearchText.bind(this);
}
onTextChanged()
{
textChangeActionCreator(searchURL);
}
render()
{....}
}
const mapStateToProps=(state)=>{return state;}
export default connect(mapStateToProps,{textChangeActionCreator})(Grid);
This is usually caused by incorrect action invocation. Make sure that you're you're both calling the textChangeActionCreator() action creator, and passing the result to dispatch() in the propsToDispatch object that you pass to connect():
export default connect(mapStateToProps, dispatch => {
/* Call textChangeActionCreator() and pass the result to dispatch() */
return {
textChange : (data) => dispatch(textChangeActionCreator(data))
}
})(YourComponent);
Update
Just reviewing your code, it seems you're calling the action creator from onTextChanged() directly. You need to call it via the component's props like so:
onTextChanged()
{
this.props.textChangeActionCreator(searchURL);
}
Try something like this:
const textChangeActionCreator = (data) => {
console.log(`in action createor ${data}`);
return (dispatch) => {
console.log('fetching...');
return fetch(data)
.then((data)=>{
console.log(data);
dispatch({ type:'text_change', payload:data });
})
.catch((err)=>{ console.log(err); });
};
};
You don't need await or async, because you are returning a promise, you should await where you are calling this function.
Also, you are using currying here, so you have to call something like:
const fetchedData = await textChangeActionCreator(data)(dispatch);
I just started to work on React Js and Redux-Thunk. Currently, I am trying to fetch data from a url using redux-thunk. I got data successfully but the issue is that it renders undefined data twice, then it gives me the desired data in props.
Here is my code.
In Actions
index.js
function getData() {
return {
type: 'FETCH'
}
}
function getSuccess(data) {
return {
type: 'FETCH_SUCCESS',
payload: data
}
}
function getFailed(err) {
return {
type: 'FAILED',
payload: err
}
}
export function fetchData() {
const thunk = async function thunk(dispatch) {
try {
dispatch(getData());
const body = await fetch("http://jsonplaceholder.typicode.com/users")
const res = await body.json();
console.log("Thunk", res);
dispatch(getSuccess(res));
}
catch(err) {
dispatch(getFailed(err));
}
}
return thunk;
}
In Reducers fetch.js
const initialState = {
state: []
}
export default function(state=initialState , actions) {
switch(actions.type) {
case 'FETCH':
return {
...state
}
case 'FETCH_SUCCESS':
return {
...state,
data: actions.payload
}
case 'FAILED':
return {
...state
}
default:
return state;
}
}
Reducers Index.js
import fetch from './fetch';
import { combineReducers } from 'redux';
const rootReducer = combineReducers ({
fetch
});
export default rootReducer;
App.js
import React, { Component } from 'react';
import './App.css';
import Main from './component/Main';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
console.log("STore", store.getState());
class App extends Component {
render() {
return (
<Provider store={store}>
<Main/>
</Provider>
);
}
}
export default App;
Main.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
class Main extends Component {
componentWillMount() {
const { fetchData } = this.props
fetchData();
}
render() {
let mydata = this.props.data.data;
console.log("Data .....<>", mydata);
return(
<div>
Main
</div>
);
}
}
const mapStateToProps =(state)=> {
return {
data: state.fetch
}
}
export default connect(mapStateToProps, {fetchData: actions.fetchData})(Main);
Output Screen Shot
Please let me know what i am doing wrong. Any help will be appreciated.
Thanks
This behavior is correct. You're doing everything in the normal way, except calling async operations in componentWillMount method instead of componentDidMount.
Read more here about it.
You need to know, that when you are using componentDidMount - you are handle this in a safe way due to commit phase in component lifecycle and it means that your request will be guaranteed trigger once instead of possible several times, which can be in render phase.
See here the visual diagram to understand this more.
Regarding several renderings - it can be explained easily.
First time, when your component is mounting you are calling asynchronous operation and it needs more time to load data than for component mounting. Thats why you are accessing data property of your initialState (which is empty array), and getting undefined.
When you response is ready and actions is being dispatched, redux trigger re-render of connected components with new state.
If you want to make your async-await syntax works you should make lifecycle with async keyword as well and await your request inside.
NOTE: It's safe, but in several cases it might cause unexpected bugs, so keep in mind. Nevertheless, I don't recommend to use it in a such way. Read about this topic in the another thread at SO.
I advice you to create some isLoading boolean status in your reducer and keep track whether data is loaded or not.
Good luck! Hope it will helps!
There is nothing wrong with your code, but there is one unnecessary action.
Why do you see two times undefined and then you see your data?
First one is coming from the initial render. You are making an async dispatching in your componentWillMount so render does not wait for it, then try to log your data. At that time it is undefined.
Your fetchData dispatches two actions: getData and getSuccess. You see second undefined because of getData action since it returns current state which props.data.data undefined at that time again.
You see the data since getSuccess updates your state now.
If you want to test this comment out getData in your fetchData function, just use getSuccess and change your console.log in the render like that:
mydata && console.log("Data .....<>", mydata);
I think getData action is unnecessary here, it does not do anything beside returning the current state. Also, try to use componentDidMount instead of componentWillMount since it will be deprecated in version 17.
I would like to fetch a an api from the state which was returned from the reducer.
This is my container: PokeDetailApi.js
import React, {Component} from "react";
import {connect} from "react-redux";
import axios from "axios";
class PokeDetailApi extends Component{
constructor(){
super();
this.state = {
pokedetails:""
}
}
componentDidMount(){
axios.get({this.props.pokemon.url}).then( (obj) => {
this.setState({
pokedetails: obj.data.results
});
});
}
render(){
if(!this.props.pokemon){
return (
<div>
<h1>Please select a pokemon.</h1>
</div>
)
}
// const url = this.props.pokemon.url;
const urlapi = this.state.pokedetails;
console.log(url);
console.log(urlapi);
return(
<div>
<p>{this.props.pokemon.name}</p>
</div>
)
}
}
function mapStateToProps(state){
return {
pokemon: state.selected_pokemon
}
}
export default connect(mapStateToProps)(PokeDetailApi);
{this.props.pokemon.url} returns a url of a specific pokemon. I want to display all the details of that pokemon using that url which is found here: https://pokeapi.co/
This is the returned object from the reducer:
{
name:"bulbasaur"
url:"http://pokeapi.co/api/v2/pokemon/1/"
}
What is the correct way of accessing the objects from an api from a reducer using axios?
You are talking about reducers, but calling fetch directly in your component.. you should be calling an action which does the fetching.
Anyway aside from this you can perform another fetch in your .then using the result of the first fetch (pokedetails), not in your render:
axios.get(this.props.pokemon.url)
.then(res => res.data.results)
.then(axios.get) // fetch on pokedetails
.then(console.log) // console.log result, here you could setState results
I'm in the process of learning React/Redux and I've run into an issue while converting a single page web app of mine to the framework/paradigm. What i'm trying to do is let the initial state of the web app have an array that is to be populated by objects from an API request, this array is called "makes". I want to do this so I can display "makes" from the API on the first page of the website upon it loading. This can be seen in the index.js file below:
import App from './App';
import './index.css';
import configureStore from './redux/store'
import { Provider } from 'react-redux'
let makesUrl = 'the url to the API'
let cached = cache.get(makesUrl)
let makes = []
// Future cache setup.
if(!cached) {
console.log("NOT CACHED")
}
else {
console.log("CACHED")
}
// Get the makes via API.
fetch(makesUrl).then((response) => {
// Pass JSON to the next 'then'
return response.json()
}).then((json) => {
json.makes.forEach((make) => {
makes.push(make)
})
})
let initialState = {
grids: [],
makes: makes
}
let store = configureStore(initialState)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
The state and dispatch are mapped to the props and passed down to the components that need them in my App.js file as such:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import './App.css'
import Head from './components/Head'
import Middle from './components/Middle'
import Foot from './components/Foot'
import actions from './redux/actions'
class App extends Component {
render() {
return (
<div className="App">
<div>
<Head />
<div className="middle container">
<Middle actions={this.props.actions} makes={this.props.makes}/>
</div>
<Foot />
</div>
</div>
);
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
At all points, in the chrome dev tools, I can see that the API call was successful and the state is shown to have makes: Array[62] with 62 objects inside, however if I console log the length of the array in the component that these makes are passed down to as seen below, it says the length is 0, and each index of the array is undefinded.
import React, { Component } from 'react'
class MakeButtons extends Component {
handleClick(event) {
event.preventDefault()
console.log("CLICK")
}
render() {
return(
<div>
{
console.log(this.props.makes.length)
}
</div>
)
}
}
export default MakeButtons
This is essentially what I've been trying to figure out for the past couple hours, so I can use the forEach or map function to return links/buttons for each of the objects in the array, however at the moment this does not work, despite dev tools showing the state to be normal. Any help/explanations would be greatly appreciated!
So you really just need to set up an action/reducer for your init, then you can call it in componentWillMount or componentDidMount because they are only called once upon loading your app.
In the way you are doing it now you have a fetch and an app using the data from the fetch that is not waiting for the async call to finish before it starts the app.
You just want to create your init action, so your action creator would be something like :
import * as services from './services';
function initMyAppDispatcher(type, data, status) {
return {
type,
data,
status
};
}
export function initMyApp(dispatch, makesUrl) {
dispatch(initMyAppDispatcher(actions.INIT_APP, {}, 'PENDING');
return services.myInitCall(makesUrl)
.then((data) =>
dispatch(initMyAppDispatcher(actions.INIT_APP, data, 'SUCCESS'),
(error) =>
dispatch(initMyAppDispatcher(actions.INIT_APP, error, 'FAILURE'),
)
.catch(yourErrorHandling);
}
Services.myInitCall is however you want to implement it, just make sure you export it back as a promise. In your case you can replace that line with fetch(makesUrl) as long as you have access to it there. Then having it set up like this, you can set your reducers like so :
case actions.INIT_APP:
if (action.status) {
switch (action.status) {
case PENDING:
//you can use this to create a "loading" state like a spinner or whatever
return state;
case SUCCESS:
// note: this is immutablejs syntax, use whatever you prefer
return state.set('makes', action.data);
case FAILURE:
return state;
default:
return state;
}
}
return state;
One thing to note is I have dispatch in my action creators because I use mapDispatchToProps in place of mapToProps. So your container looks something like this :
import * as actionCreators from './action-creators';
function mapStateToProps(state) {
return {
makes: state.get('makes')
};
}
function mapDispatchToProps(dispatch, ownProps) {
return {
initMyApp: actionCreators.initMyApp.bind(null, dispatch)
};
}
export default function(component = Component) {
return connect(mapStateToProps, mapDispatchToProps)(component);
}
then in your component componentWillMount or componentDidMount, pass in and call your init function
componentDidMount() {
this.props.initMyApp();
}