Redux connected React component not updating on state change - javascript

Whenever my 'COLLEGE_ADDED' action is dispatched I can see the state changes in the reducer. However the update related lifecycle methods on the CollegeSearchList component and it's children aren't being called. These components aren't re-rendering presumably because of this.
I have read the docs about not mutating state and I don't think I am. Complete code can be found here https://github.com/tlatkinson/react-search-widget.
components/search/college/CollegeSearchList.js
class CollegeSearchList extends Component {
componentWillUpdate (nextProps, nextState) {
console.log(nextProps.searchItems);
console.log(nextState);
return true;
}
render () {
return (
<SearchList searchItems={this.props.searchItems} SearchListItem={CollegeSearchListItem} />
)
}
}
const mapStateToProps = (state, {id}) => {
return {
searchItems: getSearchResultsById(state.searchState, id),
SearchListItem: CollegeSearchListItem,
}
};
CollegeSearchList = connect(
mapStateToProps
)(CollegeSearchList);
reducers/search.js
const searchReducer = (searchState = [], action) => {
switch(action.type) {
case 'COLLEGE_SEARCH':
return mergeData(searchState, action, 'college', 'phrase');
case 'COLLEGE_SEARCH_SUCCESS':
return mergeData(searchState, action, 'college', 'searchResults');
case 'COLLEGE_ADDED':
return updateCollegeAdded(searchState, action.collegeId, true);
case 'COLLEGE_REMOVED':
return updateCollegeAdded(searchState, action.collegeId, false);
default:
return searchState;
}
};
export default searchReducer
const updateCollegeAdded = (searchState, collegeId, added) => {
const newState = {...searchState};
for (let id of Object.keys(newState)) {
const searchComponent = searchState[id];
if(searchComponent.searchType === 'college') {
searchComponent.searchResults.forEach(searchResult => {
if(searchResult.id === collegeId) {
searchResult.added = added;
}
});
}
}
return newState;
};
const mergeData = (data, action, searchType, propertyModified) => {
return {
...data,
[action.id]: {
searchType,
...data[action.id],
[propertyModified]: action[propertyModified],
}
};
};
actions/index.js
export const addRemoveCollege = (collegeId, collegeName, addToList) => (dispatch) => {
if (addToList) {
api.addToCollegeList(collegeId)
.then(() => {
dispatch({
type: 'COLLEGE_ADDED',
collegeId,
collegeName,
});
})
} else {
api.removeFromCollegeList(collegeId)
.then(() => {
dispatch({
type: 'COLLEGE_REMOVED',
collegeId,
collegeName,
});
})
}
};

Related

Why are my Action creators not dispatching data to redux store in Next js?

I just started using NextJS for the first time and I'm trying to use it with Redux.
I have been able to set up the redux and set up the getServerSideProps function in index.js.
The problem however is that the store returns an empty state even after I have fetched the data. When I console.log the fetched data from inside the getServerSideProps function, I see it in my terminal, so I'm left to suspect that the problem is that my action creator is not dispatching the data to the store. I even tried to handle the data fetching and dispatch from inside the action creator using redux-thunk, but that also did not work. Please I really need some help with this.
My action creator:
export const initializeArticles = (articles) => {
return {
type: 'INITARTICLES',
data: articles
}
}
My reducer:
const articlesReducer = (state = initialState, action) => {
switch (action.type) {
case 'INITARTICLES':
return action.data
case 'ADDARTICLE':
return state.concat(action.data)
case 'INCREMENTARTICLELIKES':
return state.map((article) => {
if(article.id === action.id) {
return {
...article,
likes: article.likes + 1
}
} else {
return article
}
})
case 'DELETEARTICLE':
return state.filter(article => article.id !== action.id)
case 'ADDCOMMENT':
return state.map((article) => {
if (article.id === action.id) {
return {
...article,
comments: article.comments.concat(action.data)
}
} else {
return article
}
})
case 'APPROVECOMMENT':
return state.map((article) => {
if (article.id === action.articleId) {
return article.commentIsApproved = true
} else {
return article.commentIsApproved = false
}
})
default:
return state
}
}
export default articlesReducer
My store setup:
let store;
const initstore = (initialState) => {
return createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(thunk))
)
}
export const initializeStore = (preloadedState) => {
let _store = store ?? initstore(preloadedState)
if (preloadedState && store) {
_store = initstore({
...store.getState(),
...preloadedState
})
store = undefined
}
if(typeof window === 'undefined') return _store
if(!store) store = _store
return _store
}
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState])
return store
}
My getServerSideProps function:
export const getServerSideProps = () => {
const reduxStore = initializeStore()
const { dispatch } = reduxStore
getPosts().then((posts) => {
console.log(posts)
dispatch(initializeArticles(posts))
})
return { props: { initialReduxState: reduxStore.getState() } }
}
export default Home

