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.
Related
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.
I'm attempting to implement redux into a relatively simple app, however my actions don't seem to be triggering the reducers properly. Through console logging the action seems to be firing, but the respective reducer isn't being executed.
App.js:
import {Provider} from 'react-redux';
import configureStore from './src/config/configureStore.js';
const store = configureStore();
export default class App extends React.Component {
render() {
return (
<Provider store = {store}>
<RootStack />
</Provider>
);
}
}
configureStore.js:
import {createStore, applyMiddleware} from 'redux';
import reducers from '../reducers';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const store = createStore (
reducers,
applyMiddleware(thunk)
);
return store;
}
actions/index.js:
export const saveRisk = (payload) => {
console.log('saved RISK!');
return (dispatch) => {
dispatch({type: 'risk_chosen',payload: payload});
}
}
reducers/index.js:
import { combineReducers } from 'redux';
import RiskReducer from './RiskReducer';
export default combineReducers({
risk_level: RiskReducer
});
RiskReducer.js
const INITIAL_STATE = {risk_level: false};
export default (risk = INITIAL_STATE, action) => {
if(action.type === 'risk_chosen') {
console.log('RISK REDUCER SUCCESSFUL')
return {
...risk, risk_level: action.payload
};
}
console.log('REDUCER RISK:');
console.log(risk);
return risk;
}
RiskTolerance.js (A child component within RootStack which is using redux):
import { connect } from 'react-redux';
import {saveRisk} from '../../actions'
#connect(state => ({risk_level: state.risk_level.risk_level}, {saveRisk}))
export default class RiskTolerance extends React.Component {
// ...
componentDidMount(){
console.log(this.props.risk_level);
// ^^returns undefined, despite the reducer initializing it to "false"
let riskVal = 'something'
this.props.saveRisk(riskVal)
}
// ...
}
EDIT: I have changed the initial value in the reducer to an appropriate object but my reducer is still not working after the action is called. Any ideas?
Thank you!
There is problem with initial state in your reducer. Make changes as shown below:
INITIAL_STATE = { risk_level: false }
Figured it out, when calling the action I needed to write:
this.props.dispatch(this.props.saveRisk(riskVal))
Thanks for your help everyone!
I am having an issue with my React-Redux app. I have created Redux action & reducer and connected them to my React component. However, it seems like it never gets to my Redux action & reducer.
I put "debugger" in my action and reducer but those debuggers never get initiated.
Could you please help me fix this issue?
Am I missing something here?
Here the current codes in the following files:
Here is the code for my app.js:
import React, { Component } from 'react';
import { render } from 'react-dom';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import ReduxPromise from 'redux-promise';
import rootReducer from './reducers';
import App from './components/index';
const createStoreWithMiddleware = applyMiddleware(thunk, ReduxPromise)(createStore);
render(
<Provider store={createStoreWithMiddleware(rootReducer)}>
<App />
</Provider>,
document.getElementById('app')
);
Here is my code for index.js:
As you can see below, I put a debugger in the "componentDidMount()", and when In run it, it gets to that point.
import React, { Component } from 'react';
import { render } from 'react-dom';
import { connect } from 'react-redux';
import * as AllNewsApiActions from '../actions/newsApiActions';
import '../../css/style.css';
class App extends Component {
componentDidMount() {
this.props.getNewsApi();
debugger;
}
render() {
return (
<div>
<p>Hello from react</p>
<p>Sample Testing</p>
</div>
);
}
}
const mapStateToProps = (state) => ({
newNews: state.newNews
});
export default connect(mapStateToProps, {
...AllNewsApiActions
})(App);
Here is my action:
import * as types from './newsTypes';
const API_KEY = "6c78608600354f199f3f13ddb0d1e71a";
export const getNewsApi = () => {
debugger;
return (dispatch, getState) => {
dispatch({
type: 'API_REQUEST',
options: {
method: 'GET',
endpoint: `https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=${API_KEY}`,
actionTypes: {
success: types.GET_NEWS_API_SUCCESS,
error: types.GET_NEWS_API_ERROR
}
}
});
}
}
Here is my reducer:
import * as types from '../actions/newsTypes';
const initialState = {
newNews: []
};
const getNewsAPIReducer = (state = initialState, action) => {
switch(action.type) {
case types.GET_NEWS_API_SUCCESS:
debugger;
return { ...state, newNews: action.data };
case types.GET_NEWS_API_ERROR:
debugger;
return { ...state, error: action.data };
default: {
return state;
};
};
};
export default getNewsAPIReducer;
Here is the index.js file in the reducer folder:
import { combineReducers } from 'redux';
import NewsApiReducer from './newsApiReducer.js';
const rootReducer = combineReducers({
newNews: NewsApiReducer
});
export default rootReducer;
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 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 },