Redux state.set resulting in undefined - reactjs

Can someone tell me why state.set is resulting in undefined.
I have a reducer like so:
const initialState = fromJS({
description: 'default_description',
});
function helpReducer(state = initialState, action) {
switch (action.type) {
case DEFAULT_ACTION:
return state;
case CHANGE_DESCRIPTION:
console.log('Change Description reducer action.data: '+action.data); //results in string say foo
console.log('state: '+state); //results in valid state on first call
return state
.set('description':action.data);
default:
return state;
}
}
The first time I call the action CHANGE_DESCRIPTION, state = default_description.
After calling state.set the state.description = undefined.
I have used state.set before, but am unsure why it is failing now.
The action is being called by a child component like so:
Parent:
export class FOO extends React.PureComponent {
constructor(props){
super(props);
}
render() {
return (
<div>
<FOOForm onChange={this.props.changeDescription} />
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return {
dispatch,
changeDescription: (e) => dispatch(changeDescription(e.target.value))
};
}
Foo Form for sake of demonstration:
function fooForm(props){
return <FormControl onChange={props.onChange} componentClass="textarea" />;
}

I'm not sure of the context in which you implement your reducer, but I believe the syntax in immutable JS should make your set look like this:
return state.set('description', action.data)
Note that you need to pass in the property and values as arguments and not as a key value pair.

Related

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.

React-Redux component state

tl;dr I'm trying to save initial state inside a sub-container component but it gets updated to the new values every time the Redux store gets updated. I probably missed something in configuration and I need help to sort things out.
index.tsx
const store = createStore(reducers, loadedState, enhancer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById(containerId)
)
AppProps.ts
function mapStateToProps(state: StoreState){
return{
... // app props
userDetails: state.userDetails // array of objects fetched by id
}
}
function mapDispatchToProps(dispatch: ReactRedux.Dispatch<actions.AppActions>){
return{
... // app methods
detailsUpdate: (props: UpdateProps) => (dispatch(actions.detailsUpdate(props: UpdateProps)))
}
}
ReactRedux.connect(mapStateToProps, mapDispatchToProps)(App)
Actions.ts
function detailsUpdate(props: UpdateProps): UpdateUserDetails {
return {
type: UPDATE_USER_DETAILS,
props: { ... }
}
}
Reducers.ts
export function reducers(state: StoreState, action: actions.AppActions): StoreState {
let newState: StoreState = {...state};
switch (action.type) {
case actions.UPDATE_USER_DETAILS:
... // reducer code
break;
case actions.UPDATE_PRODUCTS:
... // reducer code
break;
return newState;
}
App.tsx
const App = (allProps: IAppProps, state: StoreState) => {
<UserDetailsContainer
id="generalDetails"
userDetails={allProps.userDetails.byId}
detailsUpdate={allProps.detailsUpdate}
/>
}
UserDetailsContainer.tsx 1
class UserDetailsContainer extends
React.Component<UserDetailsContainerProps, UserDetailsState> {
constructor(props: UserDetailsContainerProps) {
super(props);
this.state = {
userData: props.userDetails[props.id]
}
}
render(){
<input type="text"
value={this.props.userDetails[this.props.id].address}
/>
}
}
detailsUpdate triggers UPDATE_USER_DETAILS action and reducer updates store state with new value. Now, UserDetailsContainer receives updated version of userDetails from the store which is fine for displaying new value in <input type="text"> element.
However, this.state gets updated with new value which I expect shouldn't happen as constructor should be called only once (and is). This prevents me from referencing initial value in case I need it for reset or other.
Please ask for any missing information and/ or clarification and ignore any typos as the app works without errors otherwise.
1 Component usually renders another presentational component for <input type="text">which I omitted here for brevity.
Thanks!
The following only makes a shallow copy of state object.
let newState: StoreState = {...state};
So if you assign
this.state = {
userData: props.userDetails[props.id]
}
And then modify the array inside your reducer, you will also modify the component's state since it references the same object.
This also goes against the concept of redux - reducer should not mutate it's arguments.
Note that this exact mistake is highlighted in the redux docs: https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#common-mistake-2-only-making-a-shallow-copy-of-one-level

react props not updating with redux store

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})
}

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.

My Redux state has changed, why doesn't React trigger a re-render?

