how to handle redux props in class methods - javascript

in my react App i'm using redux with redux-thunk.right now i'm getting props in my component but i'm unable to access latest props in my component methodsso i used componentWillReceiveProps to get latest props using nextprops then i'm saving nextprops into my states but the problem here is setState is asynchronous so when i'm fetching particular state in class methods,getting prev state value instead of nextprops value which is saved in state. but when i'm console those state in class methods using setInterval getting latest state value because setState value now saved.below is my code
Action creator
export function pickup(latlng) {
return function(dispatch) {
dispatch({ type: PICKUP_STATE,payload:latlng });
};
}
Reducer
import {
PICKUP_STATE,
PICKUP_ADD,
DROPOFF_STATE
} from '../actions/types';
export default (state={},action) => {
const INITIAL_STATE = {
pickup: '',
pickupAdd:''
};
switch(action.type) {
case PICKUP_STATE:
console.log(action.payload)
return {...state,pickup:action.payload};
case PICKUP_ADD:
return{...state,pickupAdd:action.payload};
case DROPOFF_STATE:
return {...state,dropoff:action.payload}
default:
return state;
}
//return state;
}
component
import {
connect
} from "react-redux";
import * as actions from "../actions"
class Map extends React.Component {
componentWillReceiveProps(nextprops) {
if (nextprops.pickupProps !== undefined) {
this.setState({
pick: nextprops.pickupProps
}, () => {
console.log(this.state.pick);
});
}
}
isPickEmpty(emptyPickState) {
this.props.pickup(emptyPickState);
// setTimeout(() =>{ console.log('sdkjlfjlksd',this.state.pick)
},3000);
console.log(this.state.pick);
}
}
const mapStateToProps = (state) => {
// console.log(state.BookingData.pickup);
return {
pickupProps:state.BookingData.pickup,
pickupAddProps: state.BookingData.pickupAdd
}
}
export default connect(mapStateToProps,actions)(Map);
App Root file
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import "normalize.css/normalize.css"
import "./styles/styles.scss";
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import reduxThunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import AppRouter from './routers/AppRouter';
import reducers from './reducers';
import {AUTH_USER} from "./actions/types";
const middleware = [
reduxThunk,
];
const store = createStore(reducers, composeWithDevTools(
applyMiddleware(...middleware),
// other store enhancers if any
));
const token = localStorage.getItem('token');
if(token){
store.dispatch({type:AUTH_USER});
}
ReactDOM.render(
<Provider store={store}>
<AppRouter />
</Provider>
, document.getElementById('app'));
1- how can i access latest props in my class methods
OR
2- how can i access nextprops setState value in my class methods
OR
3- any best way to solve this situation
please any one help me out from this situation, i'm stuck in from 3 days