Get the EditorState(DraftJS) from DB with Redux

I want to repopulate the editor's state with the values i have saved to Firebase.
OnSubmit:
sendNotes = (e) => {
e.preventDefault();
let contentState = this.state.editorState.getCurrentContent()
let note = { content: convertToRaw(contentState) }
note["content"] = JSON.stringify(note.content);
this.props.createNote(note.content);
};
NoteAction:
export function getNote() {
return (dispatch) => {
database.on("value", (snapshot) => {
dispatch({
type: LOAD_NOTE,
payload: snapshot.val(),
});
});
};
}
noteReducer:
export default function (state = {}, action) {
switch (action.type) {
case LOAD_NOTE:
return action.payload;
default:
return state;
}
}
REDUX/firebase:
{
type: 'LOAD_NOTE',
payload: {
'-MNOwBIWNqY_ZFDO4ILs': '{"blocks":[{"key":"c27el","text":"ASD!!!!!!!!!!!!","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}',
'-MNOyHLvaORxEmuuJmzJ': '{"blocks":[{"key":"c27el","text":"HELLO WORLD","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}',
'-MNOyP50oGHRLiP3T5_h': '{"blocks":[{"key":"c27el","text":"This is a REDUX STORE","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}'
}
}
MapStateToProps:
function mapStateToProps(state, ownProps) {
return {
note: state.notes,
};
}
export default connect(mapStateToProps, { getNote })(TextEditor);
My Code:
componentDidMount() {
this.props.getNote();
}
componentWillReceiveProps = (nextProps) => {
if (nextProps.note !== null) {
let item = "";
_.map(nextProps.note, (note, key) => {
return (item = note);
});
this.setState({
editorState: EditorState.createWithContent(convertFromRaw(JSON.parse(item))),
});
}
};
The code is working but I'm not 100% sure about these lifecycle methods & if the code i have written is 'stable'. I am stuck with this, please :).

Cannot change redux boolean state

