I set some values in React Redux store while the ComponentDidMount() function. Redux Dev Tools displays that the state has been updated. But in props It doesn't get changed.
My reducer.js is,
const inititalState = {
slickColumns: null,
slickData: null
}
const reducer = (state = inititalState, actions) => {
switch(actions.type) {
case actionsTypes.SET_SLICK_GRID_COLUMNS: {
return {
...state,
slickColumns: columns
};
}
case actionsTypes.SET_SLICK_GRID_DATA: {
return {
...state,
slickData: [...mock_slick_data]
};
}
default: {
return state;
}
}
}
export default reducer;
action.js,
import * as actions from './actions';
export const setSlickGridColumns = () => {
return {
type:actions.SET_SLICK_GRID_COLUMNS
}
}
export const setSlickGridData = () => {
return {
type: actions.SET_SLICK_GRID_DATA
}
}
main.js, (Mapping Redux to state)
const mapStateToProps = state => {
return {
slickColumns: state.slickColumns,
slickData: state.slickData
};
};
const mapDispatchToProps = dispatch => {
return {
onSetSlickDataColumns: () => {
dispatch(actionTypes.setSlickGridColumns());
},
onSetSlickData: () => {
dispatch(actionTypes.setSlickGridData());
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Dimensions()(Home));
in ComponentDidMount function,
this.props.onSetSlickDataColumns(); // able to see this method is running and the state is updated in Redux Dev Tools
this.props.onSetSlickData();
console.log(this.props.slickColumns); // getting null here.
dv.setItems(this.props.slickData);
Even thought the state is updated in store, I am still not able to get the data in the props? why? any ideas?
index.js,
import slickReducer from './store/reducers/SlickGrid';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
slickReducer,
composeEnhancers(applyMiddleware(thunk))
);
const app = (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
ReactDOM.render(app, document.getElementById("root"));
[Edit]: Initially I set initialState object properties as 'null;
Adding my Redux Screenshot here,
Adding extra some logs in here. This might helpful to resolve this issue. Actually the grid instance is created in ComponentDidMount() method.
componentDidMount() {
console.log("[componentDidmount]....");
this.props.onSetSlickDataColumns();
this.props.onSetSlickData();
console.log(this.props);
if (this.props.slickColumns.length !== 0) {
const grid = (this.gridInstance = new Grid(
this.grid,
dv,
this.props.slickColumns,
options
));
// ------------------
}
}
When doing grid object, It should not have empty columns, and empty DataView.
I called the setSlickGridColumns and setSlickGridData method in various lifecycle methods such as constructor, componentWillMount and put console logs for props in mapStateToProps, constructor, componentWillMount, render and componentDidMount methods also. From the logs what I am getting is,
[mapStateToProps]
this.props.slickColumns: null
[componentConstructor].... calling onSetSlickDataColumns() and onSetSlickData() methods here..
this.props.slickColumns: null
[componentwillmount].... calling onSetSlickDataColumns() and onSetSlickData() methods here..
this.props.slickColumns: null
[render]
this.props.slickColumns: null
[componentDidmount].... calling onSetSlickDataColumns() and onSetSlickData() methods here..
this.props.slickColumns: null
[mapStateToProps]
this.props.slickColumns: Array(300) // Here props values are set
[render]
this.props.slickColumns: Array(300)
From the logs, what I understand is, The data has to be filled before the componentDidMount() method. But It doesn't setting up even though I dispatched reducer function in constructor and ComponentWillMount. Hope this logs help to resolve this problem.
Problem is you are not setting new data in your reducer you can see
const reducer = (state = inititalState, actions) => {
switch(actions.type) {
case actionsTypes.SET_SLICK_GRID_COLUMNS: {
return {
...state,
slickColumns : columns // you have to pass your new data as payload from your function : actions.payload
};
}
case actionsTypes.SET_SLICK_GRID_DATA: {
return {
...state,
slickData: [...mock_slick_data] // same here
};
}
default: {
return state;
}
}
}
You can pass your data when you dispatch an action
dispatch(actionTypes.setSlickGridColumns('Pass your data here '));
Then you can get your data as argument like
export const setSlickGridColumns = (data) => {
return {
type:actions.SET_SLICK_GRID_COLUMNS,
payload : data // pass your data as payload
}
}
Now you can use your data in reducer like actions.payload
.......
case actionsTypes.SET_SLICK_GRID_COLUMNS: {
return {
...state,
slickColumns : action.payload
};
........
try below code -> you need to return the dispatch inside the mapDispatchToProps like below
const mapDispatchToProps = dispatch => {
return {
onSetSlickDataColumns: () => {
return dispatch(actionTypes.setSlickGridColumns());
},
onSetSlickData: () => {
return dispatch(actionTypes.setSlickGridData());
}
};
};
Actually you're not going to get updated props in componentDidMount lifecycle hook, as you are dispatching your function after your component is mounted. You'll get updated props in your render, componentWillReceiveProps (deprecated), getDerivedStateFromProps and some other lifecycle hooks. You can read more about which lifecycle hooks are being called when props are updated in the official docs of react.
And then you're missing return statement in mapDispatchToProps as mentioned in one other answer.
Related
I am trying to access a value within an object I'm getting from my Redux store.
Here's my Redux state:
I'm setting this on app load like this:
useEffect(() => {
// Update the global state with the app content
getContent().then(res => {
props.updateContent(res);
});
}, []);
with an action:
export const updateContent = content => {
return {
type: "UPDATE",
payload: content
};
};
and a reducer:
const contentReducer = (state = {}, action) => {
switch (action.type) {
case "UPDATE":
return action.payload;
default:
return state;
}
};
export default contentReducer;
However, when I try to log or access the header_section, I get Cannot read property 'header_section' of undefined. Which is strange, because I can log out just the content props object and see it's there.
Here's where I'm trying to access it:
const Header = props => {
console.log("CONTENT-----", props.content.header_section);
// simplified component
return (
<button>{props.content.header_section.cta_buy_ticket}</button>
);
};
const mapStateToProps = state => {
return {
content: state.contentReducer.global_options
};
};
export default connect(mapStateToProps)(Header);
Codesandbox here - https://codesandbox.io/s/thirsty-sid-ov65i?file=/src/App.js
Your mapStateToProps picks contentReducer.global_options to be the content prop.
Your question indicates that you're trying to access contentReducer.header_section by doing content.header_section. In this case, you meant to return state.contentReducer as the content prop from mapStateToProps:
const mapStateToProps = state => {
return {
content: state.contentReducer
};
};
Now you can access props.content.header_options, which refers to the same object as state.contentReducer.header_options in mapStateToProps.
If you still get the error, then contentReducer's initial value is probably undefined. Either set an initial value with the same object shape as the data, or check that the keys you access exist before using them (e.g. if (!props.content || !props.content.header_options) return null)
I am trying to create a component that allows detecting changes in the redux store. Once the shouldUpdateData flag is set in the store, the component responsible for updating should fetch the data by using an async action creator. In my case, either the error "Maximum updates have reached" occurs or the update never happens.
Depending on the dispatch function stopFetching() (turns off the shouldUpdateData flag), the error or outcome changes. If I do the dispatch inside the action creator there are endless updates. If the code is used as it is below, no update occurs.
The reason I used the useSelector() hook from 'react-redux' is to detect a change in the store for the loading attribute.
Thank you in advance.
Here is the action creator:
export function updateDataAsync(id) {
return function (dispatch) {
// dispatch(fetchDataRequest());
return fetch(`/api/user/${id}/data`, {
method: "GET",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(
(result) => {
let {projects, notes} = result;
// New data and dispatch function
dispatch(fetchDataSuccess({projects, notes}));
},
(error) => { dispatch(fetchDataFailure(error)) }
)
}
}
Here is the reducer for this action creator:
export function savedData(state = DATA_INITIAL_STATE, action) {
switch(action.type) {
case FETCH_STATES.FETCH_DATA_REQUEST:
return {
...state,
loading: true
}
case FETCH_STATES.FETCH_DATA_SUCCESS:
return {
loading: false,
data: action.data,
error: ''
}
case FETCH_STATES.FETCH_DATA_FAILURE:
return {
loading: false,
data: {},
error: action.error.message
}
default:
return state;
}
}
The React component that is doing the update:
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
let reqSent = useRef(false);
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
})
return loading ? <LoadingAnimation /> : children;
}
const mapStateToProps = (state) => {
return {
userId: state.user.id,
shouldUpdate: state.shouldUpdateData // The flag that should trigger the update
}
}
const mapDispatchToProps = (dispatch) => {
return {
stopFetch: () => { dispatch(setShouldFetchData(false)) },
update: (id) => { dispatch(updateDataAsync(id)) },
startFetch: () => dispatch(fetchDataRequest()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(StoreUpdater);
You dint pass any dependency to useEffect so it will be called on every render which is causing infinite renders
change useEffect to
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
},[])
For complete information regarding useEffect refer this link
The reference I created inside the component responsible of the updates, was causing the problem. The reference was preventing the update dispatch to occur due to the if statement being false.
mapStateToProps and mapDispatchToProps were react-redux higher order functions to connect classes components into the store. there equalants at functional components are useSelector and useDispatch. re-write your HOC redux adaption into hooks, and add [ dependency ] at useEffect usage
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
const userId = useSelector(state => state.user.id);
const shouldUpdate = useSelector(state => state.shouldUpdateData);
let reqSent = useRef(false);
const dispatch = useDispatch() // import from 'react-redux'
useEffect(()=>{
if(!reqSent && shouldUpdate) {
dispatch(startFetch());
dispatch(update(userId));
reqSent.context = true;
}
}, [reqSent, shouldUpdate, startFetch, dispatch, update, userId])
return loading ? <LoadingAnimation /> : children;
}
export default StoreUpdater ;
I have a component that makes an API call and then updates the state through a reducer. The problem is, this doesn't work so well cause the data don't get updated in the component, it's like the react didn't notice a state change a never re-rendered the component, but I'm not sure if that's the real issue here. So the component looks like this:
class MyComponent extends Component {
componentDidMount() {
// ajax call
this.props.loadData(1);
}
render() {
return (
<Grid>
<MySecondComponent
currentData={this.props.currentData}
/>
</Grid>
);
}
}
const mapStateToProps = state => ({
reducer state.myReducer,
currentData: state.myReducer.currentData
});
const mapDispatchToProps = dispatch => {
return {
loadData: () => {
HttpClient.getData(id, (data) => {
dispatch(
action_loadCurrentData(
data
)
);
});
},
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
I am doing 2 things here: issuing an API call as soon as component is mounted, and then after data is fetched, dispatching action_loadCurrentData
This action looks like this:
//Action
export function action_loadCurrentData(
data
) {
return {
type: 'LOAD_CURRENT_DATA',
payload: {
currentData: data,
}
};
}
and the reducer:
//Reducer
const defaultState = {
};
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
state = {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
export default myReducer;
So the issue here is that the this.props.currentData that I'm passing to MySecondComponent will end up empty, as if I didn't set the data at all. However, If I stop the execution in the debugger and give it a few seconds, the data will be populated correctly, so I'm not sure what I'm doing wrong here?
Don't reassign state, return the newly created object instead
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
return {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
Your reducer needs to return the new state object, which needs to be a different instance from the previous state to trigger components update.
According to redux documentation:
The reducer is a pure function that takes the previous state and an action, and returns the next state.
And
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().
I am trying to learn redux.
I am trying to add favorites functionality through Redux.
so I created actions addFavoriteSPORTSs, reducers SPORTSReducer, and then dispatched in tab-demo.js where i am doing mapDispatchToProps and
mapStateToProps
when I click the heart icon I am adding favorites in session storage window.sessionStorage.setItem(
"favoriteValues",
JSON.stringify(action.payload)
);
but the problem is after the refresh the color is not staying in the heart.
I debugged in componentDidMount and I am able to print the favotites get item value but still colr not maintaining.
can you tell me how to fix it.
so that in future I will fix itmyself.
providing my code snippet below
https://codesandbox.io/s/5x02vjjlqp
actions/index.js
import {
ADD_SPORTS,
DELETE_SPORTS,
DELETE_ALL_SPORTS,
ADD_ALL_SPORTSS
} from "./types";
export const addFavoriteSPORTSs = data => ({
type: ADD_ALL_SPORTSS,
payload: data
});
actions/types.js
export const ADD_ALL_SPORTSS = "ADD_ALL_SPORTSS";
tab-demo.js
import { deleteAllPosts, addFavoriteSPORTSs } from "./actions/index";
componentDidMount() {
let favorites = window.sessionStorage.getItem("favoriteValues");
console.log("componentDidMount favorites--->", favorites);
if (favorites) {
this.props.addFavoriteSPORTSs(JSON.parse(favorites));
}
// debugger;
}
const mapDispatchToProps = dispatch => {
return {
onDeleteAllSPORTS: () => {
// console.log("called");
dispatch(deleteAllPosts());
},
addFavoriteSPORTSs: data => {
dispatch(addFavoriteSPORTSs(data));
}
};
};
const mapStateToProps = state => {
return {
SPORTSs: state.SPORTSs
};
};
export default withStyles(styles)(
connect(
mapStateToProps,
mapDispatchToProps
)(ScrollableTabsButtonForce)
);
SPORTSReducer.js
switch (action.type) {
case ADD_ALL_SPORTSS:
window.sessionStorage.setItem(
"favoriteValues",
JSON.stringify(action.payload)
);
return action.payload;
case ADD_SPORTS:
state = state.filter(comment => comment.id !== action.payload.id);
value = [...state, action.payload];
console.log("ADD_SPORTS state--->", state);
console.log("ADD_SPORTS value--->", value);
//return [...state, action.payload];
// state = state.filter(SPORTS => SPORTS.SPORTSID !== action.payload.SPORTSID);
// value = [...state, action.payload]
window.sessionStorage.setItem("favoriteValues", JSON.stringify(value));
console.log("JSON.stringify(value)--->", JSON.stringify(value));
console.log("state--->", state);
return state;
When the component mounts you retrieve your favourties and set the redux state via calling your prop method. Your component will receive this new state via mapStateToProps, but it won't update without a suitable lifecycle method like componentDidUpdate or componentWillReceiveProps.
You can check out the lifecycle methods here.
Also, you are mutating your state in redux which is something you want to avoid. See this line:
state = state.filter(comment => comment.id !== action.payload.id);
I would also recommend Redux middleware for these tasks. You can set up middleware that will write to session storage whenever a specific action occurs and you can then rehyrdate Redux from that as well.
I have a React component that's connected to Redux store. I'm fetching resources(posts) in the componentWillMount life-cycle method.
componentWillMount() {
this.props.fetchPosts();
}
The component will subscribe to Redux store and getting isFetching and posts from the store.
const mapStateToProps = (state) => {
return {
posts: getAllPosts(state),
isFetching: getIsFetchingPosts(state),
}
}
I'd like to show a spinner when it's still fetching, so in the render method I'd like to do this.
render() {
if (this.props.isFetching) {
return <Spinner />
}
return this.props.posts.map(post => <PostItem key={post.id}{...post}/>)
}
BUT if I console.log isFetching in the render method, first it shows false and thentrue and then finally false.
Ideally when this container renders for the first time isFetching state is already set to true and showing the spinner. What changes do I need to make to make that happen?
Here is code for the action creator and reducers
/*** Action Creator ***/
export const fetchPosts = () => (dispatch) => {
dispatch({
type: REQUEST_POSTS,
});
return axios({
method: 'get',
url: `${API_URL}/posts`,
})
.then(({data}) => {
dispatch({
type: RECEIVE_POSTS,
payload: data.posts,
})
})
.catch((response) => {
// some error handling.
});
}
/*** Reducers ***/
const initialState = {
isFetching: false,
allIds: [],
byId: {},
};
const isFetching = (state = initialState.isFetcthing, action) => {
switch (action.type) {
case REQUEST_POSTS:
return true;
case RECEIVE_POSTS:
return false;
default:
return state;
}
}
const allIds = (state = initialState.allIds, action) => {
switch (action.type) {
case RECEIVE_POSTS:
return action.payload.map(post => post.id);
default:
return state;
}
}
const byId = (state = initialState.byId, action) => {
switch (action.type) {
case RECEIVE_POSTS:
return action.payload.reduce((nextState, post) => {
nextState[post.id] = post;
return nextState;
}, {...state});
default:
return state;
}
}
const posts = combineReducers({
isFetching,
allIds,
byId,
});
export default posts;
/*** Selectors in 'posts.js' file ***/
export const getAllPosts = (state) => {
const { allId, byId } = state;
return allIds.map(id => byId[id]);
}
/*** rootReducer file ***/
import posts, * as fromPosts from './posts';
const rootReducer = combineReducers({
posts,
})
export default rootReducer;
export const getAllPosts = (state) => {
return fromPosts.getAllPosts(state.posts);
};
Thank you in advance!
The plain answer is that this is the expected behaviour, given your implementation. You're mapping the isFetching state to a prop. Here's what's happening:
The initial value of isFetching in the state tree is false, so the isFetching prop value is false, so it renders as false.
You dispatch an action which changes isFetching to true in the state tree. This new state is mapped to a new isFetching prop value of true, which causes a re-render, where it renders as true.
You (asynchronously) dispatch another action which changes isFetching back to false in the state tree. Same as in (2), this causes a re-render, with isFetching as false.
The plain solution if you want simply a render of true, false*, with the current implementation, is to set isFetching to true in your reducer's initial state.
Whether or not this implementation makes sense on a design level for this component is a broader question, that there's not enough context to answer here :-)
*update for completeness I should say I don't know if the render() function would be called twice, with isFetching resolved as true,false or three times with true,true,false in this case. I suspect that react-redux may optimise the rendering of the component such that no re-render occurs if the mapped isFetching prop changes from true->true, but don't know this for sure - would be grateful and interested if you could let me know what your logging outputs?
In any case, at the DOM level certainly only two renders would occur, due to the standard react virtual-DOM diffing optimisation, so in effect the result is the same either way