If I understand it correctly, you still need to add a maps to dispatch to get the updated states from the store. The action creator still needs to be called and then mount it to your class method using componenetsDidMount
componentDidMount() {
this.props.fetchPickUp();
}
const mapDispatch = dispatch => {
return {
fetchPickUp: () => dispatch(pickUp()),
};

Related

React Redux not re-rendering when Store changes

So I have been trying to figure this out for a day now.
I think I have set up everything correctly, however, the view does not re-render nor the prop updates. However, I can see the change in Redux Developer tools. I know there are other questions like this on Stackoverflow but none of them really helps me.
Am I not seeing something?
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import Store from './store';
import * as serviceWorker from './serviceWorker';
const store = createStore(Store, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
ReactDOM.render(
<Provider store={store} >
<App />
</Provider>
,
document.getElementById('root'));
//actions.js
const initPurchases = (payload) => {
return {
type: "INITILIZE_PURCHASES",
payload
}
}
module.exports = {
initPurchases,
}
// store.js
const initalState = {
inventory: [],
}
const rootReducer = (state = initalState, action) => {
switch(action.type) {
case "INITILIZE_PURCHASES":
state.purchases = [...action.payload];
break;
default:
return state;
}
return state;
}
export default rootReducer
import React from 'react';
import { connect } from 'react-redux';
import actions from './actions';
class App extends React.Component {
state = {}
componentDidMount = () => {
this.getPurchases();
}
getPurchases = async () => {
// call to api which returns t
this.props.initPurchases(t)
}
render() {
console.log(this.props.purchases) // Returns empty array []
return (
<div className="App">
// Some view
</div>
);
}
}
export default connect(
(state) => {return {purchases: state.purchases}},
actions,
)(App);
Logs from React Redux Developer Tools
Can somebody please help me? I can't figure out what's wrong here. I ommited most of the things that i are not related to my problem (at least I do not think they are). I can upload the entire repo to github to see the bigger context
Your reducer needs to return the new state, otherwise the state remains unchanged:
const rootReducer = (state = initalState, action) => {
switch(action.type) {
case "INITILIZE_PURCHASES":
return { ...state, purchases: [...action.payload] };
break;
default:
return state;
}
return state;
}
I think you need to implement something like:
import actions from './actions'
...
class App extends React.Component {
...
componentDidMount = () => {
this.props.initPurchases();
}
render() {
...
}
}
const mapDispatchToApp = (dispatch) => (
{
initPurchases: () => (
dispatch(actions.initPurchases())
),
}
)
...
export default connect(
(state) => {return {purchases: state.purchases}},
mapDispatchToApp,
)(App);
This is because you need to dispatch actions to the store

React-Redux connect isn't getting store from Provider

Im using Redux with React Native to manage state. I believe that I've successfully set up the store and Provider. I can use store.getState() and store.dispatch(action()) from any component successfully, however, the react-redux connect function is not allowing me to access the store from child components. Can you find anything wrong with my code below?
Login.js - This child component I'm testing won't access redux store with react-redux connect.
import React, {Component} from 'react';
import actions from '../../redux/actions';
import {connect} from 'react-redux';
const mapStateToProps = state => {
// To test if this function fires, which it is not
console.log('login state mapping through redux');
return {
state: state,
};
};
const dispatchToProps = dispatch => {
return {
userRecieved: (user) => dispatch(actions.userRecieved(user)),
};
};
export class Login extends Component {
constructor(){
super();
this.state = {
credentials: {
email: '',
password: '',
},
};
}
componentDidMount(){
// This will show whether redux is connected
console.log(this.props.state);
this.props.userRecieved('TEST USER');
}
render() {
return ( <Text>{this.props.state}</Text> );
}
}
export default connect(mapStateToProps, dispatchToProps)(Login);
App.js
import React, {Component} from 'react';
import YEET from './src/YEET.js';
import store from './src/redux/stores/index';
import {Provider} from 'react-redux';
export default class App extends Component {
render() {
return (
<Provider store={store}>
<YEET />
</Provider>
);
}
}
My Redux Files:
store.js
import { combineReducers, createStore} from 'redux';
import accountReducer from '../reducers/accountReducer';
import postReducer from '../reducers/postReducer';
const initialState = {};
const reducers = combineReducers({
account: accountReducer,
post: postReducer,
});
const store = createStore(reducers, initialState);
export default store;
actions.js
import constants from '../constants';
var userRecieved = user => ({
type: constants.USER_RECIEVED,
data: user,
});
export default {
userRecieved,
};
accountReducer.js
import constants from '../constants';
var initialState = {
user: {
photos: [],
},
};
export default (state = initialState, action ) => {
let newState = Object.assign({}, state);
switch (action.type) {
case constants.USER_RECIEVED:
const user = {
id: action.data.uid,
// photos: action.data,
};
console.log(action);
newState.user = user;
return newState;
default:
return state;
}
};
From what I see, the only reason could be that you're importing the unconnected component.
When you import the Login component, make sure that you import the default export instead of the named export.
So, wherever you import the Login component, do it like this:
import Login from 'your-login-component-location/Login'
instead of
import { Login } from 'your-login-component-location/Login'
The second one is a named export, which will return the Login class directly.
The first one is the default export, which will return the connected component.

Why state returned from my reducer is an empty object??(React-Redux)

I have a problem in my redux reducer, it does not return expected state after dispatching FETCH_BOOKS action, it returns an empty object instead of an object of state which is books that is fetched by AJAX request,
the reducer returns correct data when storing my state in array instead of object, this is so confusing, why does this happen??
These are my components
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import * as BooksAPI from './BooksAPI';
import registerServiceWorker from './registerServiceWorker';
import { createStore, applyMiddleware } from 'redux';
import { bookReducer } from './reducers/BookReducer';
import thunk from 'redux-thunk';
import {BrowserRouter as Router} from 'react-router-dom';
const middleware = [thunk];
const initialState = {};
const store = createStore(bookReducer, initialState, applyMiddleware(...middleware));
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import BookShelf from './components/BookShelf'
import AllShelves from './components/AllShelves'
import Header from './components/Header';
import SearchPage from './components/SearchPage';
import * as BooksAPI from './BooksAPI';
import { Route, withRouter } from 'react-router-dom';
import './App.css';
class App extends Component {
componentWillMount() {
this.props.fetchBooks();
}
render() {
console.log(this.props.books)
return (
<div className="App">
<Header />
<Route exact path="/" component={AllShelves} />
<Route path="/search" component={SearchPage} />
</div>
);
}
}
const mapStateToProps = (state) => {
return {
books: state.books
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchBooks: () => {
BooksAPI.getAll().then(books => dispatch({
type: 'FETCH_BOOKS',
books
}))
},
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App))
reducer that doesn't works
import { FETCH_BOOKS } from '../actions/Types.js';
import * as BooksAPI from '../BooksAPI'
const initialState = {
books: [],
query: ''
}
export const bookReducer = (state = initialState, action) => {
switch(action.type) {
case 'FETCH_BOOKS':
return {
...state,
books: action.books,
}
default:
return state;
}
}
The reducer that work
export const bookReducer = (state = [], action) => {
switch(action.type) {
case 'FETCH_BOOKS':
return action.books
default:
return state;
}
}
So why storing state in object doen't work and it works perfectly with array, i don't want to store my state in array, as books is not the only data i need to manage in my state!!!
I've checked all your codes and I think the problem possibly come from the redux store setup:
const initialState = {};
const store = createStore(bookReducer, initialState, applyMiddleware(...middleware));
I suggest removing the initialState:
const initialState = {}; // remove this line cuz we don't need it
const store = createStore(bookReducer, applyMiddleware(...middleware)); //fixed like this
In addition, I think you should fetch your books in the componentDidMount() lifecycle hook instead of componentWillMount(), like this:
componentDidMount() {
this.props.fetchBooks();
}
In the second example, you are fetching the value in the reducer as action.books, instead it should be action.payload because that's the key dispatched in action.
After any Action is dispatched you have to return a new state to the store so you have to return new state object for that you have to get the state now and change the books and return the new state so following code doing that
export const bookReducer = (state = initialState, action) => {
switch(action.type) {
case 'FETCH_BOOKS':
return {
...state,
books: action.books,
}
default:
return state;
}
}
but from the other reducer you return only the books array that coming from the action that is wrong way to do that

Can´t access props after using CombineReducer

My previous React-Redux implementation was working, but after I tried to implement the combineReducer function with seperated files, an error is thrown that I don´t really understand. Hope some of you can help me!
ERROR: Uncaught TypeError: this.props.todos.map is not a function
My Reference for that Code was the Async Example of the Redux-Doc´s. But I stated with another example and the change from each examples are not documented in the doc´s.
The first code I will show, is that I had (working):
MyStore
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import addItem from '../reducers/addItem'
export default function configureStore(preloadedState) {
const store = createStore(
addItem,
preloadedState,
applyMiddleware(thunkMiddleware, createLogger())
)
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default
store.replaceReducer(nextRootReducer)
})
}
return store
}
My Reducer
export default (state = ['Test'], action) => {
switch (action.type){
case 'ADD_ITEM':
//return action.item
return [
...state,
{
id: action.id,
text: action.item
}
]
default:
return state
}
}
Actions
export default function addItem(item){
console.log("addTOdo")
return {
type: 'ADD_ITEM',
id: nextTodoId++,
item
}
}
And the subComponent where the input is finally rendered
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
export default class TodoList extends Component {
render() {
const posts = this.props
const isEmpty = posts.length === 0
return (
<div>
<h3>Meine Aufgaben</h3>
<ul>
{isEmpty
? <h3>Sie haben noch keinen Todo´s angelegt</h3>
: <h3>Ihre Ergebnisse</h3>
}
{this.props.todos.map((todo, i) => <li key={i}>{todo.text} </li>)}
</ul>
</div>
)
}
}
const mapStateToProp = state => ({todos: state})
export default connect (mapStateToProp)(TodoList)
What I have change:
First, I created another Reducers File, called Index where I imported the addItem Reducer and exported the rootReducer:
import {combineReducers} from 'redux'
import addItem from './addItem'
import getItem from './getItem'
const rootReducer = combineReducers({
addItem,
getItem
})
export default rootReducer
After that, I changed the Store to import the rootReducer and put it´s reference in the Store (just the changes to configureStore):
import rootReducer from '../reducers/index'
const store = createStore(
rootReducer,
preloadedState,
applyMiddleware(thunkMiddleware, createLogger())
)
I don´t know if that Information is also required, but here is my Container Component:
import React, { Component, PropTypes } from 'react'
import AddTodo from '../components/AddTodo'
import TodoList from '../components/TodoList'
import { connect } from 'react-redux'
import addItem from '../actions/addItem'
import getItems from '../actions/getItems'
class App extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.state = {text: ''}
}
handleClick(e){
console.log(e);
const {dispatch} = this.props
dispatch(addItem(e));
}
componentDidMount(){
console.log("COMPONENT MOUNT");
const {dispatch} = this.props
// dispatch(getItems())
}
componentWillReceiveProps(nextProps) {
console.log("GETTT IT");
console.log(nextProps)
}
render() {
return (
<div>
< h1 > Hallo </h1>
<AddTodo handleAddItem={this.handleClick}/>
<TodoList/>
</div>
)
}
}
App.propTypes = {
dispatch: PropTypes.func.isRequired
}
function mapStateToProps(state){
return {
AddTodo
}
}
export default connect (mapStateToProps)(App)
I hope this issue is not to basic and someone can help me. Thanks in advance!
If you inspect your redux state you will see that the following code sets up 2 more keys in the state (addItem and getItem):
const rootReducer = combineReducers({
addItem,
getItem
})
So, now to connect todos you need to one of the 2 new keys. If todos is not defined on those, then you need to add the reducer of todos to the combineReducers call.
So this needs to map to a valid location in state:
const mapStateToProp = state => ({todos: state})