I feel little confused, the problem is defineAvailableTouch action and state update connected to it.
Here is my code:
Actions/index.js
import {
ANIMATE_HELLO,
HANDLE_SCROLL,
IS_TOUCH_DEVICE,
SET_ABOUT_TOP,
SET_CONTACT_TOP,
SET_PORTFOLIO_TOP
} from "../Constants/ActionTypes";
export const animateHello = hello => ({
type: ANIMATE_HELLO,
payload: hello
});
export const handleScroll = scrollDelta => ({
type: HANDLE_SCROLL,
payload: scrollDelta
});
export const defineTouchAvailable = isTouchDevice => ({
type: IS_TOUCH_DEVICE,
payload: isTouchDevice
});
export const setAboutTop = aboutTop => ({
type: SET_ABOUT_TOP,
payload: aboutTop
});
export const setContactTop = contactTop => ({
type: SET_CONTACT_TOP,
payload: contactTop
});
export const setPortfolioTop = portfolioTop => ({
type: SET_PORTFOLIO_TOP,
payload: portfolioTop
});
Reducers/index.js
import {
IS_TOUCH_DEVICE,
} from "../Constants/ActionTypes";
import { initialState } from "../Constants/InitialState/InitialState";
export const rootReducer = (state = initialState, action) => {
switch(action.type) {
case ANIMATE_HELLO:
return {
...state,
hello: action.payload
};
case HANDLE_SCROLL:
return {
...state,
scrollState: action.payload
};
case IS_TOUCH_DEVICE:
console.log(action.payload); //!!!!!! THIS PRINTS EXPECTED VALUE !!!!!!!!!!
return {
...state,
isTouchDevice: action.payload
};
case SET_ABOUT_TOP:
return {
...state,
aboutTop: action.payload
};
case SET_CONTACT_TOP:
return {
...state,
contactTop: action.payload
};
case SET_PORTFOLIO_TOP:
return {
...state,
portfolioTop: action.payload
};
default:
return state
}
};
InitialState.js
export const initialState = {
scrollState: 0,
hello: 'H',
aboutTop: 0,
portfolioTop: 0,
contactTop: 0,
isTouchDevice: true
};
App.js
import React, { Component } from 'react';
import { connect } from "react-redux";
import About from "./Containers/About";
import Contact from "./Containers/Contact";
import Page from "./Containers/Page";
import Projects from "./Containers/Projects";
import {
defineTouchAvailable,
handleScroll
} from "./Actions";
window.onbeforeunload = () => {
handleScroll(0);
document.documentElement.scrollTop = 0;
};
const mapStateToProps = state => {
return {
isTouchDevice: state.isTouchDevice
}
};
const dispatchStateToProps = dispatch => {
return {
defineTouchAvailable: isTouchDevice =>
dispatch(defineTouchAvailable(isTouchDevice)),
handleScroll: scrollState => dispatch(handleScroll(scrollState))
}
};
class App extends Component {
componentDidMount() {
try {
document.createEvent('touchevent');
this.props.defineTouchAvailable(true);
} catch(e) {
this.props.defineTouchAvailable(false);
}
console.log(this.props.isTouchDevice); //!!!!!!!!!!!!!!! THIS ALWAYS PRINTS VALUE FROM initialState !!!!!!!!!!!!!!
if(this.props.isTouchDevice) {
document.documentElement.scroll(0, 1);
}
document.addEventListener('scroll', () => {
if (document.documentElement.scrollTop === 0) {
this.props.handleScroll(0);
}
});
}
render() {
return (
<div>
<Page/>
<Projects/>
<About/>
<Contact/>
</div>
);
}
}
export default connect(mapStateToProps, dispatchStateToProps)(App);
I really can't figure out whats wrong here.
As I commented
reducer console.log prints correct value that is expected to be assigned to my state (isTouchDevice field), but
after assigning it in dispatch action nothing changes - it is always value from initialState.
Can someone please explain it to me? Do I change my redux state uncorrectly? Then why other actions work as they're expected to?
The updated value of isTouchDevice will be available in componentDidUpdate, render or componentWillReceiveProps, not in componentDidMount.
componentDidMount will only be called one time when your component is mounted.
Note: componentWillReceiveProps is deprecated, better to not use it.

Trying to populate props with async promise inside of ComponentDidMount