I am trying to design a notification component where notifications will appear on certain occasions (like connections problems, successful modifications, etc.).
I need the notifications to disappear after a couple of seconds, so I am triggering a state change to delete the notification from Redux state from setTimeout inside the notification's componentDidMount.
I can see that the state does change, but React-Redux is not re-rendering the parent component so the notification still appears on the DOM.
Here is my Redux reducer:
const initialState = {
notifications: []
}
export default function (state = initialState, action) {
switch(action.type) {
case CLEAR_SINGLE_NOTIFICATION:
return Object.assign ({}, state, {
notifications: deleteSingleNotification(state.notifications, action.payload)
})
case CLEAR_ALL_NOTIFICATIONS:
return Object.assign ({}, state, {
notifications: []
})
default:
return state
}
}
function deleteSingleNotification (notifications, notificationId) {
notifications.some (function (notification, index) {
return (notifications [index] ['id'] === notificationId) ?
!!(notifications.splice(index, 1)) :
false;
})
return notifications;
}
and my React components (Main and Notification):
/* MAIN.JS */
class Main extends Component {
renderDeletedVideoNotifications() {
console.log('rendering notifications');
const clearNotification = this.props.clearNotification;
return this.props.notifications.map((notification)=> {
return <Notification
key={notification.id}
message={notification.message}
style={notification.style}
clearNotification={clearNotification}
notificationId={notification.id}
/>
});
}
render() {
console.log('rerendering');
return (
<div className="_main">
<Navbar location={this.props.location} logStatus={this.props.logStatus}
logOut={this.logout.bind(this)}/>
<div className="_separator"></div>
{this.props.children}
<BottomStack>
{this.renderDeletedVideoNotifications()}
</BottomStack>
</div>
);
}
}
function mapStateToProps(state) {
return {logStatus: state.logStatus, notifications: state.notifications.notifications};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({checkLogStatus, logOut, clearNotification, clearAllNotifications}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Main);
/* NOTIFICATION.JS */
export default class Notification extends Component{
constructor(props){
super(props);
this.state = {show: true}
}
componentWillReceiveProps(nextProps){
if(nextProps.message){
this.setState({show: true});
}
}
clearNotification(notificationId){
this.props.clearNotifications(notificationId);
}
componentDidMount(){
console.log('notification mount');
setTimeout(()=>{
console.log('timed out');
this.props.clearNotification(this.props.notificationId);
}, 1000);
}
closeNotification(){
this.props.clearNotification(this.props.notificationId);
this.setState({show: false});
}
render(){
const notificationStyles = () =>{
if (this.props.style === "error"){
return {backgroundColor: 'rgba(152, 5, 19, 0.8)'}
}
return {backgroundColor: 'rgba(8, 130, 101, 0.8)'}
};
if(!this.state.show){
return null;
}
return (
<div className="notification" style={notificationStyles()}>
<div className="notificationCloseButton" onClick={this.closeNotification.bind(this)}>
<i className="material-icons">close</i>
</div>
{this.props.message}
</div>
)
}
};
You've got everything hooked up correctly, but you're missing one key concept for Redux:
With Redux, you never mutate any part of state.
From the Redux guide:
Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
In deleteSingleNotification, you're using .splice to cut the old notification out of your array. Instead, you need to return a brand new array with the unwanted notification missing from it. The easiest way to do this is with the .filter function:
function deleteSingleNotification(notifications, notificationId){
return notifications.filter (notification => {
return notification.id !== notificationId
}
}
Here is a JSBin with your working notification system!
So here is why this works: React-Redux's job is to update your components whenever a specific part of your Redux store is changed. It uses a === test on every part of the state tree to know if anything changed.
When you go and change the state with something like .splice, it checks and thinks nothing is different.
Here's an example to demonstrate the problem:
var array = [ 'a', 'b', 'c' ]
var oldArray = array
array.splice (1, 1) // cut out 'b'
oldArray === array // => true! Both arrays were changed by using .splice,
// so React-Redux *doesn't* update anything
Instead, React-Redux needs us to do this:
var array = [ 'a', 'b', 'c' ]
var oldArray = array
array = array.filter (item, index => index !== 1) // new array without 'b'
oldArray === array // false. That part of your state has changed, so your
// componenet is re-rendered
Redux uses this approach for performance reasons. It takes a really long time to loop through a big state tree looking to see if everything is the same. When you keep your tree immutable, only a === test is needed and the process gets much easier.

Resources