React-Redux - failing to show props on view page - reactjs

EDIT: Ive changed my return to an object, yet still i'm getting empty props.
console.log of mapStateToProps(state) shows that temperature is empty. I am assuming I am getting back a non changed state, temperature from my axios call is not returned to my weatherPage.js.
my whole environment is working fine, im just trying to make an axios get request.
I'm having a bit of a difficulty passing my object through the Redux lifecycle, from the actions to the reducer while keeping the propTypes validation method and trying to use Object.assign() (which is truly the right way of mutating the state with a single deep copy as Dan Abramov noted.)
The error:
my props are empty. The axios call I've made in src/actions/weatherActions.js is not showing up as a prop.weatherDetails.temperature in src/components/weatherPage.js
, it returns my default state.
I'm new to ES6 and Redux, I've included propTypes into my page and I'm having bit of an issue with this, I think the issue comes from supplying the right state that comes from the action.
When the choose button is pressed i'm supposed to receive the temp_c (axios calls this json)
src/components/weatherPage.js
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {withRouter} from 'react-router';
import * as WeatherActions from '../../actions/weatherActions';
class WeatherPage extends React.Component {
render() {
return (
<div>
<h2>temps: {this.props.weatherDetails.temperature}</h2>
<input
type="submit"
onClick={this.onClickSave.bind(this)}
value="CHOOSE"/>
</div>
);
}
onClickSave() {
WeatherActions.getWeather(this.props.dispatch);
}
WeatherPage.propTypes = {
weatherDetails: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired
};
function mapStateToProps(state) {
return {
weatherDetails: state.weathers.weatherDetails
};
}
export default connect(mapStateToProps)(withRouter(WeatherPage));
Since this.props.weatherDetails.temperature shows my current state, I know that problem lies between the action and the reducer.
src/actions/weatherActions.js
import axios from 'axios';
export const ActionTypes = {
WEATHER: { LOAD_WEATHER: 'WEATHER.LOAD_WEATHER' } };
export function getWeather(dispatch) {
console.log('in getWeather method');
console.log('this is getWeather dispatch: ', dispatch);
axios({
url: 'http://api.weatherunlocked.com/api/trigger/32.08,34.78/current%20temperature%20gt%2016%20includecurrent?app_id=ba2f68f0&app_key=0356747cc4d1d4ba0dd5cc25a0c86743',
method: 'get'
}).then(function (response) {
//console.log('in the then func with this res: ', JSON.stringify(response));
dispatch({
type: ActionTypes.WEATHER.LOAD_WEATHER,
temperature: response.CurrentWeather.temp_c
},
function () {
console.log('dispatch completed');
});
});
console.log('end of class getWeather');
I'm performing a simple axios call, yet i'm not sure if i'm dispatching the 'payload' (temperature: response.CurrentWeather.temp_c) correctly to appear through the reducer and back into view.
Here is my reducer:
src/reducers/weatherReducer.js
import * as WeatherActions from '../actions/weatherActions';
const initialState = {
weatherDetails: {
area: '',
temperature: 'sdf'
}
};
function WeatherReducer(state = initialState, action) {
console.log('in WeatherReducer, this is action: ' + JSON.stringify(action));
switch (action.type) {
case WeatherActions.ActionTypes.WEATHER.LOAD_WEATHER:
return [...state, Object.assign({}, action.temperature.data)];
default:
return state;
}
}
export default WeatherReducer;
What am I missing here on this build?

You state object must be returned as an object rather than an array from the reducer like
switch (action.type) {
case WeatherActions.ActionTypes.WEATHER.LOAD_WEATHER:
return Object.assign({}, state, action.temperature.data);
default:
return state;
}

Related

React-Redux - How to redirect the page after storing the data in redux store?

I am trying out a react-redux sample code where I would like to add a course in one form upon clicking 'Add Course', I want to update the store and redirect to a new page with the list of courses.
But for some reason, the redirect happen after calling the redux action creator. It stays in the same page.
Any ideas how to redirect the results to a different page?
import React from "react";
import { connect } from "react-redux";
import * as courseActions from "../../redux/actions/courseActions";
import PropTypes from "prop-types";
import { bindActionCreators } from "redux";
import history from './history'
class CoursesPage extends React.Component {
state = {
course: {
title: "",
},
};
handleSubmit = (event) => {
event.preventDefault();
this.props.actions.loadCourses.createCourse(this.state.course).then(() => {
alert('Inside Promise')
history.push('/AllCourses'); //This doesn't get executed.
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<h2>Courses</h2>
<h3>Add Course</h3>
<input type="submit" value="Add Course" />
{this.props.courses.map((course) => (
<div key={course.title}>{course.title}</div>
))}
</form>
<hr />
</div>
);
}
}
CoursesPage.propTypes = {
courses: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired,
};
function mapStateToProps(state) {
return {
courses: state.courses,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: {
loadCourses: bindActionCreators(courseActions, dispatch),
},
};
}
export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage);
Action Code:
import * as types from "./actionTypes";
export function createCourse(course) {
return { type: types.CREATE_COURSE, course };
}
Reducer:
import * as types from "../actions/actionTypes";
export default function courseReducer(state = [], action) {
debugger;
switch (action.type) {
case types.CREATE_COURSE:
return [...state, { ...action.course }];
default:
return state;
}
}
history.js
import createHistory from 'history/createHashHistory'
export default createHistory()
You can create a custom middleware to do so:
const hasAddedData = (state) => {
// Your checks
}
const redirectMiddleware = history => storeAPI => next => action => {
console.log('dispatching', action)
const result = next(action);
// If the action is the one you want to trigger the redirect (action.type)
// and the state pass the checks (storeAPI.getState()),
// do the redirect.
//
// This would be like:
if (action.type === types.CREATE_COURSE && hasAddedData(storeAPI.getState())) {
history.push(destinationPath);
}
// You must return the result of next(action) to avoid breaking Redux.
return result;
}
And wherever you create your Redux Store:
// history should already be provided by React Router.
const middlewareEnhancer = applyMiddleware(redirectMiddleware(history))
const store = createStore(yourRootReducer, middlewareEnhancer)
If you need to check the previous state too, just set a const with storeAPI.getState() before running next(action). You can expand your middleware with other redirect checks or scenarios you need for additional actions.
WARNING: I wanted to give you the vanilla code solution, but keep in mind these three things:
This is a task that is probably better and opinionatedly made by a library (check connected-react-router).
Also, instead of making a custom middleware for action specific tasks, you can use a widely accepted middleware library such as redux-saga.
Think about your app workflow. Do you need additional state properties (done flags, selection properties...)? Are all the CREATE_COURSE actions going to redirect or only a fraction of them? Will a specific REDIRECT action make things easier for you? Do you really need an imperative redirect or would it be possible, with the right state structure, a declararive Redirect with React Router component?

React component loading twice with redux

I am trying to get the state from redux store but whenever I access my redux action my component is loaded twice
By loading route manually first time it returns the two objects,one object of empty state and other object of updated state
However when I am switching between routes it returns the new state but twice each time or two objects of same state.
This causes undefined error on loading component specially when manually loading the route , when I am accessing any state property,
Redux action
export function getRelationshipStatus() {
let headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
}
return dispatch => {
axios.get('http://localhost/relation/api/get-relationship-status', { headers })
.then( response => dispatch({type:ActionTypes.GET_RELATIONSHIP_STATUS,payload:response.data}))
}
};
Reducer
import React from 'react'
import {GET_RELATIONSHIP_STATUS} from '../actions/action-types'
const initialState={
profile:{},
}
export default function (state=initialState,action) {
switch(action.type){
case GET_RELATIONSHIP_STATUS:
console.log(action.payload);
return {...state,profile:action.payload}
break;
default:
return state
}
}
Actual Component
import React, { Component } from 'react'
import {connect} from 'react-redux'
import {getRelationshipStatus} from '../../store/actions'
class People extends Component {
constructor(props){
super(props)
}
componentDidMount(){
this.props.getRelationshipStatus()
}
render() {
console.log(this.props.profile)
return (
<div>
</div>
)
}
}
function mapStateToProps(state) {
return {
profile: state.profile,
}
}
export default connect(
mapStateToProps,
{ getRelationshipStatus }
)(People);
Hey it's total natural behaviour of react-redux loading twice.
Even i can explain you but that's not question
I'm sure this behaviour is not problem
By the way what is error name?

Dispatched action received by reducer, but not appearing in action list and not rerendering

I have a Comment component that has a delete button. When the button is clicked, it called the action, makes the axios call, and when the call returns it dispatches the update to the reducer. The only problem is that it's not triggering the rerender of the parent component. The action, although it is updating the state, does not appear in the list of dispatched actions in the Redux DevTools. All other actions work and display in the DevTools, but for some reason this one doesn't.
My thought after reading the comment section below is that it's because I'm making a shallow copy of my object. Am I wrong to think that making a shallow copy, modifying a deeper object, and returning the shallow copy wouldn't trigger a rerender? Isn't the fact that the shallow object is a different reference enough to trigger it? I'm confident I'm doing this the same way in other places and I havenn't have a problem elsewhere. It
This is the action list in Redux DevTools after deleting the comment. I would expect that it would have "delete_comment" near the bottom somewhere, but it's not:
The data is passed from the parent components CommentList -> CommentThread -> Comment.
Is this truly dispatching?
This is a simplified component:
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {
deleteComment,
} from "../../actions/comment_actions";
class Comment extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
const {comment, data} = this.props;
if (!data) {
//console.log("mir_data doesnt exist");
return <div/>;
}
return (
<div key={"comment" + comment.id} id={"c" + comment.id}>
<button onClick={() => this.props.deleteComment(comment.id)}>Delete</button>
</div>
);
}
}
function mapStateToProps(state) {
return {
user: state.user
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
deleteComment,
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Comment);
Here's a simplified action file:
export const DELETE_COMMENT = 'delete_comment';
export function deleteComment(id, callback = null) {
return (dispatch, getState) => {
axiosInstance.post('/delete-comment/', {comment_id: id}, getState().api.axios).then(
response => {
dispatch(dispatchDeleteComment(id));
//dispatch(dispatchViewUserInfo(response.data));
if (response.status === 200)
callback();
}
);
}
}
export function dispatchDeleteComment(id) {
return {
type: DELETE_COMMENT,
payload: id
};
}
Here's the simplified reducer:
import {DELETE_COMMENT} from "../actions/comment_actions";
export default function(state = {}, action){
let newState = {...state};
switch(action.type){
case DELETE_COMMENT:
//some logic
delete newState.comments[action.payload];
return newState;
default:
return state;
}
}

Redux action is undefined outside the Promise

I've been using async calls with Redux + Promise Middleware like this:
export function getAllFiles() {
const request = axios.get(`${URL}/files`).then(res => res.data);
return { type: "GET_FILES", payload: request };
}
So far it worked, since from my understanding Promise Middleware takes care of this. Also, if I console.log(res.data) inside .then, I will get expected result. However, when I check it from reducer file it's undefined. Can someone please school me if I'm missing something huge.
import React, { Component } from "react";
import { connect } from "react-redux";
import Info from "./../components/Info/info";
import { getAllFiles } from "./../actions";
class HomeContainer extends Component {
componentWillMount = () => {
this.props.dispatch(getAllFiles);
};
render() {
console.log(this.props);
return (
<div>
<Info />
</div>
);
}
}
function mapStateToProps(state) {
return {
files: state.files
};
}
export default connect(mapStateToProps)(HomeContainer);
import { combineReducers } from "redux";
import fileReducer from "./file_reducer.js";
const rootReducer = combineReducers({ wineReducer });
export default rootReducer;
// file_reducer.js
export default function(state = {}, action) {
switch (action.type) {
case "GET_FILES":
return {...state, files: action.payload}
default:
return state;
}
}
The main problem here is your treating, what is clearly an async action, as a synchronous one.
The redux store by default will only deal with action objects, in your case since you need to wait for a result before we fire the action object, we need some middleware to allow the store to handle this.
Have a look at the applyMiddleware function, plenty of links to common thunk middleware and code samples.

Component not updating after mapStateToProps fires

I am learning how to implement redux from the ground up, and have run into a problem with my components' re-rendering. My search for this issue on StackOverflow has produced a gazillion results, of which the answer to the question is always "you mutated your state." I have read the connect documentation, I've looked at a bunch of people with similar problems, and I just can't see where state mutation might be the problem here, so I'm going to try asking with my simple example.
Here's my container component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addPokemon } from '../../../redux/actions';
import ReduxTester from '../../components/redux_tester/redux_tester';
export class ReduxTesting extends Component {
constructor(props) {
super(props);
}
addPokemon(name, pokeType) {
addPokemon(name, pokeType);
}
render() {
return (
<div>
<ReduxTester
pokemon={this.props.pokemon}
addPokemon={this.addPokemon}
/>
</div>
);
}
}
const MapStateToProps = function(state) {
return {
pokemon: state.pokemon,
};
};
export default connect(MapStateToProps)(ReduxTesting);
Here's my reducer:
const defaultState = {
pokemon: {},
};
export default function pokemonReducer(state = defaultState, action) {
const newState = Object.assign({}, state);
switch (action.type) {
case 'ADD_POKEMON':
newState.pokemon[action.name] = action.pokeType;
break;
default:
break;
}
return newState;
}
My specific issue is simply that ReactTesting's componentWillReceiveProps method is not firing, and so the component is not being updated and re-rendered. Note that the mapStateToProps method is firing after the action is dispatched. I know this is such a repetitive issue and it's unlikely that my problem is something different, but I just can't find the answer. Any assistance is appreciated.
Edit: For additional clarification, here is my actions.js, where I've imported the dispatch function directly:
import { dispatch } from './store';
// Returns an action that fits into the reducer
export function addPokemon(name, pokeType) {
dispatch({
type: 'ADD_POKEMON',
name,
pokeType,
});
}
Edit 2: I've found some additional information, but I don't understand it. It seems that in MapStateToProps, a re-render is triggered when I assign the entire Redux state to one prop - but not when I assign just a portion of the Redux state to prop.
This triggers a re-render:
const MapStateToProps = function(state) {
return {
pokemon: state,
};
};
This does not:
const MapStateToProps = function(state) {
return {
pokemon: state.pokemon,
};
};
Yet I have confirmed my redux store does have the pokemon property and that is where the updates are occurring in the state. Any insight?
Calling your action creator without going through redux won't dispatch the action:
export class ReduxTesting extends Component {
constructor(props) {
super(props);
}
addPokemon(name, pokeType) {
this.props.addPokemon(name, pokeType);
}
.
.
.
function mapDispatchToProps(dispatch) {
return({
addPokemon: (name, pokeType) => {dispatch(addPokemon(name, pokeType))}
})
}
export default connect(MapStateToProps, mapDispatchToProps)(ReduxTesting);
EDIT 2
You're probably mutating your state, try with something like this:
Reducer
switch (action.type) {
case 'ADD_POKEMON':
return {
...state,
pokemon[action.name]: action.pokeType;
};

Resources