React + Redux: Component does not update

Trying out React + Redux, and probably am doing something obviously stupid, because a component that fires an action to fetch data over the network does not get updated (re-rendered) when the data is fetched.
Here are the relevant bits of my code:
The top-level index.js serving as an entry point for the app:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, browserHistory } from 'react-router';
import reduxPromise from 'redux-promise';
import createLogger from 'redux-logger';
const logger = createLogger();
import routes from './routes';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware(reduxPromise, logger)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Router history={browserHistory} routes={routes} />
</Provider>
, document.querySelector('.container'));
Top-level container App:
import React, {Component} from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions';
import Header from '../components/header';
import Showcase from '../components/showcase';
function mapStateToProps(state) {
return {
resources: state.resources
}
}
function mapDispatchToProps(dispatch) {
return {
fetchResources: () => {
dispatch(Actions.fetchResources());
}
}
}
class App extends Component {
render() {
console.log('props in App', this.props);
return (
<div>
<Header/>
<Showcase
fetchResources={this.props.fetchResources}
resources={this.props.resources}
/>
</div>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
Component that triggers an action to sends a request for data when it is about to mount and is supposed to show the fetched data:
import React, {Component} from 'react';
import {connect} from 'react-redux';
class Showcase extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchResources();
}
render() {
console.log('resources', this.props);
return (
<div>
This is showcase
</div>
);
}
}
export default connect(state => ({resources: state.resources}))(Showcase)
Action Creator:
import * as types from '../constants/ActionTypes';
import axios from 'axios';
export function fetchResources() {
return {
type: types.FETCH_FIRST,
payload: axios.get('/sampledata/1.json')
}
}
Reducer for the fetch action:
import * as types from '../constants/ActionTypes';
export default function resourcesReducer (state={}, action) {
switch (action.type) {
case types.FETCH_FIRST:
console.log('about to return', Object.assign (state, {resources: action.payload.data }))
return Object.assign (state, {resources: action.payload.data });
default:
return state
}
};
and finally the root reducer:
import { combineReducers } from 'redux';
import navigationReducer from './navigation-reducer';
import resourcesReducer from './resources-reducer';
const rootReducer = combineReducers({
navigationReducer,
resourcesReducer
});
export default rootReducer;
So, here is what I am observing. The action to request data is successfully triggered, a request is sent, the reducer receives it when the promise is resolved, and updates the state with the fetched data. At this point, I would expect the top-level App component and the Showcase component to detect that the store has updated, and to re-render, but I do not see it in the console.
Also, I am confused by redux-logger’s console output:
Specifically, I am surprized to see that the state contains reducers from the rootReducer — I don't know if it's right (an example on Redux logger Github page shows a state without reducers). It also seems surprising that the prev state as reported by redux-logger contains the same resourcesReducer object as the next state, although intuitively I would expect prev state to be more or less empty.
Could you please point out what I am doing wrong and how to get React components respond to the state changes?
==================================================
UPDATED:
1) Changed the mapStateToProps function in the App component so that it correctly maps to reducer states:
function mapStateToProps(state) {
return {
resources: state.resourcesReducer
}
}
2) Still passing the resources down to the `Showcase component:
render() {
console.log('props in App', this.props);
return (
<div>
<Header navigateActions={this.props.navigateActions}/>
React simple starter
<Showcase
fetchResources={this.props.fetchResources}
resources={this.props.resources}
/>
</div>
);
3) Trying to display resources on the screen by stringifying it to see what’s actually inside this object:
render() {
console.log('resources', this.props);
return (
<div>
This is showcase {JSON.stringify(this.props.resources)}
</div>
);
}
See this on the screen: This is showcase {}. The component does not seem to re-render.
Here’s the screenshot of the console showing that App’s props have updated with the values from the next state. Still, that did not cause the component to re-render:
UPDATED AGAIN: And my javascript-fu was poor, too. I did not quite realize that by returning Object.assign (state, {resources: action.payload.data }); I was in fact mutating the state, and that a simple inversion of arguments would let me achieve what I intended. Thanks to this discussion on SO for enlightenment.
I am surprized to see that the state contains reducers from the rootReducer
This is how it works. Take a closer look at combineReducers().
const rootReducer = combineReducers({
navigationReducer,
resourcesReducer
});
Recognise that it's not a list of parameters; it's a single object parameter. Perhaps it is clearer in verbose syntax:
var rootReducer = combineReducers({
navigationReducer: navigationReducer,
resourcesReducer: resourcesReducer
});
The resourcesReducer key points to the state returned by the resourcesReducer() function. That is, the state variable within the resourcesReducer() is just one part of the entire state.
The functions passed to connect() take the entire state as an argument. What yours should actually look like is this:
export default connect(state => ({
resources: state.resourcesReducer.resources
}))(Showcase);

Categories