I have been building a React-Redux application to display some weather data (openweathermap.org API) if a button gets clicked.
Somehow when the Container is rendered the data are not arriving, even if I managed to handle the promise using Axios.
As you can see in the console.log, the 'tempo' object is empty once it arrives in the container. Then, once the button is clicked, the request correctly arrives on the container and 'tempo' gets the data I want to render.
The problem occurs when I try to access those properties arrived after that the onClick() event was fired. They do not exist yet, so the whole components throw an error.
I think there is some problem with the async await response managed in the Axios request but I cannot find it.
Sorry if the explanation was not properly technical.
I remain at disposal for clarifications.
Action Creator with the API request
import axios from 'axios';
export const GET_CECCIOLA = 'GET_CECCIOLA';
export function submitWeather() {
const url = 'https://api.openweathermap.org/data/2.5/weather?appid=ce6111c5cb481755173214d6bf62f51a&q=Cecciola,it';
const cecciola = axios.get(url);
return {
type: 'GET_CECCIOLA',
payload: cecciola
}
}
Container responsible for the rendering when button is clicked
import React, { Component } from 'react';
import {connect} from 'react-redux';
class CecciolaTime extends Component {
render() {
console.log(this.props.tempo)
return (
<div>
<h2>{this.props.tempo}
</h2>
</div>
);
}
}
function mapStateToProps ({ tempo }) {
return { tempo };
}
export default connect(mapStateToProps)(CecciolaTime);
Container with the onClick() method
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {submitWeather } from '../actions/index';
class SearchBar extends Component {
constructor(props) {
super(props)
this.getWeather = this.getWeather.bind(this);
}
getWeather(e) {
e.preventDefault();
this.props.submitWeather(e);
}
render() {
return (
<form>
<button onClick={this.getWeather}>
tempo a Cecciola
</button>
</form>
)
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ submitWeather }, dispatch);
}
export default connect(null, mapDispatchToProps)(SearchBar);
Reducer
import { GET_CECCIOLA } from '../actions/index';
export default function(state = [], action) {
switch (action.type) {
case GET_CECCIOLA:
return [action.payload.data, ...state];
}
return state;
}
Reducer_Index
import { combineReducers } from 'redux';
import CecciolaReducer from './cecciola_reducer';
export default combineReducers({
tempo: CecciolaReducer
})
Store (I am using Redux-Promise as middleware)
import React from 'react';
import './index.css';
import App from './components/App';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers'
import ReduxPromise from 'redux-promise';
const storeWithMiddleware = applyMiddleware(ReduxPromise)(createStore);
render(
<Provider store={storeWithMiddleware(rootReducer)}>
<App />
</Provider>,
document.getElementById('root')
)
If you are trying to display non-existing property in tempo object and it fails - the most common way to handle it - just check if this property exists, like that:
import React, { Component } from 'react';
import {connect} from 'react-redux';
class CecciolaTime extends Component {
render() {
const { name } = this.props.tempo
return (
<div>
{/* Check if name exists then display */}
<h2>{name && name}</h2>
</div>
);
}
}
function mapStateToProps ({ tempo }) {
return { tempo };
}
export default connect(mapStateToProps)(CecciolaTime);
NOTE: You're trying to render an object { this.props.tempo } in h2 tag, which can cause another error.
UPDATE (from comments): I've find the issue, it was because you're setting result into array and it's actually keeped in 0 index in array. So you can access to your variables via this.props.tempo[0].name. To avoid this mess just use object instead of array as initial state, it's much easier to handle then.
I've created sandbox for you with working code (click to see).
Hope it will helps.
Related
Problem
I wired up my react application with a Redux store, added an api action to gather data from my backend including middleware redux-promise. Most everything seems to work as I can see my store in the React web editor along with the combine reducer keys. When I have my action called, it works and console logs the completed promise. However, my reducers never run. I thought it was an issue with my dispatch on the main container, but I've tried every way that I can think of at this point - regular dispatch() and bindActionCreators. HELP!
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import promiseMiddleware from 'redux-promise';
import RootReducer from './reducers';
const createStoreWithMiddleware = applyMiddleware(promiseMiddleware)(createStore)
let store = createStore(RootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));`
Combine Reducers
import { combineReducers } from 'redux';
import ReducerGetPostings from './reducer_get_postings'
const rootReducer = combineReducers({
postingRecords: ReducerGetPostings
})
export default rootReducer;
Reducer
import { FETCH_POSTINGS } from '../actions/get_postings'
export default function (state = null, action) {
console.log('action received', action)
switch (action.type) {
case FETCH_POSTINGS:
return [ action.payload ]
}
return state;
}
Action API
import axios from 'axios';
import { url } from '../api_route';
export const FETCH_POSTINGS = 'FETCH_POSTINGS'
export function fetchpostings() {
const postingRecords = axios.get(`${url}/api/postings`)
console.log('Postings', postingRecords)
return {
type: FETCH_POSTINGS,
payload: postingRecords
};
}
Container
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { fetchpostings } from '../../actions/get_postings.js'
class Dashboard extends Component {
//....lots of other functionality already built here.
componentDidMount() {
axios.get(`${url}/api/postings`)
.then(res => res.data)
.then(
(postingRecords) => {
this.setState({
postingData: postingRecords,
postingOptions: postingRecords
});
},
(error) => {
this.setState({
error
})
}
)
// primary purpose is to replace the existing api call above with Redux Store and fetchpostings action creator
fetchpostings()
}
}
function mapDispatchToProps(dispatch) {
// return {actions: bindActionCreators({ fetchpostings }, dispatch)}
return {
fetchpostings: () => dispatch(fetchpostings())
}
}
export default connect(null, mapDispatchToProps)(Dashboard);
You are not dispatching your action, when you call fetchpostings() in componentDidMount you are calling the method imported from actions/get_postings.js, not the method that will dispatch.
Try this.props.fetchpostings() instead.
You also did not bind state to props you need to do that as well.
I am calling my action 'GetMobileList' on page load. but after the action is executed nothing gets called like mapstatestoprops or any reducer. They get called before action is called and after the action is called nothing happens. I am binding a list on page load.After the action is completed I am rendering the list component via props of the parent component.
import React, { Component } from 'react';
import GetProductsList from './list'
import GetMobileList from '../actions/index'
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
class Add extends Component
{
constructor (props) {
super(props);
}
componentDidMount()
{
GetMobileList();
}
render(){
return (
<div>
<GetProductsList mobilesList={this.props.mobiles}/>
</div>
)
}
}
function mapStateToProps (state) {
return {
mobiles: state.mobiles
}
}
export default connect (mapStateToProps,{GetMobileList}) (Add)
......................................................
export default function GetMobileList()
{
var mobiles= JSON.parse(localStorage.getItem('mobiles'));
return {
type:'MOBILE_LIST',
payload : mobiles
};
}
Index.js for Reducer
import {combineReducers} from 'redux';
import MobilesList from './mobileslist';
const rootreducer = combineReducers({
mobiles:MobilesList
});
export default rootreducer;
REDUCER -
export default function (state=null,action)
{
switch(action.type)
{
case 'MOBILE_LIST':
{
return action.payload
}
}
return state;
}
in matchDispatchToProps, GetMobileList becomes a prop.
So instead of calling as GetMobileList(), try this.props.GetMobileList()
for starting, i saw many questions which looks like mine on stackoverflow but i think i miss something, this is why i'm asking this question.
I have a Maincomponent in my app which add datas to my store using the action addDatas.
This part works, i can access to my store in the context and in the children of MainComponent with this.props.
But when i go to OtherComponent (which is a basic component where i just want to show all the datas collected in MainComponent) my store seems to be empty.
Can someone tells me what i'm doing wrong and what OtherComponent should looks like for access the datas i set in the store when i was using MainComponent.
Thanks.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer from './reducers'
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
reducers.js
import { ADD_DATAS } from '../constants';
const reminder = (action) => {
switch (action.type) {
case ADD_DATAS:
return {
datas: action.datas,
id: action.id
};
default:
return {
text: action.text,
id: action.id
};
}
}
const reminders = (state = [], action) => {
let reminders = null;
switch (action.type) {
case ADD_DATAS:
reminders = [...state, reminder(action)];
return reminders;
default:
return state;
}
}
export default reminders;
Action.js
import {ADD_DATAS} from '../constants';
// ADD_DATAS = 'ADD_DATAS' in constants
export const addDatas = (text, id) => {
const action = {
type: ADD_DATAS,
datas: text,
id: id
}
return action;
}
App.js
import React, { Component } from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import RouterComponent from '../Router/RouterComponent';
import OtherComponent from './OtherComponent';
import MainComponent from './MainComponent';
class App extends Component {
render() {
return (
<div className="container-fluid">
<div className="row">
<Sidebar />
<Router>
<RouterComponent>
<Switch>
<Route exact path="/oth" component={OtherComponent}/>
<Route exact path="/main" component={MainComponent}/>
<Route exact path="/" component={MainComponent}/>
</Switch>
</RouterComponent>
</Router>
</div>
</div>
);
}
}
export default App;
MainComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { addDatas } from '../actions'
class MainComponent extends Component {
addDataStore(text, id){
this.props.addDatas(text, id)
}
render(){
return ( .... )
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({addDatas}, dispatch);
}
function mapStateToProps(state){
return {
reminders: state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MainComponent);
OtherComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
class OtherComponent extends Component {
render(){
console.log(this.props.reminders)
}
}
function mapStateToProps(state){
return {
reminders: state
}
}
export default connect(mapStateToProps)(OtherComponent);
Sidebar.js
import React, { Component } from 'react';
import '../css/sidebar.css';
export default class Sidebar extends Component {
render(){
return (
<nav className="col-2 col-md-2 sidebar">
<div className="sidebar-logo">
<a href="/main">
MainComponent
</div>
</a>
</div>
<ul >
<li >
<a href="/main" >
MainComponent
</a>
</li>
<li >
<a href="/oth" >
OtherComponent
</a>
</li>
</ul>
</nav>
)
}
}
To Understand this we first need to understand the below snippet
export default connect(mapStateToProps, mapDispatchToProps)(MainComponent);
we are passing mapStateToProps function to the connect method which we get from react-redux. So let's Understand how connect works and what it actually does
1. It calls your mapStateToProps function and passes the current value of (redux state/ redux store) to the function.
2. Then whatever value is returned by the mapStateToProps function after execution is passed down as props to the mainComponent(in your case).
So Since the child component for the main component is not having connect statement the props are not available to it.
You can make the redux state available as props by two was
1. Passing it down from main component as follows inside mainComponent.js render method we have
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { addDatas } from '../actions'
class MainComponent extends Component {
addDataStore(text, id){
this.props.addDatas(text, id)
}
render(){
return (
<Child1 reminders={this.props.reminders}/*can be accessed as this.props.reminders*//>
<Child2 reminders={this.props.reminders}/*can be accessed as this.props.reminders*//>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({addDatas}, dispatch);
}
function mapStateToProps(state){
return {
reminders: state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MainComponent);
2.Another way to do this will be using connect Statement inside your child component as well
export default connect(mapStateToProps, mapDispatchToProps)(MainComponent);
class Child1 extends Component{
.....
}
function mapDispatchToProps(dispatch){
return bindActionCreators({addDatas}, dispatch);
}
function mapStateToProps(state){
return {
reminders: state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Child1);//need to change here must be same as className
You should connect your OtherComponent to redux store as well using connect HoC.
You need to connect other components with mapStateToProps and mapDispatchToProps functions as you do in MainComponents.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { addDatas } from '../actions'
class OtherComponent extends Component {
addDataStore(text, id) {
this.props.addDatas(text, id)
}
render() {
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addDatas }, dispatch);
}
function mapStateToProps(state) {
return {
...state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(OtherComponent);
You would connect the otherComponent as well using connect HOC, in order to access the Store. Once you have a component connected the Store in the Hierarchy you can pass the data as props on to its children. However you do need to connect the top level component/s to store
import React, { Component } from 'react';
import { connect } from 'react-redux'
class OtherComponent extends Component {
render(){
console.log(this.props.reminders)
}
}
function mapStateToProps(state){
return {
reminders: state
}
}
export default connect(mapStateToProps)(OtherComponent);
Well,
my problem wasn't on my implementation of Redux but on the fact that i used href for navigate between my components.
The problem was that my Sidebar component wasn't in the Router component, then i had to save the history in the redux store and call history.push in my sidebar component with the history i saved before.
In all my components inside the router i added in the constructor:
constructor(props){
super(props);
...
this.props.addHistory(props.history);
}
addHistory add history if it doesn't already exist.
Then in my sidebar, i use the history in the store for use the push function:
for (var e in this.props.reminders){
if(this.props.reminders[e].history !== undefined ){
this.props.reminders[e].history.push('/oth');
}
}
I don't know if it's the cleanest way to do this, but it works.
So, I have searched high and low for a solution to this problem and haven't found an answer. Any closely related questions end up solving the problem by fixing state mutation issues which I am pert near positive I don't have. Also, I have done similar things a few times before that have worked just fine, and referring back to the code for those projects shown no significant differences with what I'm doing here.
I have a small React app that will get bigger over time so I'm using Redux for state management. Currently I have a main App component that will eventually be the parent for my routing, but for now I just have a single component inside of it that will be a Login page. The current thing I am trying to accomplish is simply to change the value of showCreateAccountFlyout from false to true when I click ButtonPseudoAnchor in the Login page.
App.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
// import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
/* ---- Routes ----- */
import Login from './scenes/Login';
/* ----- Redux Store ----- */
import store from './redux/configureStore';
/* ----- Styles ----- */
import reset from './globalStyles/reset.scss';
export default class App extends React.Component<void> {
render() {
return (
<Provider store={store}>
<Login />
</Provider>
)
}
}
Login.js
import React from 'react';
import { Link } from 'react-router-dom';
import ButtonBasic from '../components/buttons/ButtonBasic';
import ButtonRow from '../components/buttons/ButtonRow';
import ButtonPseudoAnchor from '../components/buttons/ButtonPseudoAnchor';
import FlyoutFullScreen from '../components/containers/FlyoutFullScreen';
import FormElementBasic from '../components/formElements/FormElementBasic';
import PageBasic from '../components/containers/PageBasic';
import { connect } from 'react-redux';
import { toggleCreateAccountFlyout } from '../redux/actions/loginActions';
/* ----- Styles ----- */
import loginStyles from './loginStyles.scss';
class Login extends React.Component {
constructor() {
super();
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler() {
this.props.toggleCreateAccountFlyoutHandler(!this.props.showCreateAccountFlyout);
}
render() {
return (
<PageBasic contextualModifier="loginPage">
<div className={loginStyles.loginContainer}>
<div className={loginStyles.baphometLogo}>baphomet</div>
<FormElementBasic
type="text"
placeholderText="Username or Email"
/>
<FormElementBasic
type="password"
placeholderText="Password"
/>
<ButtonRow rowAlignment="center">
<ButtonBasic
buttonText="Enter"
buttonType="primary"
/>
</ButtonRow>
<ButtonPseudoAnchor onClickFunc={this.onClickHandler}>Create Account</ButtonPseudoAnchor>
</div>
<FlyoutFullScreen showFlyout={this.props.showCreateAccountFlyout} />
</PageBasic>
);
}
}
const mapStateToProps = state => {
return {
showCreateAccountFlyout: state.loginReducer.showCreateAccountFlyout
}
};
const mapDispatchToProps = dispatch => {
return {
toggleCreateAccountFlyoutHandler: showCreateAccountFlyout =>
dispatch(toggleCreateAccountFlyout(showCreateAccountFlyout))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
loginActions.js
export const TOGGLE_CREATE_ACCOUNT_FLYOUT = 'TOGGLE_CREATE_ACCOUNT_FLYOUT';
export function toggleCreateAccountFlyout(showCreateAccountFlyout) {
return {
type: TOGGLE_CREATE_ACCOUNT_FLYOUT,
payload: showCreateAccountFlyout
}
}
loginReducer.js
import { TOGGLE_CREATE_ACCOUNT_FLYOUT } from '../actions/loginActions';
export default function loginReducer(state = {
showCreateAccountFlyout: false
}, action) {
switch(action.type) {
case TOGGLE_CREATE_ACCOUNT_FLYOUT:
return { ...state, showCreateAccountFlyout: action.payload };
default:
return state;
}
}
reducers/index.js
import { combineReducers } from 'redux';
import loginReducer from './loginReducer';
const rootReducer = combineReducers({
loginReducer
});
export default rootReducer;
configureStore.js
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const middleware: Function = applyMiddleware(thunk);
export default createStore(rootReducer, middleware);
As I step through everything, the new showCreateAccountFlyout value is being passed along, it just appears to never get passed back to the Login component from the reducer. Maybe there is something super simple I'm missing? I dunno.
I am utilizing thunk with react and redux. My action creator executes an API call and returns data in a property "data" and my reducer returns that object. I have this returned object mapped to props in my component. Its an array of 16 items (each item is an image url). when I console.log(this) I can click through and see the data, but if I go further like console.log(this.props.showGallery.imageLinks) it shows undefined.
Another situation is that when I render { this.props.showGallery.imageLinks } I can clearly see all the text of the items in the array on my web page but when I use .map on it, the console says cannot read property "map" of undefined and the web page is just empty. Am I doing this wrong? How can I make this data like normally?
Am I understanding redux concepts wrongly?
actions.js
export const SHOW_GALLERY = 'SHOW_GALLERY';
import axios from 'axios';
// FETCH THE IMAGES
export const actionGallery = () => {
return ( dispatch ) => {
axios.get('../images')
.then(res => {
if (res.status === 200) {
return dispatch({ type: SHOW_GALLERY, data: [...res.data] });
}
})
.catch(err => console.error(err));
}
}
reducer
images.js
import { HEAD_SELECTED, SHOW_GALLERY } from '../actions/actions';
import { actionSelectHeadImg } from '../actions/actions';
import { actionGallery } from '../actions/actions';
// Show the image links from an array
export function showGallery(state={}, action) {
switch(action.type) {
case SHOW_GALLERY:
return Object.assign({}, state, { imageLinks: new Array(action.data) })
default:
return state;
}
}
combined Reducers for above:
import React from 'react';
import { combineReducers } from 'redux';
import { showGallery, headSelected } from './images';
// Combine all your reducers to this
const allReducers = combineReducers({
showGallery,
headSelected
});
export default allReducers;
component / container
Gallery.js
import React from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionGallery } from '../actions/actions';
import cheerio from 'cheerio';
class Gallery extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
}
this.props.actionGallery();
}
render() {
return (
<div>
<h1>This is the Gallery.</h1>
<br />
<div className="container">
<div className="row">
<div className="col-md-8">
<h2>H2 h2 h2 2h2 </h2>
{ this.props.showGallery.imageLinks.forEach((i, index) => {
<p>i</p>
}) }
</div>
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
showGallery: state.showGallery,
headSelected: state.headSelected,
newState: state
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
actionGallery,
actionSelectHeadImg
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Gallery);
A regular component that just holds the other container/components
import React from 'react';
import ReactDOM from 'react-dom';
import Gallery from './containers/Gallery';
import HeadingImg from './containers/HeadingImg';
class App extends React.Component {
render() {
//console.log(this.props.actionGallery())
return (
<div>
<center>
<h3>SELECTED IMAGE:</h3>
<br />
<HeadingImg />
<hr />
<Gallery />
</center>
</div>
);
}
}
export default App;
TOP LEVEL COMPONENT (MAIN COMPONENT)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import App from '../Components/Earth/app';
import allReducers from '../Components/Earth/reducers';
import thunk from 'redux-thunk';
const store = createStore(allReducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App store={store}/>
</Provider>
, document.getElementById("root"));
I'm assuming when you create your store, you only have the one reducer. If that is the case then your assumption about 'state.showGallery' existing doesn't. Instead imageLinks will be in state without the 'showGallery'.
If my assumption is correct, then you should change your mapStateToProps to have showGallery as:
showGallery: { imageLinks: state.imageLinks },