React component not re-rendering with connect() - reactjs

My reducer:
export default function summary(state = {
"summary":null
}, action = null) {
switch (action.type) {
case GET_SUMMARY_REQUEST_SUCCESS:
const newState = Object.assign({}, state);
newState.summary = action.data;
return newState;
break;
case GET_SUMMARY_REQUEST_ERROR:
return Object.assign({}, state, {
sumary:null
});
break;
default: return state;
}
};
Root reducer:
import summary from './Summary.js'
const rootReducer = combineReducers({
summary
});
Inside my component, I am using connect to map state to props>
My components render function is something like this:
render() {
const summary = this.props.summaryContent || [];
return (
<div className={cx(styles['loading'])} >
<Loader width="4" />
{"Loading\u2026"}
</div>
);
}
function mapStateToProps(state, ownParams) {
return {
summaryContent: state.summary
};
}
export default connect(mapStateToProps)(Summary);
On componentWillMount, I am dispatching an action to update in the state in summary. Now, my componentWillReceiveProps is showing me the updated state in summary, but the component is not rendering.

I can see a few issues here:
You use summary both as a reducer key and as an index in your reducer state. That means summaryContent should map to state.summary.summary. (Though I'd suggest changing it to adopt a less confusing naming convention.)
Your render function doesn't utilise this.props.summaryContent in any useful way. It simply assigns it to a variable and then returns a loading output.
You've misspelled summary in the GET_SUMMARY_REQUEST_ERROR case.
I strongly recommend configuring ESLint which will alert you to issues like unused and misspelt variables.

Related

I can't figure out how to use visibilityFilters in react redux todo app

I have a todo app that does all 4 crud operations but I can't filter them based on their current status here's the app on codesandbox.
import { SET_VISIBILITY_FILTER } from "../actionTypes";
const initialState = {
filters: ["SHOW_ALL"]
};
const visibilityFilter = (state = initialState, { type, payload }) => {
switch (type) {
case SET_VISIBILITY_FILTER:
return { payload };
default:
return state;
}
};
export default visibilityFilter;
Any explanations will be appreciated.
I have also checked other react redux todo app github repos but most of them are old and it didn't look like they were writing in the best possible way, so I am trying to find a better way (and so far failing at it)
A few issues
filters is an array in the initial state, but you send single values there after in your action, and you also use it a single value when filtering with it.
you expect payload in your reducer but the data you dispatch does not wrap things in payload
dispatch({
type: SET_VISIBILITY_FILTER,
filter
});
in continuation to the above you should use the already defined action setFilter for setting a filter, which correctly wrap the data in a payload property.
fixing these 3 issues, you get https://codesandbox.io/s/problems-with-redux-forked-hv36h which is working as intended.
What you are doing is an anti-pattern when you mutate the redux state variable inside the component like this:
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case "SHOW_ALL":
return todos;
case "SHOW_COMPLETED":
return todos.filter((t) => t.completed);
case "SHOW_ACTIVE":
return todos.filter((t) => !t.completed);
default:
return todos;
}
};
Instead what you should do, listen to the SET_VISIBILITY_FILTER action on toDoReducer.js:
//import SET_VISIBILITY_FILTER action
case SET_VISIBILITY_FILTER:
let toDoClone = [...state.todos]
//if(filter = something)
toDoClone.filter(t => //your condition)
return {
...state,
todos: toDoClone
}

Redux MapDispatchToProps not functioning

So I'm new to Redux and I'm trying to get this base model working so I can quickly work on a small personal project, I set everything up and have no errors but I'm trying to test and my function doesn't work so I was hoping someone could point out what I've missed.
I've followed multiple different tutorials and each has a different approach so that has me lost a bit so I apologize for that.
My store.js looks like so
import rootReducer from "./reducers";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
I've used a combineReducers in my index.js in reducers folder and the auth: points to the authReducer.js file, which is this
const INIT_STATE = {
email: "",
password: "",
isLoggedIn: "false"
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
isLoggedIn: action.value
};
default:
return state;
}
};
Now What I'm aiming for is to have a button that changes that "IsLoggedIn" initial state to a true string instead of a false, I've went into my actions folder and made an authActions.js which looks like so
import { IS_LOGGED_IN_CHANGE } from "../actions/types";
import store from "../store";
export const isLoggedInChange = value => {
return dispatch => {
dispatch({
type: IS_LOGGED_IN_CHANGE,
value
});
};
};
And Finally I want to show you my component page which is showing all this, It's looking like so
import { connect } from "react-redux";
import styles from "./Landing.module.css";
import { isLoggedInChange } from "../../actions/authActions";
class Landing extends Component {
makeTrue = () => {
isLoggedInChange("true");
};
constructor(props) {
super(props);
this.state = {
email: "",
password: ""
};
}
render() {
return (
<div className={styles.background}>
<button onClick={this.makeTrue}>MAKE TRUE</button>
{this.props.isLoggedIn}
</div>
);
}
}
const mapStateToProps = state => ({
isLoggedIn: state.auth.isLoggedIn
});
const mapDispatchToProps = dispatch => ({
isLoggedInChange: value => dispatch(isLoggedInChange(value))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Landing);
Can you tell if I dropped anything making this? why is the button not changing the store state? TIA
Two problems here. You're calling your action creator directly not props.isLoggedInChange
makeTrue = () => {
this.props.isLoggedInChange("true");
};
And you need to spread the old state inside your action
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
...state,
isLoggedIn: action.value
};
Isn't the point of my mapDispatchToProps to be able to use the function right away as I was doing
Yes, the problem is mapDispatchToProps inject a function (or multiple functions) wrapped in dispatch into your props.
import { actionCreator } from './actions
const mapDispatchToProps = dispatch =>({
actionCreator : () => dispatch(actionCreator)
})
Now you have two actionCreator, one globally available in the scope (which is your action creator) and props.actionCreator which is the original action creator wrapped in dispatch. So when you call actionCreator() from inside your component it won't throw any errors (cause there is a function named actionCreator in the scope, but you will be calling the wrong function, the right one is located at props.actionCreator.
Why do I need to spread the state?
A reducer is a pure function which receives a state and action and returns the new state. When you just return
return {
isLoggedIn : true
}
You're actually overwriting the original state (which contains other properties), so first you need to spread the original state to maintain it's structural integrity and them overwrite the properties you want
return{
...state,
isLoggedIn : !state.isLoggedIn
}
Redux state is immutable so you need to return a brand new instance of state, change your reducer state to the below.
export default (state = INIT_STATE, action) => {
switch (action.type) {
case IS_LOGGED_IN_CHANGE:
console.log(action);
return Object.assign({}, state, {
isLoggedIn: action.value
});
default:
return state;
}
};
The key difference there being the
return Object.assign({}, state, {
isLoggedIn: action.value
});
Object.assign in the way I'm using it here combines the state object into a brand new object. Check out immutability within redux reducers and I'd recommend adding redux-immutable-state-invariant as a dev package, it can detect when you're directly modifying state and help point out errors like this
Return the state with the new value for isLoggedIn. Use the reducer like this:
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
...state,
isLoggedIn: action.value
};

