I'm pretty new to react/redux (and javascript in general) so please bear with me on my use of the terminology here...
I am trying to understand how Components and Reducers work so currently I am practicing on a small app that I mostly copied/pasted from a tutorial. The issue I am having is that I am trying to dispatch an action from my Component which alters the Redux state but I am not even seeing my console.log() messages inside my reducer function(s) when I dispatch an action from my Component.
This is what I currently have:
TwApp.js
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { TWAPP_USERDATA_AUTH_TOKEN } from '../Constants'
import { loginSuccess } from '../actions'
class TwApp extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.handleRefreshClick = this.handleRefreshClick.bind(this)
}
componentDidMount() {
console.log("TwApp componentDidMount")
this.props.loginSuccess() // This is where I want to dispatch an action
}
componentDidUpdate() {
}
handleChange() {
}
handleRefreshClick(e) {
e.preventDefault()
this.props.loginSuccess()
}
render() {
const { loggedIn } = this.props;
console.log("Rendering TwApp.")
if (!loggedIn) {
console.log("user not logged in. loggedIn = " + loggedIn)
}
else {
return (
<div>
<p>Success</p>
</div>
)
}
}
}
function mapStateToProps(state) {
// nothing for now
}
function mapDispatchToProps(dispatch) {
return {
loginSuccess: () => { dispatch(loginSuccess) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TwApp)
actions.js
export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'
export function loginSuccess() {
return {
type: USER_LOGIN_SUCCESS,
}
}
reducers.js
// Contains a bunch of stuff that isn't being used yet
import { combineReducers } from 'redux'
import {
USER_LOGIN_SUCCESS, INVALIDATE_SUBREDDIT,
REQUEST_POSTS, RECEIVE_POSTS
} from './actions'
function reducer1(state = {}, action) {
console.log("reducer1: state =" + JSON.stringify(state) + ", action = " + JSON.stringify(action))
switch (action.type) {
case USER_LOGIN_SUCCESS:
console.log("Reducer USER_LOGIN_SUCCESS")
state.loggedIn = true
return state
case RECEIVE_POSTS:
case REQUEST_POSTS:
default:
return state
}
}
function reducer2(state = {}, action) {
console.log("reducer2: state =" + JSON.stringify(state) + ", action = " + JSON.stringify(action))
switch (action.type) {
case INVALIDATE_SUBREDDIT:
case RECEIVE_POSTS:
case REQUEST_POSTS:
default:
return state
}
}
const rootReducer = combineReducers({
reducer1,
reducer2
})
export default rootReducer
Neither reducer1's nor reducer2's console.log() message appears in console. When I call dispatch() from my TwApp component, do all reducers (reducer1 and reducer2) get called? Am I misunderstanding something?
Thanks
You are dispatching 'loginSuccess' which is a function, or by redux terms an action creator.
You should dispatch actions, which are plain objects, to your reducers.
What you want to do is dispatch the action that loginSuccess will create for you:
loginSuccess: () => { dispatch(loginSuccess()) }
comonentDidUpdate as you have it already:
componentDidMount() {
console.log("TwApp componentDidMount")
this.props.loginSuccess()
}
then remove your mapDispatchToProps function completely and export the following:
export default connect(mapStateToProps, {loginSuccess})(TwApp)
Related
I am running this piece of code when an item in my menu is clicked.
import React, { Component } from 'react';
import 'react-dropdown-tree-select/dist/styles.css';
import { connect } from 'react-redux';
import '../../../css/tree.css';
import updateSelectedTree from '../../../redux/actions/menu/updateSelectedTree';
import {updateTraceName} from '../../../redux/actions/plot/traceActions';
import { removeRenameProp, pushRenameProp } from '../../../redux/actions/menu/renameActions'
import resolveTraceName from '../../../utils/resolveTraceName';
import MeasureTreeSelector from './MeasureTreeSelector';
class MeasureTreeSelectorContainer extends Component {
render() {
return (<MeasureTreeSelector
isSingle={false}
tags={this.props.tags}
selected={this.props.selected}
handleTreeChange={this.handleTreeChange}
> </MeasureTreeSelector>);
}
handleTreeChange = (curr, selected) => {
console.log(curr)
// Get the order of the selected items
const selectedProperty = [...curr.path.split("/").splice(1), curr.value]
if(curr.checked) {
this.props.pushRenameProp(selectedProperty)
} else {
this.props.removeRenameProp(selectedProperty)
}
console.log(this.props.rename_props)
this.props.updateSelectedTree(this.props.rename_props)
}
updateTraces = () => {
this.props.measures.forEach(element => {
const newName = resolveTraceName(element, this.props.rename_props);
console.log(newName)
this.props.updateTraceName(newName, element.id)
})
}
}
const mapStateToProps = (state) => {
console.log(state.menuReducer)
return {
tags: state.menuReducer.tags,
selected: state.menuReducer.selected,
measures: state.menuReducer.json,
rename_props: state.menuReducer.rename_props
}
}
const mapDispatchToProps = {
updateSelectedTree,
updateTraceName,
removeRenameProp,
pushRenameProp
}
export default connect(mapStateToProps, mapDispatchToProps)(MeasureTreeSelectorContainer);
pushRenameProp and removeRenameProp are synchronous actions that are dispatched that update rename_props. These reducers don't mutate the state but create a new one which means there should be an update in the component.
export function pushRenameProp(prop) {
return {
type:'PUSH_RENAME_PROP',
payload: prop
}
}
export function removeRenameProp(prop) {
return {
type:'REMOVE_RENAME_PROP',
payload: prop
}
}
case 'PUSH_RENAME_PROP':{
console.log(action.payload)
return {
...state,
rename_props: [...state.rename_props,action.payload]
}
}
case 'REMOVE_RENAME_PROP': {
return {
...state,
rename_props: state.rename_props.filter((e,i) => {
return JSON.stringify(e) != JSON.stringify(action.payload)
})
}
}
When I execute this action, the devtools show that the redux state has updated, but the code shows that rename_props is empty.
It seems to be that the component props are one state change behind the redux state
This line this.props.updateSelectedTree(this.props.rename_props) sets the rename_props as they were on before setting them.
So it just negates the change.
You could do this in a thunk:
export function pushRenameProp(prop) {
return function (dispatch, getState) {
dispatch({
type:'PUSH_RENAME_PROP',
payload: prop
})
const state = getState();
return dispatch(updateSelectedTree(state.menuReducer.rename_props)
}
}
To ensure that the updateSelectedTree has the newest state.
*code is not tested, but I think you get the gist of this.
But also, maybe, updateSelectedTree is not needed at all.
You change the state by just push/remove, why update?
I am trying to retrieve a set of products from an API using a redux store.
To achieve this, I created actions and reducers, a function mapStateToProps() and a function mapDispatchToProps(), the latter one contains a function fetchProducts(), which is defined in the action file and should retrieve the list of products when called.
All of this is done in the following way:
This is the overview component, which should render the data (the actual rendering is left out, because it does not affect this question):
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types'
import fetchProductsAction from '../actions/ProductFetchingActionFile'
import {bindActionCreators} from 'redux';
export class Overview extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchProducts();
}
render() {
return(
<h1>test</h1>
)
}
}
const mapStateToProps = state => ({
error: state.rootReducer.productsReducer.error,
products: state.rootReducer.productsReducer.products,
pending: state.rootReducer.productsReducer.pending,
})
const mapDispatchToProps = dispatch => bindActionCreators({
fetchProducts: fetchProductsAction
}, dispatch)
Overview.propTypes = {
fetchProducts: PropTypes.func
}
export default connect(mapStateToProps, mapDispatchToProps)(Overview);
This is the action file (ProductFetchingActionFile):
import {
PREFIX,
FETCH_PRODUCTS_PENDING,
FETCH_PRODUCTS_SUCCESS,
FETCH_PRODUCTS_ERROR
} from "./types";
function fetchProductsPending() {
return{
type: FETCH_PRODUCTS_PENDING
}
}
function fetchProductsSuccess(products) {
return{
type: FETCH_PRODUCTS_SUCCESS,
products: products
}
}
function fetchProductsError(error) {
return{
type: FETCH_PRODUCTS_ERROR,
error: error
}
}
function fetchProducts() {
return dispatch => {
dispatch(fetchProductsPending());
return fetch(`localhost:8080/products`)
.then(res => res.json())
.then(res => {
if(res.error) {
throw(res.error);
}
dispatch(fetchProductsSuccess(res));
return res
})
.catch(error => {
dispatch(fetchProductsError(error));
})
}
}
export default fetchProducts
This is the reducer file:
import {
FETCH_PRODUCTS_PENDING,
FETCH_PRODUCTS_SUCCESS,
FETCH_PRODUCTS_ERROR
} from "./types"
const initialState = {
pending: false,
products: [],
error: null
}
export function productsReducer(state = initialState, action) {
switch(action.type) {
case FETCH_PRODUCTS_PENDING:
return {
...state,
pending: true
}
case FETCH_PRODUCTS_SUCCESS:
return {
...state,
pending: false,
products: action.products
}
case FETCH_PRODUCTS_ERROR:
return {
...state,
pending: false,
error: action.error
}
default:
return state;
}
}
Now, I get the following error when the Overview component is loaded:
TypeError: fetchProducts is not a function. This error fires on the call to fetchProducts in the componentDidMount() function.
Also, when the Overview.props is printed in the console before this call, it does not contain the function fetchProducts, so I suspect that it has to do something with the mapDispatchToProps, but I cannot find the problem.
Edit: it is good to note that if the imported fetchProductsAction is printed in the constructor of the Overview component, it shows the correct function. It does however not end up in the props of Overview, so I expect the problem to be there.
try this
const mapDispatchToProps = dispatch => {
fetchProducts : () => {
dispatch(fethcProducts())
}
}
You're importing your action as
import fetchProductsAction from '../actions/ProductFetchingActionFile'
when it looks like it's not being exported as a default.
It should probably be:
import { fetchProductsAction } from '../actions/ProductFetchingActionFile'
I encountered this problem when I was testing my newly created action and reducer. The prop is not being updated even though I'm setting it to a fixed value within my reducer.
Component:
class <ComponentName> extends Component {
componentDidMount() {
login()
}
render() {
if(this.props.isLogged)
return (
<App/>
);
else
return (
<ErrorScreen/>
);
}
}
function mapStateToProps(state) {
return {
isLogged:state.auth.isLogged
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(login())
};
};
export default connect(mapStateToProps,mapDispatchToProps)(<ComponentName>)
Action:
export function login() {
return {
type:"TEST"
}
}
Reducer:
const initState = {
isLogged: false,
}
export default (state=initState, action) => {
switch(action.type) {
case "TEST":
return {
...state,
isLogged: true
}
break;
default:
return state
}
}
Combine Reducer:
import {combineReducers} from 'redux'
import AuthenticationReducer from './authenticationReducer'
export default combineReducers({
auth: AuthenticationReducer
})
Provider:
import React, {Component} from "react";
import <ComponentName> from './app/screens/<ComponentName>'
import store from './app/store'
import {Provider} from 'react-redux'
export default () =>
<Provider store={store}>
<<ComponentName>/>
</Provider>;
Been trying to debug this for some time now. I still don't know why this is happening. Maybe I implemented it wrongly? If there are some files I forgot to include, please inform me. Thanks and have a nice day!
The reason your code isn't working as expected is because you're calling the login() action creator, rather than the login() method that is returned from mapDispatchToProps() (and injected into the props of <ComponentName/>).
Try revising your code by adding this.props before your call to login() like so:
class <ComponentName> extends Component {
componentDidMount() {
// Update this line here so that the login() method
// injected by connect() is called (ie via this.props)
this.props.login()
}
render() {
if(this.props.isLogged)
return <App/>
else
return <ErrorScreen/>
}
}
I used reactJS and i know that a component that is wrapped with connect helper that listens to specific reducer when its reducer's state changes it causes the component to re-render.
I don't know why same procedure doesn't work for react-native, i tested my action creators as well as reducers and checked hundred percent that they return new state, And when i checked componentWillRecieveProps i found that the new state is returned correctly and the component doesn't re-render.
Reducer
const INITIAL = {
isSigned: null
}
export default (state = INITIAL, action) => {
switch(action.type){
case SIGNED_IN : return {...state, isSigned: true};
case LOGGED_OUT: return {...state, isSigned: false};
default: return state;
}
}
Component
import React, { Component } from 'react';
import { ActivityIndicator } from 'react-native';
import { connect } from 'react-redux';
import * as actions from '../../actions';
class Loading extends Component {
constructor(props){
super(props);
}
componentDidMount(){
this.props.checkSigned();
switch(this.props.isSigned){
case null : return;
case false : this.props.navigation.navigate('Auth');
case true : this.props.navigation.navigate('App')
}
}
render(){
return (
<ActivityIndicator size="large" color="black" />
)
}
}
const mapStateToProps = ({signed}) => {
const {isSigned} = signed;
return {
isSigned
}
}
export default connect(mapStateToProps, actions)(Loading);
Actions
export const SIGNED_IN = 'SIGNED_IN';
export const LOGGED_OUT = 'LOGGED_OUT';
//Action Creators
export const checkSigned = () => async dispatch => {
let token = await AsyncStorage.getItem('fb_token');
if(token){
dispatch({type: SIGNED_IN})
}
dispatch({type: LOGGED_OUT})
}
You need to use bindActionCreators to dispatch your actions as props
import { bindActionCreators } from 'redux';
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
const mapStateToProps = state => {
return {
isSigned: state.isSigned
}
}
export default connect(mapStateToProps, actions)(Loading);
// In actions, you need to fix action code
export const checkSigned = () => async dispatch => {
let token = await AsyncStorage.getItem('fb_token');
if(token){
dispatch({type: SIGNED_IN});
} else {
dispatch({type: LOGGED_OUT});
}
}
I think the problem is that you're running your state change logic in componentDidMount. componentDidMount doesn't run when your component re-renders, but componentDidUpdate does. Put your logic there.
I'm new to react and I'm trying to understand on to make async ajax request. I was able to get the request completed and the data returned added to my state, but I can't render the data to my component. Here's my setup.
UserPage component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { getUsers } from '../../actions';
class User extends Component {
displayName: 'User';
componentWillMount() {
this.props.getUsers();
}
renderUsers() {
return this.props.users.map(user => {
return (
<h5>{user.Name}</h5>
);
});
}
render() {
var component;
if (this.props.users) {
component = this.renderUsers()
} else {
component = <h3>asdf</h3>;
}
return (
<div>
{component}
</div>
);
};
};
function mapStateToProps(state) {
return {
users: state.all
};
}
export default connect(mapStateToProps, { getUsers })(User);
Action
import request from '../helpers/request';
import { GET_USERS } from './types';
export function getUsers() {
return request.get(GET_USERS, 'Person/GetPeople');
}
With the get function from request.js module
get: function (action, url) {
return (dispatch) => {
axios.get(`${ROOT_URL}${url}`)
.then(({ data }) => {
dispatch({
type: action,
payload: data
});
});
};
}
User_reducer
import { GET_USERS } from '../actions/types';
export default function (state = {}, action) {
switch (action.type) {
case GET_USERS:
console.log(action);
return { ...state, all: action.payload }
default:
return state;
};
}
When I load the UserPage component, I can see the request being done and the state being updated in the redux dev tool, but I can't display this.props.users.
If I take out the if(this.props.users) in the render() method of the User component, I get an undefined on this.props.users.
What am I doing wrong?
Any help greatly appreciated! Thank you
SOLVED
The solution was to set the users property to state.users.all in the mapStateToProps function.
function mapStateToProps(state) {
return {
users: state.users.all
};
}
Thank you
Could you check, do you have this.renderUsers() defined in that render function? It might be you forgot to bind it, and that function is undefined?
Try to add this.renderUsers = this.renderUsers.bind(this); in your constructor and see, if it helps.