I am trying to use redux with react. The mapStateToProps is not called.
The dispatch calls are working properly, the action reaches the reducer and the newState is returned and the state of the store is modified. But mapStateToProps is not called and the component is not rerendering.
class Wrapper extends React.Component {
updateChartData (data) => {
...logic...
this.props.actions.chartDataAction(modifiedData);
}
}
function mapStateToProps(state, ownProps) {
return {
chartData: state.chartData
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(chartDataAction, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Wrapper);
The other classes that use updated state is as follows
class Chart extends React.Component {
render(){
/*console.log(this.props.chartData);*/
}
}
function mapStateToProps(state, ownProps) {
console.log(state);
return {
chartData: state.chartData
};
}
let ChartComponent = withStyles(styles)(Chart);
export default connect(mapStateToProps)(ChartComponent);
The action is as follows
export default function (state = [], action) {
if (action.type === actionTypes.CHANGE_CHARTDATA) {
return action.chartData;
} else {
return state;
}
}
The reducer is
export default function (state = [], action) {
if (action.type === actionTypes.CHANGE_CHARTDATA) {
return action.chartData;
} else {
return state;
}
}
I've even checked using redux dev tools. The chartData is being updated in the redux store. The chartData is properly passed to the Chart class and it renders on the first update. On consecutive dispatching, the chartData is updated in the redux store, but the mapStateToProps of Chart class is not called and the Chart class is not rerendering.
UPDATE:
I'm trying to live update the chart and it does not render if the few entries in the chartData array are changed.
I noticed that the chart component rerenders when the whole chartData is changed. But I want it to rerender if any part of the chartData is changed.
Edit : Be sure to follow the immutable update patterns even outside of your reducer (in updateChartData() method in your case).
You are probably mutating data in your "...logic...", you can use immutable-js to make it easier for you.
Related
Im trying to make an api request from redux then take that data and put it in my react state (arrdata). The api call works but i cant seem to get the state on my app.js to update based on the redux api call. Am i missing something?
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
arrdata: []
};
}
componentDidMount() {
this.props.loadData();
console.log(this.props.data);
}
render() {
const {arrdata} = this.state
return ( ......)}}
const mapStateToProps = state => {
return {
data: state.data
};
};
export default connect(mapStateToProps, dataAction)(App);
Action
export function loadData() {
return dispatch => {
return axios.get("https://api.coincap.io/v2/assets").then(response => {
dispatch(getData(response.data.data.slice(0, 10)));
});
};
}
export function getData(data) {
return {
type: "GET_DATA",
data: data
};
}
Reducer
let initialState = {
data: []
};
const mainReducer = (state = initialState, action) => {
if (action.type === "GET_DATA") {
return {
...state,
data: action.data
};
} else {
return {
...state
};
}
};
export default mainReducer;
I think you are misleading store with state. Your arrdata is empty since it's stored inside state, but your data comes from props.
Anyways, arrdata in state remains empty, since you are not setting the state anywhere. To do that, you would have to use e.g. getDerivedStateFromProps lifecycle hook, however I wouldn't recommend that.
render() {
const { data } = this.props;
console.log(this.props.data);
return (
// do something with your data
);
}
It should log your data properly.
Note: You don't need state, actually. It's a better approach to manipulate over props, instead of saving data from props into state (in most cases).
I am developing a Web application using React JS + Redux. I am new to React. What I am doing now is trying to set the state on one page and then retrieve the state in another page after redirection.
I have a Component called EventListComponent that displays the list of events. Inside that component, I change the state of a reducer calling an event.
This is the reducer function I call.
import * as EditEventActions from '../actions/edit.event.actions';
export default function (state = { }, action){
switch (action.type)
{
case EditEventActions.EVENT_SET_EDITING_EVENT:
return { ...state, event: action.payload }
default:
return state;
}
}
I fire the actions before redirecting to another page like this.
this.props.setEditingEvent(event);
this.props.history.push({
pathname : '/event/'+ event.id +'/edit'
});
In the new page, I render the component called, EditEventComponent.
This is the definition of the EditEventComponent
export class EditEventComponent extends React.Component{
constructor(props)
{
super(props);
alert(this.props.event.id)//cannot retrieve event here
}
render(){
return (
<h4>This is the Edit Event component</h4>
);
}
}
function mapStateToProps(state)
{
return {
event: state.editEvent.event
};
}
function matchDispatchToProps(dispatch)
{
return bindActionCreators({
}, dispatch);
}
const enhance = compose(withWidth(), withStyles(themeStyles, { withTheme: true }), connect(mapStateToProps, matchDispatchToProps))
export default enhance(EditEventComponent);
As you can see, inside the EditEventComponent I am trying to retrieve the event field of the state which is set in the previous page. But I cannot retrieve it.
My questions are
Is the state (of the redux store) reset after redirecting to the new page?
What is wrong with my code?
If what I am doing is not the right approach, what would be the best way to pass an object from one page to another in React Redux?
Here is my action
export const EVENT_SET_EDITING_EVENT = "(EVENT) SET EDITING EVENT";
export const setEditingEvent = (data) => ({
type: EVENT_SET_EDITING_EVENT,
payload: data
});
Here is the reducer
import * as EditEventActions from '../actions/edit.event.actions';
export default function (state = { }, action){
switch (action.type)
{
case EditEventActions.EVENT_SET_EDITING_EVENT:
return { ...state, event: action.payload }
default:
return state;
}
}
I expose the EventListComponent in this way as well.
const enhance = compose(withWidth(), withStyles(themeStyles, { withTheme: true }), connect(mapStateToProps, matchDispatchToProps))
export default enhance(EventListComponent);
You are probably not setting the type correctly in setEditingEvent action and the reducer returns the initial state since it doesn't hit EVENT_SET_EDITING_EVENT
I have a simple redux action and reducer that changes state without making any sort of web service request:
// Action
export const setMyState = (value) => {
return {
type: 'SET_STATE',
payload: value
};
};
// Reducer
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SET_STATE':
return { myState: action.payload }
}
}
When I set the state via the action in my react component and immediately call that state from the redux prop binding, I do not get the current state:
import React from 'react';
import { connect } from 'react-redux';
import { setMyState } from '../actions';
class MyScreen extends React.Component {
componentWillMount() {
this.props.setMyState('value');
console.log(this.props.myState);
}
}
const mapStateToProps = state => {
const { myState } = state;
return myState;
};
export default connect(mapStateToProps, { setMyState })(MyScreen);
I understand that render will be called again when the redux state and binded props change, but what I'm trying to do is fire off another Redux action based on the resulting state. My options appear to be:
Figure out how to latest Redux state in React immediately after state change
Fire off a redux action in the reducer instead, which sounds like an anti-pattern.
Set some sort of state in my component or redux that the action should be fired on the next render, which seems clunky.
config your reducer like:
switch (action.type) {
case 'SET_STATE':
return {
...state,
loading: true,
};
case 'SET_STATE_SUCCESS':
return {
...state,
loading: false,
payload: value
};
}
and then in your component listen to
this.props.myState.loading
then trigger your action if (loading) and so on!
My solution is to use event loop handles the update, not the call stack. In particular for your case you the implementation would include setTimeout(() => {}); as the following:
import React from 'react';
import { connect } from 'react-redux';
import { setMyState } from '../actions';
class MyScreen extends React.Component {
componentWillMount() {
setTimeout(() => { this.props.setMyState('value'); },0);
setTimeout(() => { console.log(this.props.myState); },0);
}
}
const mapStateToProps = state => {
const { myState } = state;
return myState;
};
export default connect(mapStateToProps, { setMyState })(MyScreen);
What are you using this Redux state for?
If you are trying to access the data you had just set in the Redux state or if it's data that's already sitting in the Redux state you could dispatch both calls at the same time.
If you need to make the call because there is some work done to the data while setting it in the Redux state (I recommend doing this work in the action not the reducer) then componentWillReceiveProps(nextProps) will do what you need. You can compare the current props with the next props and trigger another update to the Redux state.
The best way to do this would probably be to use a Redux middleware.
I am having bit of a wrinkle with Redux-Saga as the reducers I had done isn't updating the store as it should suppose to do.
I have used Saga to load a static in-app data and then fired the action with the payload passing the data to reducers, I did console.log() to make sure the reducers are getting the payload from action, which they are - but the problem appears to happen when I return the data into the state so that it could be accessible within the components. In props I only get default state from the reducers, any help on this matter would be highly appreciated. Following is the code I am using;
actions.js
export function loadAppAction() {
return {
type: types.LOAD_APP
}
}
export function loadAppDataAction(data) {
return {
type: types.LOAD_APP_DATA,
payload: data
}
}
api.js
import appData from '../components/appData';
export function appDataResponse() {
return appData;
}
app.js
export class App extends Component {
constructor(props) {
super(props);
const { loadAppAction } = this.props;
loadAppAction();
}
render() {
const {
initialLoadData,
activateModalAction,
deactivateModalAction,
toggleModal
} = this.props;
console.log('props', this.props)
return (
<div className="app">
{
toggleModal &&
<SignInModal
deactivateModalAction={deactivateModalAction}
/>
}
</div>
}
}
function mapStateToProps({ initialLoadReducer, toggleModalReducer }) {
console.log('lets see', initialLoadReducer);
return {
initialLoadData: initialLoadReducer,
toggleModal: toggleModalReducer
};
};
export default connect(
mapStateToProps, {
loadAppAction: actions.loadAppAction,
activateModalAction: actions.activateModalAction,
deactivateModalAction: actions.deactivateModalAction,
})
(App);
initialLoadReducers.js
export default function (state = [], action) {
switch (action.type) {
case types.LOAD_APP_DATA:
return [...action.payload];
default:
return state;
}
}
saga - index.js
function* watchLoadAppAction() {
yield takeEvery(types.LOAD_APP, loadAppSaga);
}
export default function* rootSaga() {
yield all ([watchLoadAppAction()]);
}
loadAppSaga.js
export default function* loadAppSaga(action) {
const response = yield call(api.appDataResponse);
yield put(actions.loadAppDataAction(response));
}
Following is the screenshot of my console for reference
I would suggest to call loadAppAction when componentDidMount not in the constructor. React doc also suggested the same.
https://reactjs.org/docs/react-component.html#componentdidmount
export class App extends Component {
componentDidMount() {
this.props.loadAppAction();
}
...
}
Right so, I got down to the problem here, I was trying to initialise the app with an empty array which will not going to work anyway, as the component is expecting to receive props from redux which is an empty array. Which is why, React didn't create the DOM at its first run and that caused the app to stop re-rendering even though the props are changing.
To make it work, I now initialise the app with the same data structure but with empty string values and in the next step making the data available through Redux Saga into the reducer and passing it back into the React component.
I know it's me... Because I suck :)
But I believe I am doing everything correct but when I dispatch my action and the state changes my view does not re-render.
I believe this is the simple change that might be needed but the
render()
render(){
....
<div className={data.participants}>
+{store.getState().currentSex.participants}
</div>
....
}
Function that calls the action
onSetParticipants = () => {
console.info(store.getState()); //participants = 1
store.dispatch (currentSex.setParticipants(3));
console.info(store.getState()); //participants = 3
}
reducer currentSex.js
import { List, Map } from 'immutable';
const initialState = {
participants: 1
};
function currentSex (state = initialState, action) {
switch (action.type) {
case 'SET_PARTICIPANTS':
return {
...state,
participants:action.participants
}
}
return state
}
export default currentSex;
Actions
export const SET_PARTICIPANTS = 'SET_PARTICIPANTS';
export function setParticipants(participants) {
return {
type: SET_PARTICIPANTS,
participants: participants
}
}
How I have done my connect, as I believe this helps
function mapStateToProps(state, ownProps) {
return {
errorMessage: state.errorMessage,
inputValue: ownProps.location.pathname.substring(1)
}
}
export default connect(mapStateToProps, { })(App)
Please forgive me is this isn't enough or completely the wrong information.
But why does my page no re-render when i can see the state has changed?
edit
Its worth mentioning that my state has objects inside of it:
In your mapStateToProps you need to add the state that you want to render inside your component. In this case it looks like state.participants.
function mapStateToProps(state, ownProps) {
return {
errorMessage: state.errorMessage,
participants: state.participants,
inputValue: ownProps.location.pathname.substring(1)
}
}
And then use this.props.participants in your component.
import * as actions from '../actions';
/* ... */
render(){
....
<div className={data.participants}>
+{this.props.participants}
</div>
....
}
/* ... */
export default connect(mapStateToProps, actions)(App)
edit
And add the actions to your connect function, as well as importing them. Call your actions using this.props.currentSex(3) inside the function within your component that handles change events.