Component works even with state mutation redux

I know it can sound weird, but I would like to create an incoherent state in my Redux application as an example for students, and generate a bug.
So I made a state mutation on purpose but the component render correctly and everything is fine !
My example is just a simple counter app, you click on the button and the value displayed will be incremented.
My action :
export function incrementCounter() {
return {
type: INCREMENT_COUNTER,
};
}
My reducer with state mutation :
export default function ressourceReducer(state = initialState, action) {
switch (action.type) {
case INCREMENT_COUNTER:
// here it is ( redux invariant even throw and error if activated )
state.counter++;
return {
counter: state.counter
}
}
return state;
}
My component :
const mapStateToProps = state => ({
counter: state.ressource.counter,
});
Root reducer :
const rootReducer = combineReducers({
ressource: ressourceReducer
});
Problem, the props this.props.counter is coherent, no rendering problem in the class...
Can you help to make a state mutation that will have a real impact ?

Setting redux state doesn't work after a hard refresh

I am setting my redux state through a value I have in localStorage. This works fine when I navigate into my page. However, when I do a hard refresh the state is never set, despite the value in localStorage being passed down.
This is what my code looks like:
class SomeComponent {
componentWillMount() {
if (typeof localStorage !== 'undefined') {
console.log('I get to here...', localStorage.getItem('someValue')) // this comes in as expected always
this.props.setMyReduxState(localStorage.getItem('someValue'))
}
}
render () {
// will have the value of the localStorage item someValue when navigated into the page
// will be an empty string if I do a hard refresh
console.log('this.props.myReduxState', this.props.myReduxState)
return (
<div>
Stuff...
</div>
)
}
}
const mapStateToProps = (state) => {
return {
myReduxState: state.something.myReduxState || ''
}
}
const mapDispatchToProps = (dispatch) => {
return {
setMyReduxState (someValue) {
dispatch(setMyReduxState(someValue))
}
}
}
Any ideas?
Edit: just a small addition to simplify the problem: I also tried it sending a string directly to setMyReduxState function, without the localStorage, the state still isn't being set. So basically something like this:
componentWillMount() {
this.props.setMyReduxState('some string!')
}
From my understanding every time the redux state is set, the component should re-draw, which isn't happening when there is a hard refresh. Are there any reasons for this or something being done incorrectly?
Edit2: Including my action creator and reducer in case needed:
const SET_MY_REDUX_STRING = 'admin/users/SET_MY_REDUX_STRING'
const defaultState = {
myReduxState: ''
}
export function setMyReduxState (value) {
return {
type: SET_MY_REDUX_STRING,
myReduxState: value
}
}
export default function reducer (state = defaultState, action = {}) {
switch (action.type) {
case SET_MY_REDUX_STRING:
return Object.assign({}, state, { myReduxState: action.myReduxState })
default:
return state
}
}
Some of the checklist you need to follow while using redux -
Are you dispatching an action creator that returns an object with 'type' and data? Here 'type' is mandatory.
Does your reducer return the state with the updated data that it received?
Make sure you do not mutate the state in reducer. Always use {...state, someKey: someValue}

