React not re-rendering after redux update - javascript

import {UPDATE_USER} from '../actions/index';
const DEFAULT_STATE = {
createdAt:"",
name:"",
email:"",
password:"",
skill:"",
goal:"",
step1:"",
step2:"",
step3:"",
step4:"",
step5:"",
posts:[],
completed:0
}
export default function(state = DEFAULT_STATE, action) {
if (action.error) {
action.type = 'HANDLE_ERROR'; // change the type
}
switch (action.type) {
case UPDATE_USER:
console.log(action.payload)
return {
createdAt:action.payload.createdAt,
name:action.payload.name,
email:action.payload.email,
password:action.payload.password,
goal:action.payload.goal,
skill:action.payload.skill,
step1:action.payload.step1,
step2:action.payload.step2,
step3:action.payload.step3,
step4:action.payload.step4,
step5:action.payload.step5,
completed:action.payload.completed,
}
React is not detecting a prop change. I'm pretty sure the answer lies with me mutating reducer arguments(from researching the question). Does anyone know how I would restructure to not mutate?
edit -my react class snippet is below. My map dispatch to props is at bottom. A user logs in to app gets redirected to this page where I set local state of page from redux in componentwillMount(). Then I have a function that calls api and updates redux. React is supposed to see this change because props have changed? Or do I have to set state manually within a componentWillRecieveProps()?
class YourPage extends React.Component {
constructor(props) {
super(props);
this.state = {
post:"",
date:"",
email:"",
completed:0,
posted:true,
timeSincePost:"",
lastPost:""
}
this.handleInputChange = this.handleInputChange.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
const mapStateToProps = (state) =>({
name:state.user.name,
email:state.user.email,
completed:state.user.completed,
})
const mapDispatchToProps = (dispatch) => ({
callApi: (value, state) => {
var obj = {
date:moment.tz(moment.tz.guess()).format(),
post:state.post,
email:state.email,
completed:(parseFloat(state.completed) + .75),
}
API.addPost(obj)
.then(function(res){
dispatch(updateUser(res.data))
})
}
})
export default connect(mapStateToProps,mapDispatchToProps)(YourPage);

Use Object.assign
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
import { UPDATE_USER } from '../actions/index';
const DEFAULT_STATE = {
createdAt: "",
name: "",
email: "",
password: "",
skill: "",
goal: "",
step1: "",
step2: "",
step3: "",
step4: "",
step5: "",
posts: [],
completed: 0
}
export default function (state = DEFAULT_STATE, action) {
if (action.error) {
action.type = 'HANDLE_ERROR'; // change the type
}
switch (action.type) {
case UPDATE_USER:
console.log(action.payload)
return Object.assign({}, state, {
createdAt: action.payload.createdAt,
name: action.payload.name,
email: action.payload.email,
password: action.payload.password,
goal: action.payload.goal,
skill: action.payload.skill,
step1: action.payload.step1,
step2: action.payload.step2,
step3: action.payload.step3,
step4: action.payload.step4,
step5: action.payload.step5,
completed: action.payload.completed,
});

Related

componentDidUpdate doesn't update state with Redux

The goal of this app is to let met add an item in a Flatlist present in the List Item screen, using React-Redux and React Navigation. Basically I type name and category in the Create Item screen and send it in the form of an array to the List Item screen with React Navigation, and once I'm in the List Item screen I use componentDidMount to dispatch the action and update the state in the class compoment, the problem is that nothing shows up, even using the console.log it just gives me back the empty array present in the Redux Reducers screen.
CREATE ITEM SCREEN
export class item extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
category: '',
};
}
submitItem = (name, category) => {
this.props.navigation.navigate("ListItem", {
itemList: {
name: name,
category: category,
}});
};
render() {
const { name, category } = this.state;
return (
<Container>
<Header>
<Left>
<Button onPress={() =>
this.submitItem(
this.state.name,
this.state.category,
)
}>
<Text>Sumbit</Text>
</Button>
ITEM LIST SCREEN
class ListItem extends Component {
constructor(props) {
super(props);
this.state = {
itemList:[],
};
}
componentDidMount (props, state) {
if (this.props.route.params?.itemList) {
() => this.props.dispatch({type:'ADD_ITEM'});
}
return null;
}
REDUX REDUCER
const initialState = {
currentUser: null,
itemList: [],
};
export const user = (state = initialState, action) => {
switch (action.type){
case USER_STATE_CHANGE:
return {
...state,
currentUser: action.currentUser,
};
case 'ADD_ITEM':
return{
itemList: state.itemList,
}
default:
return state
}
};
I think when you are dispatching an action you are not adding the action.payload to the state.itemList.What I meant
const initialState = {
currentUser: null,
itemList: [],
};
export const user = (state = initialState, action) => {
switch (action.type){
case USER_STATE_CHANGE:
return {
...state,
currentUser: action.currentUser,
};
case 'ADD_ITEM':
return{
...state,
itemList: addItemToList(state.itemList,action.payload), //a util function to add items to the list.
// And action.payload is the value ,which passed when you are dispatching the action 'ADD_ITEM'
}
default:
return state
}
};
And when you are dispatching the action it should be
componentDidMount (props, state) {
if (this.props.route.params?.itemList) {
() => this.props.dispatch({type:'ADD_ITEM',payload:this.props.route.params.itemList});
// passing the itemList to as the payload of the action.
}
return null;
}
I guess this modification should suffice. More on React-Redux here

React component not getting redux's initial state set by reducer

While creating the reducer, i have set the initial state but while loading the component for the first time, i am getting an empty state.
reducer file -
const initialValues = { geo: { latitude: 0, longitude: 0 } }
const emp = (state = initialValues, action) => {
switch (action.type) {
case 'bike': {
return { ...state, data: action.payload }
}
case 'car': {
return { ...state, headers: action.payload }
}
default:
return state
}
}
component file-
export default function Emp() {
const emp = useSelector(state => state)
console.log('emp state', emp)
}
index.js
const store = createStore(reducer,
{},
compose(
applyMiddleware(
loggingMiddleware,
),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
)
I got the solution. Actually i was not using combine reducer before thats why reducer's state was not getting initialized.

React Redux reducer not Updating State

So I have a reducer which doesn't seem to be updating the state at all whenever I called the 'LOGIN' action. It could be a problem with my React Redux code. It's either my component is not getting re rendered whenever the store's state changes or the reducer is not changing the stores state at all.
Reducer ------
const initialState = {
messages: [],
loginDetails: {
email: '',
password: ''
},
activeUsers: [],
loginActive: false
}
const messageReducer = (state = initialState, action) => {
switch(action.type) {
case 'ADD_MESSAGE':
if(state.messages.length < 50) {
let newStateMessages = [...state.messages]
newStateMessages.unshift(action.payload);
console.log(newStateMessages);
return {...state, messages: newStateMessages};
} else {
let newStateMessages = [...state.messages]
newStateMessages.pop();
newStateMessages.unshift(action.payload);
return {...state, newStateMessages};
}
case 'LOGIN':
console.log('LOGIN');
console.log(action);
const newLoginDetails = {
email: action.payload.email,
password: action.payload.password
};
console.log({...state, loginDetails: newLoginDetails});
return {...state, loginDetails: newLoginDetails};
case 'UPDATE_USERS':
const newActiveUsers = action.payload;
return {...state, activeUsers: newActiveUsers};
case 'LOGIN_ACTIVE':
return {...state, loginActive: true};
case 'LOGIN_EXIT':
return {...state, loginActive: false};
default:
return state;
}
}
export const store = createStore(messageReducer);
React Redux connect -----
const mapStateToProps = state => {
return { ...state }
}
export default connect(mapStateToProps)(Home);
This mapStateToProps returns...
{
activeUsers: []
dispatch: ƒ dispatch(action)
loginActive: true
loginDetails: {email: "", password: ""}
messages: []
__proto__: Object
}
when it should return...
{
activeUsers: []
loginActive: true
loginDetails: {email: "example#gmail.com", password:
"password"}
messages: []
__proto__: Object
}
I have tested for sure that the dispatch to the reducer is getting called, and the payload is correct. However, the reducer is failing to update the state with the LOGIN action type.
Can you try this:
const mapStateToProps = ({activeUsers,loginActive,loginDetails,messages}) => ({
activeUsers,
loginActive,
loginDetails,
messages
})

React-redux component update state attributes after async saga calls from reducer

I am trying to develop a simple image list component with react-redux stack.
This are my actions, reducers, saga and component root definitions -
// Actions
export const getImageListData = () => ({
type: IMAGE_LIST_GET_DATA
});
export const getImageListDataSuccess = (data) => {
console.log("ACTION::SUCCESS", data);
return ({
type: IMAGE_LIST_GET_DATA_SUCCESS,
payload: data
});
};
// Reducers
export default (state = INIT_STATE, action) => {
console.log("REDUCER::", state, action);
switch (action.type) {
case IMAGE_LIST_GET_DATA: return { ...state, isLoading: true };
case IMAGE_LIST_GET_DATA_SUCCESS: return { ...state, items: action.payload.data, isLoading: false };
default: return { ...state };
}
}
// Sagas
import imagesData from "Data/images.json";
function* loadImages() {
try {
const response = yield call(loadImagesAsync);
console.log("SAGA:: ", response);
yield put(getImageListDataSuccess(response));
} catch (error) {
console.log(error);
}
}
const loadImagesAsync = async () => {
const contacts = imagesData;
return await new Promise((success, fail) => {
setTimeout(() => {
success(contacts);
}, 2000);
}).then(response => response).catch(error => error);
};
export function* watchGetImages() {
console.log("ACTION::INIT", IMAGE_LIST_GET_DATA);
yield takeEvery(IMAGE_LIST_GET_DATA, loadImages);
}
export default function* rootSaga() {
yield all([
fork(watchGetImages)
]);
}
Now in the component I am calling - getImageListData action
and with this mapStateToProps and connect provider -
const mapStateToProps = ({ ImageList }) => {
const {items} = ImageList;
return {items};
};
export default connect(
mapStateToProps,
{
getImageListData
}
)(ImageListLayout);
I am mapping the image list response to the component props.
My component definition is as follows -
class ImageListLayout extends Component {
constructor(props) {
super(props);
this.state = {
displayMode: "imagelist",
pageSizes: [8, 12, 24],
selectedPageSize: 8,
categories: [
{label:'Cakes',value:'Cakes',key:0},
{label:'Cupcakes',value:'Cupcakes',key:1},
{label:'Desserts',value:'Desserts',key:2},
],
orderOptions:[
{column: "title",label: "Product Name"},
{column: "category",label: "Category"},
{column: "status",label: "Status"}
],
selectedOrderOption: {column: "title",label: "Product Name"},
dropdownSplitOpen: false,
modalOpen: false,
currentPage: 1,
items: [],
totalItemCount: 0,
totalPage: 1,
search: "",
selectedItems: [],
lastChecked: null,
displayOptionsIsOpen: false,
isLoading:false
};
}
componentDidMount() {
this.dataListRender();
}
dataListRender() {
this.props.getImageListData();
}
render() {
...
}
}
Now in my component I am able to correctly access this.props.items obtained from reducer with action IMAGE_LIST_GET_DATA_SUCCESS, but I also want to update some of the state variables like isLoading, currentPage, totalItemCount, totalPage and since these belong to this component itself and not their parents I do not want to map them to the props but want to update the state of the component and trigger a re-render.
Can someone please tell me what should I be doing to fix this or am i missing anything else here ?
In your current setup I see no reason for you to have isLoading, etc. in the state. You should just map it to props:
const mapStateToProps = ({ ImageList, isLoading }) => {
const {items} = ImageList;
return {items, isLoading};
};
I don't get why you say "and since these belong to this component itself and not their parents I do not want to map them to the props but want to update the state of the component and trigger a re-render." what do parents have to do with anything here?

mapStateToProps doesn't map to props for some reason

I'm fairly new to React/Redux. The redux Chrome devtools tell me that I'm successfully creating and modifying state. However, while the console log 'dah state' runs and satisfactorily tells me that my state has the correct information in it, I'm not mapping to props at that point.
I'm not entirely sure if my reducer is made correctly, but I suspect that it is because I'm creating new state, even though it doesn't map to props.
I'm also fairly sure that mapStateToProps, while it runs, is not triggering a rerender
Here is my relevant container
import React, { Component, PropTypes } from 'react';
import TopicsGrid from '../components/TopicsGrid.jsx';
import { connect } from 'react-redux';
import { fetchTopics } from '../actions/topics';
import Main from '../components/Main.jsx';
class AboutContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
topics: [{
title: '',
description: '',
link: '',
src: '',
message: '',
selected: false,
_id: ''
}]
}
}
onChange = (action) => {
this.props.dispatch(action);
}
componentDidMount() {
fetchTopics();
}
componentWillReceiveProps(nextProps) {
console.log('nextProps', nextProps)
this.setState({
topics: nextProps.topics
})
}
render() {
console.log('PROPS', this.props)
return (
<div className="container">
<TopicsGrid
topics={this.state.topics}
onChange={this.onChange}/>
</div>
);
}
};
AboutContainer.propTypes = {
dispatch: PropTypes.func.isRequired,
topics: PropTypes.array.isRequired
};
AboutContainer.defaultProps = {
state: {
topics: [{
title: '',
description: '',
link: '',
src: '',
message: '',
selected: false,
_id: ''
}]
}
};
const mapDispatchToProps = (dispatch) => {
return {
dispatch: dispatch
}
}
const mapStateToProps = (state) => {
console.log('dah state', state)
return Object.assign({}, state, {
topics: state.topics.topics
})
}
export default connect(mapStateToProps, mapDispatchToProps)(AboutContainer);
Here is my reducer
import * as types from '../constants/action-types';
import * as SectionNames from '../constants/section-names';
const initialState = {
topics: []
}
export default function about(state = initialState, action) {
if (action.section !== SectionNames.TOPICS) {
return state;
}
let mods = {};
switch (action.type) {
case types.FETCH_TOPICS_SUCCESS:
mods = {
topics: action.topics
}
// return Object.assign({}, state, {
// topics: action.topics
// });
break;
case types.FETCH_TOPICS_ERROR:
mods = {
topics: action.topics
}
// return Object.assign({}, state, {
// topics: action.topics
// });
break;
case types.TOPIC_SELECTED:
console.log('selected')
//topic can be selected or unselected
//only one topic can be selected at once.
mods = {
topics: action.topics
}
mods.topics[action.index].selected = true;
return Object.assign({}, state, mods);
break;
case types.TOPIC_UNSELECTED:
//topic can be selected or unselected
//only one topic can be selected at once.
mods = {
topics: action.topics
}
mods.topics[action.index].selected = false
break;
default:
return state;
}
return Object.assign({}, state, mods);
}
Since you're using mapStateToProps you can use props directly instead of passing them into the component state.
render() {
const { topics } = this.props
return (
<div className="container">
<TopicsGrid
topics={topics}
onChange={this.onChange}
/>
</div>
);
}
#connect(state => ({
data: state.module.data
}), {actoionCreators});
use this at the top of your class it will decorate your class and map states to props
in your module you have to switch on action.type, in each case you should return an object with your custom changes in state for example in LOAD_SUCCESS you have to return an object like this :
return {
...state,
loading:false,
loaded:true,
data:action.data
}
so redux knows that when loadSuccess dispatched the state is gonna change with loading to false and ....
notice that reduce should return state when an unknown action dispathed so in your default case you should return state

Categories

Resources