ReactJs Redux Data Fetch - javascript

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.

Related

Updating Redux state and getting updated data into component with mapStateToProps ERROR

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.

Confused with REDUX actions and reducers

so I am trying to refactor some code from my previous question:
React: How to update one component, when something happens on another component
So I started digging deep into the existing code template to see how it was implemented.
I found a reducers.js where I added a new reducer: ActiveTenant
import Auth from './auth/reducer';
import App from './app/reducer';
import ThemeSwitcher from './themeSwitcher/reducer';
import LanguageSwitcher from './languageSwitcher/reducer';
import ActiveTenant from './activetenant/reducer';
export default {
Auth,
App,
LanguageSwitcher,
ThemeSwitcher,
ActiveTenant
};
That new reducer is like this:
import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';
const initState = new Map({
tenantId: ''
});
export default function(state = initState, action) {
switch (action.type) {
case actions.SET_TENANT_ACTIVE:
{
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
.then(response =>{
if(response.status === 200){
console.log("Tenant activated");
}else{
throw "error";
}
})
.catch(error => {
console.error(error);
});
return state.set('tenant', state.Name);
}
default:
return state;
}
}
and actions for that reducer
const actions = {
SET_TENANT_ACTIVE: 'SET_TENANT_ACTIVE',
setTenantActive: () => ({
type: actions.SET_TENANT_ACTIVE
}),
};
export default actions;
Then from the component itself, I need to call the action when a row is selected on the front end, so I have refactored the commented code, into one line.
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';
const { setTenantActive } = actions;
class ListTenants extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.id,
TestSiteCollectionUrl: row.TestSiteCollectionUrl,
TenantName: row.TenantName,
Email: row.Email
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'TenantName',
dataIndex: 'TenantName',
key: 'TenantName',
},
{
title: 'TestSiteCollectionUrl',
dataIndex: 'TestSiteCollectionUrl',
key: 'TestSiteCollectionUrl',
},
{
title: 'Email',
dataIndex: 'Email',
key: 'Email',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRows[0].TenantName != undefined){
console.log(selectedRows[0].TenantName);
const options = {
method: 'post'
};
setTenantActive(selectedRows[0].TenantName);
/* adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+selectedRows[0].TenantName.toString(), options)
.then(response =>{
if(response.status === 200){
Notification(
'success',
'Tenant set to active',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Tenant not activated',
error
);
console.error(error);
}); */
}
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
However, its not clear to me the relationship between the action and the reducer, if I check the debugger the action is executed, and none parameter is received, but the reducer is never executed.
DO i have to put a dispatch somewhere?, what I am missing in this puzzle?
So the first thing to understand is the Redux Cycle:
Action Creator-->Action-->dispatch-->Reducers-->State
Action Creator: An action creator is a function that is going to create or return a plain JavaScript object knowns as an Action with a type property and payload property which describes some change you want to make on your data.
The payload property describes some context around the change we want to make.
The purpose of an Action is to describe some change to the data inside our application.
The Action Creator is to create the Action.
The dispatch function is going to take in an Action and make copies of that object and pass it off to a bunch of different places inside our application which leads us to the Reducers.
In Redux, a reducer is a function responsible for taking in an Action. Its going to process that Action, make some change to the data and return it so it can be centralized in some location.
In Redux, the State property is a central repository of all information produced by our reducers. All the information gets consolidated inside the State object so our React application can easily reach into our Redux side of the app and get access to all the data inside the application.
So this way the app does not have to go around to each separate reducer and ask for the current State.
So digest that for a couple of minutes and then look at your architecture.
Let's skip over to reducers.
Reducers are called with an Action that was created by an Action Creator. The reducer will take a look at that Action and decide whether it needs to modify some data based on that Action.
So in other words, the job of a reducer is not to execute API requests but to process actions sent to it by the action creator.
So instead of this:
import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';
const initState = new Map({
tenantId: ''
});
export default function(state = initState, action) {
switch (action.type) {
case actions.SET_TENANT_ACTIVE:
{
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
.then(response =>{
if(response.status === 200){
console.log("Tenant activated");
}else{
throw "error";
}
})
.catch(error => {
console.error(error);
});
return state.set('tenant', state.Name);
}
default:
return state;
}
}
Your reducer should look something like this:
import { SET_TENANT_ACTIVE } from "../actions/types";
const initialState = {
tenantId: ''
};
export default (state = initialState, action) {
switch (action.type) {
case SET_TENANT_ACTIVE:
return {...state, [action.payload.id]: action.payload };
default:
return state;
}
}
Then inside your action creators file, you should have an action creator that looks something like this:
import axios from 'axios';
import { SET_TENANT_ACTIVE } from "../actions/types";
export const setTenant = id => async (dispatch) => {
const response = await axios.post(`/tenants/${id}`);
dispatch({ type: SET_TENANT_ACTIVE, payload: response.data });
};
You also need to learn about Redux project structure because after the above refactor, you are missing how to wire all this up to your component. In your component file there is no connect() function which also requires the Provider tag and you have none of that.
So for this I recommend first of all your set up your folder and file structure like so:
/src
/actions
/components
/reducers
index.js
So inside your index.js file it should look something like this:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import reduxThunk from "redux-thunk";
import App from "./components/App";
import reducers from "./reducers";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducers,
composeEnhancers(applyMiddleware(reduxThunk))
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
So your goal here is to ensure that you get that Provider tag at the very top of your component hierarchy and ensure that you pass it a reference to your Redux store that gets all the reducers loaded up into it.
So above I have created the store and passed it our set of reducers and it will return back to you all your applications State.
Lastly, what you see above is I created an instance of <Provider> and wrapped the <App /> component with it and then you want to pass the <Provider> component is a single prop called store. The store is the result of calling createStore() and calling the reducers.
The <Provider> is what interacts with the Redux store on our behalf.
Notice, I also have wired up Redux-Thunk that J. Hesters mentioned, you are making an ajax request as far as I can see from your code which is why I offered an asynchronous action creator for you, which means you will need Redux-Thunk or some middleware like that, let me not offend the Redux-Saga fans, so you have those two choice at least. You seem relatively new to Redux, just go with Redux-Thunk.
Now you can use the connect() component inside your component file to finish wiring up those action creators and reducers to your component or your React side of the application.
import React, { Component } from 'react';
import { connect } from "react-redux";
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';
After importing connect, you create an instance of it below:
export default connect()(ListTenants);
Please don't argue with me on the above syntax (actually had a former student report me to administrators for using this syntax as evidence of not knowing what I was doing).
Then you need to configure this connect() React component by adding mapStateToProps if you are going to need it, but definitely pass in actions as the second argument to connect(). If you realize you don't need mapStateToProps, then just pass in null as the first argument, but you can't leave it empty.
Hope all this was helpful and welcome to the wonderful world of React-Redux.
You are using reducers wrong. Reducers are supposed to be pure. Yours has side-effects showing that you haven't understood Redux, yet.
Instead of writing down a solution for you (which would take forever anyways since one would have to explain Redux in total), I suggest you invest the 3 hours and go through the Redux docs and follow the tutorials (they are great).
Afterwards you might want to look into Redux Thunk. But, you might not need thunks.
PS: (Small thing to bring up, but I haven't seen anyone using Maps in Redux. Is there a reason you do that? You might want to use plain objects instead.)
Your action is not correct you should pass an active tenant name as parameter.
Ref. https://redux-starter-kit.js.org/api/createaction
We could have written the action types as inline strings in both places.
The action creators are good, but they're not required to use Redux - a component could skip supplying a mapDispatch argument to connect, and just call this.props.dispatch({type : "CREATE_POST", payload : {id : 123, title : "Hello World"}}) itself.
Ref. https://redux-starter-kit.js.org/usage/usage-guide

Async with redux works? But still gives error, should I ignore it?

I am using a mix of laravel, react.
Attempting to implement the redux-thunk middleware.
I am having issues with async calls.
I would like to use jquery for ajax (which successfully retrieve the API data, but I am getting an error which reads,
"Error: dispatch is not a function", meaning I cannot make any changes to the store. From what I understand dispatch and GetState is passed through the thunk middle-ware. Correct?So why am I not able to use the function?
It also gives me an error which reads: "Error: Actions may not have an undefined "type" property. Have you misspelled a constant?"
Another issue that arises, after trying to deal with the above issue is: "Error: Actions must be plain objects. Use custom middleware for async actions."
I have read many similar questions but I still cannot seem to get it to work.
"Actions must be plain objects. Use custom middleware for async actions."
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import FilterBar from './SideBar/FilterBar';
import Store from '../redux/store/mainStore';
import { REMOVE_ATTRIBUTE_FILTER,ADD_ATTRIBUTE_TO_FILTER, removeAttribute } from '../redux/actions/actions';
Store.subscribe(()=>{
console.log("store changes", Store.getState())
})
console.log(Store.getState());
Store.dispatch({
type:ADD_ATTRIBUTE_TO_FILTER,
payload:{'make':23}
})
if (document.getElementById('InventoryDisplay')) {
ReactDOM.render(
<Provider store={Store}>
<FilterBar/>
</Provider>
,document.getElementById('FilterBar'));
}
mainstore.js
```
import { createStore,applyMiddleware,combineReducers,compose } from 'redux';
import thunk from 'redux-thunk';
import {inventoryFilter,availableAttributes} from '../reducers/reducer';
const Store = createStore(
///combine imported reducers
combineReducers({
activeFilter:inventoryFilter,
availableAttributes:availableAttributes
},
///initilize store
{},
applyMiddleware(thunk)
));
export default Store;
```
actions.js what is relevant
```
///first case
const getAttributes2 = (dispatch) => {
return(
$.ajax('/getFilteredAttributes/', {
type: 'GET',
dataType : 'json'
}).done(response => {
dispatch(addAttribute("make",32));
}).fail((xhr, status, error) => {
console.log("failed");
})
)
};
///second case
const getAttributes = (dispatch) => {
return ()=>{}
}
export {
ADD_ATTRIBUTE_TO_FILTER,addAttribute,
REMOVE_ATTRIBUTE_FILTER,removeAttribute,
GET_INVENTORY,getInventory,
GET_AVAILABLE_ATTRIBUTES,getAttributes,
}
```
component connect to store that dispatch action
```
import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import * as ActionCreators from '../../../redux/actions/actions';
class DropDownList extends React.Component{
componentDidMount(){
this.props.addAttributes("make",32)
this.props.getAttributes()
this.props.removeAttributes("make",32)
}
render(){
return(
<div>
</div>
)
}
}
function mapStatesToProps(state){
return{
activeFilters:state.activeFilter,
availableAttributes:state.availableAttributes
}
};
const mapDispatchToProps = dispatch => {
return {
addAttributes: (type,value) => {
dispatch(ActionCreators.addAttribute(type,value))
},
removeAttributes: (type,value) => {
dispatch(ActionCreators.removeAttribute(type,value))
},
getAttributes: () => {
dispatch(ActionCreators.getAttributes())
}
}
}
DropDownList.propTypes = {
availableAttributes: PropTypes.object.isRequired,
activeFilters: PropTypes.object.isRequired,
}
export default connect(mapStatesToProps,mapDispatchToProps)(DropDownList)
```
My solution for case one for the second error was to just place the ajax function call into in object that contains the "type" property. Like such
return (
{
type: "Something",
$.ajax('/getFilteredAttributes/', {
type: 'GET',
dataType: 'json'
}).done(response => {
dispatch(addAttribute("make", 32));
}).fail((xhr, status, error) => {
console.log("failed");
})
})
The ajax call is made, but dispatch still isn't available, I am lost and looking for the best solution? Maybe I am overthinking something or missed a minor detail. I have tried other solutions but none has worked for me.
Please Help.
It's pretty difficult to know exactly what's going on, but I definitely wouldn't recommend ignoring / hacking round an error you're getting using a common feature of a popular library. It's important to get your implementation right for simplicity moving forward.
It looks to me like the way you are using thunk is a little odd.
The function that is returned by the action you dispatch has dispatch and getState as arguments:
In your case your thunk action might look like this
In your actions.js:
export function getAttributes2(){
return function(dispatch, getState){
// you could dispatch a fetching action here before requesting!
return $.ajax('/getFilteredAttributes/', {type: 'GET', dataType: 'json'})
.done(response => dispatch(saveTheResponseAction(response)))
.fail((xhr, status, error) => console.log("failed"))
}
Mapping that thunk function to your props:
import {getAttributes2} from '../../../redux/actions/actions';
const mapDispatchToProps = dispatch => {
return {
getAttributes2: () => dispatch(getAttributes2()),
}
}
This way you can dispatch an action from the .done part of your api call with the result in it, you can respond to errors, you can even dispatch an action before returning your api call to let redux know you've requested but not yet received the data, allowing you to do all kinds of loading states.
I hope this helps, let me know :)

react ajax call is undefined at first

I'm using Axios to make an AJAX call and the data returns undefined and then it consoles the array after a few seconds. I've tried componentDidMount and componentWillMount. I've tried making a constructor with initial state as the props. getInitial state is deprecated unless using React.createClass.
Here's my code, anything helps!
actions/index.js
import axios from 'axios';
import { FETCH_STRAINS } from './types';
const ROOT_URL = `https://www.cannabisreports.com/api/v1.0/strains?sort=name&page=3`;
export function fetchStrains() {
return dispatch => {
axios.get(ROOT_URL)
.then(response => {
dispatch({
type: FETCH_STRAINS,
payload: response.data.data
})
})
.catch( error => console.log(error));
}
}
reducer/index.js
import { FETCH_STRAINS } from '../actions/types';
import initialState from './initialState';
export default function(state = initialState.strains, action) {
switch(action.type) {
case FETCH_STRAINS:
return { ...state, strains: action.payload };
default:
return state;
}
}
app.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from './actions';
import './App.css';
class App extends Component {
componentWillMount() {
this.props.fetchStrains();
}
render() {
return (
<div className="App">
{this.props.strains === undefined ? console.log("this is undefined") : console.log(this.props.strains)}
</div>
);
}
}
function mapStateToProps( state ) {
return { strains: state.strains.strains }
}
export default connect(mapStateToProps, actions)(App);
The issue you're facing isn't because your code is wrong. From a quick glance it looks like you're doing it right.
The problem is that your app exists and is showing before you have all the data ready. The axios call takes a very long time to complete. Until it's done, your app is showing something to the user, whether you like it or not.
So between startup and data arrival, strains is going to be undefined. You'll have to decide what to show the user while they're waiting. A common solution is a spinner.
You need to use async actions & need to import thunk-middleware while you combine your reducers.
export function fetchStrains() {
// Thunk middleware knows how to handle functions.
// It passes the dispatch method as an argument to the function,
// thus making it able to dispatch actions itself.
return function (dispatch) {
// First dispatch: the app state is updated to inform
// that the API call is starting.
// The function called by the thunk middleware can return a value,
// that is passed on as the return value of the dispatch method.
// In this case, we return a promise to wait for.
// This is not required by thunk middleware, but it is convenient for us.
axios.get(ROOT_URL)
.then(response => {
dispatch({
type: FETCH_STRAINS,
payload: response.data.data
})
})
.catch( error => console.log(error));
}
}

React/Redux can't iterate over array in state

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();
}

Categories