react props not updating with redux store - reactjs

I've always used react-redux connect to configure props but I need to use a react Component to use lifecycle methods. I'm noticing that my props that I'm grabbing from the store seem to be static and they do not update as the store updates.
Code:
class AlertModal extends Component {
title
isOpen
message
componentDidMount() {
const { store } = this.context
const state = store.getState()
console.log('state', state)
console.log('store', store)
this.unsubscribe = store.subscribe(() => this.forceUpdate())
this.title = state.get('alertModal').get('alertModalTitle')
this.isOpen = state.get('alertModal').get('isAlertModalOpen')
this.message = state.get('alertModal').get('alertModalMessage')
this.forceUpdate()
}
componentWillUnmount() {
this.unsubscribe()
}
updateAlertModalMessage(message) {
this.context.store.dispatch(updateAlertModalMessage(message))
}
updateAlertModalTitle(title) {
this.context.store.dispatch(updateAlertModalTitle(title))
}
updateAlertModalIsOpen(isOpen) {
this.context.store.dispatch(updateAlertModalIsOpen(isOpen))
}
render() {
console.log('AlertModal rendered')
console.log('AlertModal open', this.isOpen) <======= stays true when in store it is false
return (
<View
How do I set up title, isOpen, and message so they reflect the store values at all times?

It should be something like this. In your Confirmation component:
const mapStateToProps = (state) => {
return { modalActive: state.confirmation.modalActive };
}
export default connect(mapStateToProps)(Confirmation);
In your reducer index file, is should be something like this:
const rootReducer = combineReducers({
confirmation: ConfirmationReducer
});
I believe you have your own reducer file called ConfirmationReducer here. It should be something like this.
import { ON_CONFIRM } from '../actions';
const INITIAL_STATE = {modalActive: true};
export default function(state = INITIAL_STATE, action) {
console.log(action);
switch (action.type) {
case ON_CONFIRM:
return { ...state, modalActive: action.payload };
}
return state;
}
Make sure you write your own action creator to create an action with the above type and relevant payload of boolean type.
Finally you should be able to access the property from the store inside your Confirmation component like this:
{this.props.modalActive}
You have not posted entire code, so it makes very difficult to give a solution to the exact scenario. Hope this helps. Happy coding!

For me the problem was that I was assigning this.props.myObject to a variable which wasn't deep cloned so I fixed it by using
let prev = Object.assign({}, this.props.searchData)
What I was doing
let prev = this.props.searchData
So I was disturbing the whole page.Seems quiet noob on my part.

this may help you
componentWillReceiveProps(nextProps) {
console.log();
this.setState({searchData : nextProps.searchData})
}

Related

Refresh components after modifying Redux store

After creating a store, when I modify datas from JASON file, the refresh arrive at the connect function but the component is not rebuild.
I think it's caused by the immutability, I got something
Store
function songReduceur(state = initialState, action) {
switch (action.type) {
case CONSTANTS.DELETE_CHORD:
let DELETE_C_newChordOrder = state.mesures[action.mesure].chordOrder
DELETE_C_newChordOrder.splice(action.index, 1)
return {
...state,
mesures:{
...state.mesures,
[action.mesure]: {
...state.mesures[action.mesure],
chordOrder: DELETE_C_newChordOrder
}}}
default:
return state
}
}
export default songReduceur
Component
Here the state is update but not on the component MesureArea itself
const mapStateToProps = (state, ownProps) => {
const id = ownProps.id
return {
chiffrage: state.song.mesures[id].chiffrage,
chordOrder: state.song.mesures[id].chordOrder,
chordHistoric: state.song.chordHistoric
}
}
export default connect(mapStateToProps)(MesureArea)
The Solution
The mistake is again the immutability x) it's something like
chordOrder: [...DELETE_C_newChordOrder]

React component doesn't receive props after Redux store update

What does work:
Saga pulls the data from an API. The reducer for UPDATE_LOTS fires up and returns the new state.
Redux store is updated with the correct data as can be observed in the chrome extension and through logging.
What doesn't work:
The componentDidUpdate never fires up. Nor does componentWillReceiveProps when replaced by it.
Since the component never received an update, there's no re-rendering either.
Most of the advice on this topic discusses how people accidentally mutate the state, however in my case I don't do that. I've also tried the following construction {...state, lots: action.data} instead of using ImmutableJS's Map.set with no luck.
Any ideas? Not listing the saga files here because that part of the data flow works perfectly.
The component:
class Lots extends React.Component {
constructor(props) {
super(props);
this.props.onFetchLots();
}
componentDidUpdate() {
console.log('updated', this.props.lots);
}
render() {
const lots = this.props.lots;
console.log('render', lots);
return (lots && lots.length) > 0 ? <Tabs data={lots} /> : <div />;
}
}
Mapping and composition:
function mapDispatchToProps(dispatch) {
return {
onFetchLots: () => {
dispatch(fetchLots());
},
};
}
function mapStateToProps(state) {
return {
lots: state.lots,
};
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
const withReducer = injectReducer({ key: 'lots', reducer: lotsReducer });
const withSaga = injectSaga({ key: 'lots', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(Lots);
Reducer:
export const initialState = fromJS({
lots: false,
});
function lotsReducer(state = initialState, action) {
switch (action.type) {
case FETCH_LOTS:
console.log('fetch lots');
return state;
case UPDATE_LOTS:
console.log('update lots', action.data.data);
return state.set('lots', action.data.data);
default:
return state;
}
}
Everything was correct except for the mapStateToProps function.
Since ImmutableJS was used, I had to access the state property as state.get("lots") instead of state.lots.
Doing so fixed the problem.

Passing second argument (ownProps) to mapDispatchToProps - is a bad pactice?

I'm new in developing React app. So, i have some questions.
What's the best practice to access state properties inside mapDispatchToProps? Is it a bad practice to use ownProps.store.getState() in mDTP function?
What's the reason to use second argument in mDTP (except to passing additional properties in container components)?
Please advise what to read on this theme.
Thank you very much! Sorry, for my language.
This answer might help you:
What is mapDispatchToProps?
mapDispatchToProps exists to make the reducers accessible through the component, when following a Container > Component pattern.
For example, i have these reducers:
const updateToPreviousMonth = (state) => {
let newState = state,
currentMonth = newState.get('currentMonth');
let previousMonth = sanitizedDate(moment(currentMonth).subtract(1, 'month'));
return newState.set('currentMonth', previousMonth);
};
const updateSelectedDate = (state, action) => {
return state.set('selectedDate', action.selectedDate);
};
export default (state = initialState, action = {}) => {
switch (action.type) {
case constants.SET_TO_PREVIOUS_MONTH:
return updateToPreviousMonth(state);
case constants.UPDATE_SELECTED_DATE:
return updateSelectedDate(state, action);
default:
return state;
}
};
The constants are the Actions, and the functions (reducers) which return the changed state.
const mapDispatchToProps = {
setToPreviousMonth: CalendarViewRedux.actions.setToPreviousMonth,
updateSelectedDate: CalendarViewRedux.actions.updateSelectedDate
};
export class CalendarView extends PureComponent {
componentDidMount() {
this.props.loadSchedules();
}
render() {
return (<CalendarViewRender
{...this.props} />);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CalendarView);
On this example, i am passing the actions in mapDispatchToProps, and when called, they will activate the reducers from before, since i used mapDispatchToProps, they are now available in the CalendarView component.
Hope this helps, please mark as solved if this was helpful.
I have 'ownProps' on my mapDispatchToProps to navigate to HomeScreen if I need a reset state (in my case I need to show different screens if user is logged on or off). Here's the example:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onChangeText: (key, value) => {
dispatch(onChangeField(key, value))
},
goToHomeScreen: () => {
ownProps.navigation.dispatch(StackActions.reset({index: 0, key: null, actions: [NavigationActions.navigate({ routeName: 'LoggedDrawer'})]}))
},
}
}
And for calling i simply do this:
this.props.goToHomeScreen();
Also, I believe this could help:
What is the use of the ownProps arg in mapStateToProps and mapDispatchToProps?

Redux, React, MapStateToProps: Retrieve a specific element in an array from the redux state tree

I have a reducer named "leagues" in the redux state tree, which is just an array of individual league objects. Each league has a unique id (assigned in the backend), and a name.
I'm trying to write a Component that represents an individual league, and so I want to have a mapStateToProps function that retrieves the correct league. The correct league is known from the url, that is, through the match prop in react-router-v4.
I tried:
function mapStateToProps(state, ownProps) {
return {
league: state.leagues.find(aLeague => aLeague.id === ownProps.match.params.id)
}
}
But that led to an error "state.leagues.find" is not a function.
Then I tried
function mapStateToProps(state, ownProps) {
return {
league: state.leagues[ownProps.match.params.id]
}
}
which doesn't error, but retrieves the wrong league - if the id is 3, then this retrieves state.leagues[3], instead of state.leagues.where(league.id === 3)
The value of leagues when I attempt to access the a single league's page:
leagues: [
{
id: 54,
name: 'Test League'
},
{
id: 55,
name: 'Another Test'
}
],
And the leagues reducer code:
const initialState = {
leagues: []
};
export default (state = initialState, action = {}) => {
switch(action.type) {
case SET_USER_LEAGUES:
return state = action.leagues
case ADD_USER_LEAGUE:
return [
...state,
{
id: action.league.id,
name: action.league.name,
}
];
default: return state;
}
}
Any help is much appreciated.
Thanks!
This is because when the redux store is initialized you most likely are setting the initial state to null which is why you get an error state.leagues.find is not a function and once the state is resolved through an async ajax call then the state is there. I recommend to make that kind of logic in a react lifecycle method like componentDidMount and set the state there for the league once the leagues state is available. like this:
componentDidMount() {
const { leagues } = this.state;
if (leagues && leagues.length > 0) {
// update the state to the correct league here and this.props.id is the id that you want
const league = leagues.find(aLeague => aLeague.id === this.props.id);
this.setState({
league
}
}
}
I hope that helps.
It seems like when your component first renders, its default state has been set to null and causes the app to crash when you try to use array method find on a null object. Set your initial state for your leagues reducer to an empty array.
Then if your array is empty, your app probably hasn't retrieved results yet, and you can display a message like "Loading...".
However, this doesn't solve the problem of you actually have 0 items in your database, for example. Then you'll show falsely show loading even when there is 0 records.
For that, I would also suggest adding a isLoading reducer (with default state true), that maintains the state of your application during the time it is fetching async data. When your async calls complete, dispatch an action to update the appstate and set isLoading to false. Only then should you try to retrieve values from your leagues reducer.
I would also suggest you have another "league" reducer that does the filtering so you don't have to maintain this logic in your component.
I see that Array.prototype.find is not supported in IE. So, there could be a browser compatibility issue.
You can always use Array.prototype.filter instead (assuming that state.leagues will always be an Array:
function mapStateToProps(state, ownProps) {
const { leagues = [] } = state;
return {
league: leagues.filter(aLeague => aLeague.id === ownProps.match.params.id)[0]
}
}
Your reducer looks wrong to me. Could you try this:
export default (state = initialState, action = {}) => {
switch (action.type) {
case SET_USER_LEAGUES:
return {
...state,
leagues: action.leagues,
};
case ADD_USER_LEAGUE:
const leagues = [...state.leagues, { id: action.league.id, name: action.league.name, }];
return {
...state,
leagues,
};
default:
return state;
}
}
Some of your functions are manipulating the state and changing between returning a bool, an object and an array.
To resolve this, I now use an object instead of an array. (See reference 1 below for the reason). And I render the component after the store state is loaded so I can use mapStateToProps. I've put some code extracts below from working code. Hopefully also provides some tips on how to use Redux reducers for this use case. Please excuse any typos, I edited the code extracts inline here.
Store
import { createStore, applyMiddleware, combineReducers } from 'redux'
import thunkMiddleware from 'redux-thunk'
import * as reducers from './reducers'
const initialState = {
items: {
/* id(1): {id: PropTypes.number.isRequired, name: PropTypes.string}
id(n): {id: PropTypes.number.isRequired, name: PropTypes.string} */
}
}
var rootReducer = combineReducers(reducers)
window.store = createStore(rootReducer, initialState, applyMiddleware(thunkMiddleware))
Action
export const UPDATE_ITEM = 'UPDATE_ITEM'
export const updateItem = item => ({
type: UPDATE_ITEM,
item
})
Reducer
import { UPDATE_ITEM } from './actions'
export const items = (state = null, action) => {
switch (action.type) {
case UPDATE_ITEM:
let id = action.item.id
return Object.assign({}, state, {
[id]: Object.assign({}, state[id], action.item)
})
default:
return state
}
}
Add some objects to the store
import { updateItem } from './actions'
var item1 = {id: 1, name: 'Alice'}
window.store.dispatch(updateItem(item1))
var item2 = {id: 2, name: 'Bob'}
window.store.dispatch(updateItem(item2))
SomeComponent mapStateToProps
function mapStateToProps (state, ownProps) {
return {
item: state.items[ownProps.id]
}
}
Load Component like this after the store is populated.
ReactDOM.render(
<Provider store={window.store}>
<SomeComponent id={1} />
<SomeComponent id={2} />
</Provider>,
document.getElementById('root')
)
Just wanted to note that my main motivation to solve this was that I was mapping the entire object state (state.items) to props, but then render was called after an update to any array element which was horrible for performance.
References:
[1] https://stackoverflow.com/a/36031765/1550587

setState works but redux store update doesn't

First, I want to mention that the only thing I'm changing between two approaches is setState vs going through the Redux store. Not changing anything else i.e. components, etc.
If I use the setState approach, I can close my modal but if I go through the store, it doesn't close. Any idea why?
Here's my reducer:
import 'babel-polyfill';
import * as types from '../actions/actionTypes';
const initialState = {
modals: {
"modal1": { isDisplayed: true },
"modal2": { isDisplayed: false }
}
};
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_IS_DISPLAYED_MODAL :
return Object.assign({}, state, {
modals: action.modals
})
default: return state
}
}
}
Here are the two versions of my onClick action that is supposed to close the modal.
This is the setState version and it works:
displayModal(modalId, value)
{
let modals = this.props.modals;
modals[modalId].isDisplayed = value;
return setState({modals: modals});
}
And here's the version that goes through the redux store and it does NOT close my modal.
displayModal(modalId, value)
{
let modals = this.props.modals;
modals[modalId].isDisplayed = value;
return this.props.actions.displayModal(modals);
}
There's not much to the action but here it is:
export const displayModal = (modals) => {
return {
type: types.SET_IS_DISPLAYED_MODAL,
modals
};
}
Just so you see how it looks in my component, here it is:
render() {
return(
<div>
<div>Some info...</div>
{this.props.modals["modal1"].isDisplayed
? <Modal1 />
: null}
{this.props.modals["modal2"].isDisplayed
? <Modal2 />
: null}
</div>
);
}
BTW, I know that I'm hitting the action and the reducer. I also know that if I put a debugger in my mapStateToProps, I'm hitting it with updated state for my modals. So I know both the action and the reducer are doing what they're supposed to.
UPDATE:
I just tried something and this fixed the issue. I added last line to mapStateToProps and updated the section in my component:
function mapStateToProps(state) {
return {
modals: state.modals,
isModal1Displayed: state.modals["modal1"].isDisplayed // Just added this line
}
}
And changed the code in my component to:
render() {
return(
<div>
<div>Some info...</div>
{this.props.isModal1Displayed
? <Modal1 />
: null}
</div>
);
}
First of all, never mutate state in Redux reducer - it must be a pure function to work and detect changes correctly. Same rules apply to objects which you get with props.
You must change your code so you only dispatch an action to the store and reduce it to a new state.
First, dispatch an action:
displayModal(modalId, value)
{
this.props.actions.displayModal(modalId, value);
}
Your action will carry information which modal to hide or show:
export const displayModal = (modalId, value) => {
return {
type: types.SET_IS_DISPLAYED_MODAL,
modalId,
value
};
}
Then you can reduce it:
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_IS_DISPLAYED_MODAL :
return Object.assign({}, state,
{
modals: Object.assign({}, state.modals,
{
[action.modalId]: { isDisplayed: action.value }
})
})
default: return state
}
}
As you can see there is a lot of boilerplate here now. With ES6 and ES7 you can rewrite your reducer with the object spread operator or you can use Immutable.js library, which will help you with setting properties deep in the hierarchy.
Reducer with object spread operator looks like this:
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_IS_DISPLAYED_MODAL :
return {
...state,
modals: {
...state.modals,
[action.modalId]: { isDisplayed: action.value }
}
}
default: return state
}
}
You may ask yourself why your fix works. Let me explain.
You change a modal state when you dispatch an action to the Redux by mutating state in place modals[modalId].isDisplayed = value. After that the action is dispatched, reduced and mapToProps gets called again. There is probably reference check in connect higher order component and you have mutated the modal but not the modals object so it has the same reference = your component doesn't re-render. By adding a isModal1Displayed field you are actually disabling optimizations because there is boolean comparison, not a reference check and your component rerenders.
I hope it will help you with understanding Redux and it's principles.

Resources