So inside of my uncontrolled PossibleMatches component, I know from the way React works, the initial rendering phase will occur with empty prop values (if those prop values rely on external application state (mapStateToProps)) regardless of whether or not I have a componentDidMount lifecycle method or constructor setup. In response to this, I've setup a promise inside of the componentDidMount so that when I dispatch prop functions [defaultPieces, arrangePieces], I can have the UI render an ActivityIndicator to indicate something is currently fetching. The problem is, I cannot seem to get the mapStateToProps function to understand the state when I call mapStateToProps from within the success phase of the promise. Here it is:
class PossibleMatches extends Component {
constructor(props){
super(props);
}
componentDidMount(props){
return new Promise((resolve, reject) => {
let state;
let {defaultPieces, arrangePieces, isFetching} = this.props;
let makeClothesAppear = function(){
defaultPieces();
arrangePieces();
isFetching = true;
}
resolve(makeClothesAppear());
}).then(function(state){
mapStateToProps(state);
this.props.isFetched = true
this.props.isFetching = false;
}).catch((error) => {
console.log('FetchClothesError: ', error);
})
}
}
How the UI would make a decision on what to display:
renderDecision(){
const {UpperComponents, LowerComponents} = this.props;
const {currentUpperComponent, currentLowerComponent} = this.state.currentComponent.whichPiece;
const {LowerComponentEnabled, UpperComponentEnabled} = this.state;
if (this.props.isFetching){
return (<div className='activityLoader'>
<ActivityIndicator number={3} duration={200} activeColor="#fff" borderWidth={2} borderColor="50%" diameter={20}/>
</div>);
} else if (this.props.isFetched){
return (<div className = "PossibleMatches_Container">
<i className = 'captureOutfit' onClick = {this.snapshotMatch}></i>
{UpperComponents.map((component) => {
return (<UpperComponent key={component.createdAt} id={component.id}
switchComponent={this.switchFocus}
setCurrentPiece = {this.setNewPiece}
evaluatePiece={this.isOppositeComponentSuggested}
image={component.image}
toggleToPiece = {(LowerComponentEnabled) => {if (LowerComponentEnabled === false){this.setState({LowerComponentEnabled: true})}else{return;} this.setState({currentLowerComponent: this.props.suggestedBottoms[0]})}}
isLowerComponentEnabled={LowerComponentEnabled}
ref={this.residingUpperComponent}
className = {this.state.currentComponent.whichPiece.whichType === 'match' ? 'PossibleMatches_Container' : this.state.currentComponent.whichPiece.whichType === 'bottom' ? 'standalonePiece' : 'standalonePiece'}/>)
})
}
{LowerComponents.map((component) => {
return (<LowerComponent key={component.createdAt} id={component.id}
setCurrentPiece = {this.setNewPiece}
evaluatePiece={this.isOppositeComponentSuggested}
image={component.image}
toggleToPiece={(UpperComponentEnabled) => {if (UpperComponentEnabled === false){this.setState({UpperComponentEnabled: true})}else{return;} this.setState({currentUpperComponent: this.props.suggestedTops[0]})}}
switchComponent={this.switchFocus}
isUpperComponentEnabled={UpperComponentEnabled}
ref={this.residingLowerComponent}
className = {this.state.currentComponent.whichPiece.whichType === 'match' ? 'PossibleMatches_Container' : this.state.currentComponent.whichPiece.whichType === 'bottom' ? 'standalonePiece' : 'standalonePiece'}/>)
})
}
</div>)
}
}
render(){
return(
<div className = 'GorClothingContainer'>
{/*<Wardrobe upperComponent={this.state.currentComponent.whichPiece.currentUpperComponent} lowerComponent={this.state.currentComponent.whichPiece.currentLowerComponent} enableCapture={(snapshot) => this.snapshotMatch = snapshot} />*/}
{this.renderDecision()}
</div>
);
}
My PossibleMatches Reducer
import {INITIAL_PIECES, GET_ANCILLARY_PIECES, ORGANIZE_PIECES, SET_CONTEMPLATED_PIECE} from '../actions/types';
const initialState = {
UpperComponents: [],
LowerComponents: [],
contemplated_piece: null,
extraTops: [],
extraBottoms: [],
standaloneTops: [],
standaloneBottoms: [],
suggestedTops: [],
suggestedBottoms: []
}
export default function(state = initialState, action){
switch(action.type){
case INITIAL_PIECES:
return Object.assign({}, state, {contemplated_piece: action.payload.contemplated_piece},
{extraTops: action.payload.extra_tops},
{extraBottoms: action.payload.extra_bottoms},
{standaloneTops: action.payload.standalone_tops},
{standaloneBottoms: action.payload.standalone_bottoms},
{suggestedTops: action.payload.suggested_tops},
{suggestedBottoms: action.payload.suggested_bottoms})
case GET_ANCILLARY_PIECES:
return Object.assign({}, state, {extraTops: action.payload.extra_tops},
{extraBottoms: action.payload.extra_bottoms},
{standaloneTops: action.payload.standalone_tops},
{standaloneBottoms: action.payload.standalone_bottoms},
{suggestedTops: action.payload.suggested_tops},
{suggestedBottoms: action.payload.suggested_bottoms})
case ORGANIZE_PIECES:
return Object.assign({}, state, {UpperComponents: action.payload.UpperComponents},
{LowerComponents: action.payload.LowerComponents})
case SET_CONTEMPLATED_PIECE:
return Object.assign({}, state, {contemplated_piece: action.payload.contemplated_piece})
default:
return state;
}
}
My combineReducers segment
import {combineReducers} from 'redux';
const allReducers = combineReducers({
Playlist: PlaylistReducer,
eventOptions: eventTicketReducer,
possibleMatches: PossibleMatchesReducer,
Intro: combineForms({
basicUserInfo: BasicUserInfoState,
GenderInfo: GenderInfoState,
ContactInfo: ContactInfoState
}, 'Intro'),
routing: routerReducer,
form: formReducer
});
Prop Values:
PossibleMatches.defaultProps = {
isFetching: true,
isFetched: false
}
My mapStateToProps function
function mapStateToProps(state){
return {UpperComponents: state.possibleMatches.UpperComponents,
LowerComponents: state.possibleMatches.LowerComponents,
contemplatedPiece: state.possibleMatches.contemplated_piece,
extraTops: state.possibleMatches.extraTops,
extraBottoms: state.possibleMatches.extraBottoms,
standaloneTops: state.possibleMatches.standaloneTops,
standaloneBottoms: state.possibleMatches.standaloneBottoms,
suggestedTops: state.possibleMatches.suggestedTops,
suggestedBottoms: state.possibleMatches.suggestedBottoms}
}
function mapDispatchToProps(dispatch){
return {
defaultPieces: () => {
dispatch(defaultPieces())
},
arrangePieces: () => {
dispatch(arrangePieces())
},
getCorrespondingPieces: () => {
dispatch(getCorrespondingPieces())
},
setEvaluatedPiece: () => {
dispatch(setEvaluatedPiece())
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PossibleMatches)
My Question is: What exactly is wrong with the way that I've implemented the promise. With the reducers and the redux actions setup correctly(I know because I've logged the fetched items to the console from the redux actions file), how can I properly populate the prop values in mapStateToProps. Currently the error is:
Im using React 16.4.0
A simple redux use case would seem as follows
possibleMatches.jsx (Component file)
class PossibleMatches extends React.Component {
state = {
isFetching: false
}
componentDidMount() {
this.setState({isFetching: true})
fetchingSomethingFromServer()
.then(resp => {
this.setState({isFetching: false})
this.props.UpdateRedux(resp)
});
}
render() {
const { isFetching } = this.state;
const { data } = this.props;
return (
isFetching ? <div>loading...</div> : <div>{data}</div>
)
}
}
export default connect(state => ({ data: state.possibleMatches.data }), {UpdateRedux})
actions.js (action creator file)
Use this action to update any data into redux
export const UpdateRedux = (data) => {type: 'UPDATE_REDUX', payload: data}
reducers.js
This is the file that holds the redux state
const defaultState = {
data: null
}
export default (state = defaultState, action) => {
switch(action.type) {
case 'UPDATE_REDUX':
return {data: action.payload};
default:
return state
}
}
In your combine reducers import this reducer and assign it as follows
import possibleMatches from 'reducers.js';
combineReducers({ possibleMatches });

Wait for AJAX request ( React, Redux)

Need display data after AJAX call will be done.
My Reducer:
import { INCOME_PROFILE } from '../actionTypes'
import Immutable from 'immutable'
const initialUserState = [];
const profileReducer = function(state = initialUserState, action) {
//console.log('actiondata in reducer:' + action.data + action.type);
switch(action.type) {
case 'INCOME_PROFILE_IS_LOADING':
return Object.assign({}, state, { hh: action.hasFetched });
case 'INCOME_PROFILE':
return Object.assign({}, state, { course_list: action.data, hh: action.hasFetched });
default:
return state;
}
}
export default profileReducer
My action creator:
export function GET_ITEM_REQUEST() {
return {
type: INCOME_PROFILE_IS_LOADING,
hasFetched: false,
}
}
function receiveData(json) {
return{
type: INCOME_PROFILE,
data: json,
hasFetched: true
}
};
export function IncomeProfileList () {
return dispatch => {
return (
axios.post(Api.getURI("profile"),{}, {
headers: { 'X-Authenticated-Userid': '15000500000#1' }
}).then(function (response) {
//console.log(response.data);
dispatch(receiveData(response.data.body));
})
.catch((error) => {
console.log(error);
})
)
}
}
My component:
class IncomeProfile extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
this.props.IncomeListProfile();
}
render() {
console.log(this.props.isloading);
if (this.props.isloading) {
return <p>Sorry! There was an error loading the items</p>;
}
}
}
const mapDispatchToProps = function(dispatch) {
return {
IncomeListProfile: () => dispatch(IncomeProfileList())
}
}
const mapStateToProps = function(state) {
//var mystore = state.toJS()
var mystore = state.getIn(['incomeProfileList'])['course_list'];
var mystored = state.getIn(['incomeProfileList']);
console.log(mystored.hh);
var copy = Object.assign({}, mystore);
return {
items: copy.course_list,
isloading: mystored.hh
};
}
I need next: While response not finish, I no need to display data. Condition if not works now
console.log at first time get undefined - think must be false, but it not state false. and second time it's getting true.
You don't need property 'isLoading' - just handle 2 cases in which you have your data and you haven't. Put this condition in render() function, because the component is going to refresh after passing data through the reducer. Syntax will be something like this in your case:
render() {
if(!this.props.items) {
return <div>Loading...</div>;
} else {
return (
<div>Display your data!</div>
);
}
}

Categories

Resources