React components lifecycle, state and redux

I would like to use redux to store the state of my whole react application, however I am stuck with a particular case:
what to do with redux when the component needs a local state, modified by lifecycle methods like componentDidUpdate or componentDidMount ?
Example of a react component to contain "cards" arranged by isotope layout library:
componentDidMount() {
let container = ReactDOM.findDOMNode(this);
if (! this.state.isotope) {
this.setState({ isotope: new Isotope(container, {itemSelector: '.grid-item', layoutMode: 'masonry'})});
}
}
componentDidUpdate(new_props, new_state) {
if (new_state.items_list != this.state.items_list) {
if (this.state.isotope) {
this.state.isotope.reloadItems();
this.state.isotope.layout();
this.state.isotope.arrange();
}
}
}
Is there a way to remove the local state in this component and to use redux instead ? I can't see how to do it
Put your items_list in your redux state. Your reducer might look like this:
const initialState = {
items: []
};
export function myReducer(state = initialState, action) {
switch (action.type) {
case 'SET_ITEMS':
return Object.assign({}, state, {
items: action.items
});
}
return state;
}
Or for something a little more complex:
const initialState = {
items: []
};
export function myReducer(state = initialState, action) {
switch (action.type) {
case 'ADD_ITEM':
return Object.assign({}, state, {
items: [ ...state.items, action.item ]
});
case 'REMOVE_ITEM':
return Object.assign({}, state, {
items: [
...state.items.slice(0, action.index),
...state.items.slice(action.index + 1)
]
});
}
return state;
}
Once you've configured your store and Provider (see the Redux docs), set up your "smart component" like so:
function mapStateToProps(state) {
return {
items: state.items
}
}
function mapDispatchToProps(dispatch) {
const actions = bindActionCreators(actionCreators, dispatch);
return {
addItem: actions.addItem,
removeItem: actions.removeItem
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Root);
Now your items and actions are props to your Root component. If your items live in a lower order component, simply pass them down the tree as props.
I hope that gives you the general idea. With Redux what you'll find that your React components will use state a lot less and props a lot more.
One more thing...
This might be a minor matter, but I urge you NOT to store your isotope object on the component state. (Regardless of whether or not you use Redux.) The isotope object isn't really a piece of state, it's your view. In React, a component updates in response to a change in state. But your componentDidUpdate does the reverse: it changes the state in response to a component update.
As an alternative, simply store it on the object itself. i.e.
componentDidMount() {
const container = ReactDOM.findDOMNode(this);
this.isotope = new Isotope(container, {
itemSelector: '.grid-item',
layoutMode: 'masonry'
});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.items !== this.props.items) {
this.isotope.reloadItems();
this.isotope.layout();
this.isotope.arrange();
}
}
(Whilst normally I would recommend against against using these sort of instance variables in React, DOM manipulation libraries like Isotope are a worthy exception.)

Resources