My component with filters doesn't re-render after changes in Redux state. With console.log() I can see that action and reducer works. ObjectFilter.js after changes gives good result with console, but doesn't re-render.
mapReducer.js
const mapReducer = (state = initState, action) => {
switch(action.type) {
case actions.SET_FILTERS:
console.log('SET_FILTERS', state)
return({
...state,
filters: action.filters
})
default:
return state;
}
}
export default mapReducer;
mapActions.js
export const setFilters = (el, old_filters) => {
let filters = old_filters;
let new_el = !old_filters[el];
filters[el] = new_el;
console.log(filters)
return (dispatch, getState) => {
dispatch({
type:actions.SET_FILTERS,
filters: filters
})
}
}
objectFilters.js
class ObjectFilters extends Component {
changeFilterHandler = (el) => {
this.props.setFilters(el, this.props.filters);
}
render () {
console.log(this.props.filters)
return (
/* some code */
);}
}
const mapDispatchToProps = dispatch => {
return {
setFilters: (el, filters) => dispatch(setFilters(el, filters))
}
}
const mapStateToProps = state => {
return {
filters: state.mapRedux.filters
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ObjectFilters);
The problem in your code is that you are mutating the old_filters directly, instead make a clone of it and then update filter value. Never mutate state and prop directly
export const setFilters = (el, old_filters) => {
let filters = {...old_filters}; // using spread operator to create a clone
let new_el = !old_filters[el];
filters[el] = new_el;
return (dispatch, getState) => {
dispatch({
type:actions.SET_FILTERS,
filters: filters
})
}
}
Related
I am pretty new to react and redux. I am trying to call get method API in useEffect hook but the function I am calling isn't getting invoked at all. the same function is invoked when I tried calling outside the useEffect and the state is also updated but I still got that error Cannot read property 'map' of null. so in both the cases, the common thing is getting the error and my code seems good to me. all other functions and states are working except this and I am not able to figure out what I am missing. any help very much is appreciated. thank you.
Orders.js
const orders = (props) => {
// getting the orders
useEffect(() => {
props.fetchOrders();
}, []);
let orders;
if(props.loading || props.error) {
orders = <Loading />
}
console.log(props)
orders = props.orders.map((order) => <Order
key={order.id}
ingredients={order.ingredients}
price={order.price}
customer={order.customerDetails}
/>);
return(
<div>
{orders}
</div>
)
}
const mapStateToProps = state => {
return {
orders: state.orders,
error: state.error,
loading: state.loading
}
}
const mapDispatchToProps = dispatch => {
return {
fetchOrders: () => dispatch(getOrders())
}
}
export default connect(mapStateToProps, mapDispatchToProps) (errorHandler(orders, axiosInstance));
action.js
// not invoking
const getOrders = () => {
return dispatch => {
axiosInstance.get('/orders.json').then(res => {
const ordersData = transformData(res);
dispatch(fetchOrders(ordersData));
}).catch(err => {
console.log(err)
})
}
}
const transformData = (response) => {
// simplified logic
const ordersData = [];
if(response.data) {
for (let key in response.data) {
ordersData.unshift({
...response.data[key],
id: key
})
}
}
return ordersData;
}
const fetchOrders = (ordersData) => {
return {
type: actionTypes.GET_ORDERS,
orders: ordersData
}
}
export { getOrders }
reducer.js
const reducer = (state = intialState, action) => {
switch (action.type) {
.
.
case actionType.GET_ORDERS:
return setOrders(state, action);
.
.
default:
return state;
}
}
const setOrders = (state, action) => {
return {
...state,
orders: action.orders.concat(),
error: false,
loading: false
}
}
Hi developers I am currently studying react js with redux for frontend and I want to implement state management (Redux) to my sample project. My Backend I use laravel. Now I already set the Action, Services, Reducers. When I try to console log the props state to my Component it shows that my action data response is null.
Problem: The action data response is null only.
Here is my MapState & mapDisPatch
const mapStateToProps = (state) => {
return {
filterChartRes: state.dashboard.filterChartRes,
}
}
const mapDisPatchToProps = (dispatch) => {
return {
loadFilterChartData: (selectionRange) => dispatch(loadFilterChartData(selectionRange)),
}
}
My Action:
export const loadFilterChartData = (selectionRange) => {
return (dispatch) => {
getFilterChartData(selectionRange).then((res) => {
console.log(res)
dispatch({ type: 'FILTER_CHART_RESPONSE', res })
},
error => {
dispatch({ type: 'FILTER_CHART_ERROR', error });
}
)
}
}
My Services:
export const getFilterChartData = (selectionRange) => {
const http = new HttpService();
//let filterData = selectionRange !== "" ? selectionRange : null;
let url = "auth/filter_chart";
return http.getData(url)
.then(data => {
return data;
})
}
My Reducers:
const initState = {
filterChartRes: null,
filterChartErr: null
};
const DashboardReducer = (state = initState, action) => {
switch (action.type) {
case 'FILTER_CHART_RESPONSE':
return {
...state,
filterChartRes: action.res.data
}
case 'FILTER_CHART_ERROR':
return {
...state,
filterChartErr: 'action.error'
}
default:
return state
}
}
export default DashboardReducer;
My Render:
const {filterChartRes } = this.props
console.log(filterChartRes, "My Filter");
My Work Output:
Back End Controller:
public function filter_chart() {
return 'Sample Data';
}
Hope Someone help on my problem
To solved this issue:
You must call the props inside the DidMount
componentDidMount = () => {
this.props.loadFilterChartData()
}
My axios transaction is all done in the redux actions so that I can re-use the function. The issue is that, I need to fetch the data first which is done by redux and then re-assign the value in a state, but the data cannot be populated in the state. Below is how my code looks like.
Setting.js
...
import { getUserDetail } from './redux/actions/settingActions';
export default function Setting() {
const dispatch = useDispatch()
const { user } = useSelector(state => state.settingReducer)
const [userDetail, setUserDetail] = useState()
useEffect(() => {
dispatch(getUserDetail())
setUserDetail(user) // I want to set the user here
}, [])
...
}
settingActions.js
export const getUserDetail = () => (dispatch, getState) => {
axios.get('url-goes-here')
.then(res => {
dispatch({
type: SET_USER_DETAIL,
payload: { res.data }
})
})
.catch(error => {
throw error;
})
}
settingReducer
function initialState() {
return {
...
user: {}
}
}
export default function (state = initialState(), action) {
const { type, payload } = action;
switch (type) {
case SET_USER_DETAIL:
return {
...state,
user: payload
}
default:
return state
}
}
My purpose of doing this is because I want to do some user details update but I want it to be done within the same file.
put user and dispatch as dependency in useEffect
useEffect(() => {
dispatch(getUserDetail())
setUserDetail(user)
}, [user,dispatch])
How can I use the mapdispatchtoprops function correctly to dispatch to reducer? First, I get data from the server and want to send this data to the reducer. firebaseChatData function cannot be transferred to the mapdispatchtoprops because it is inside the component
Messages.js
const MessageUiBody = ( { messages, loading } ) => {
const userData = JSON.parse(localStorage.getItem("user-data"));
useEffect( () => {
const firebaseChatData = () => (dispatch) => {
firebaseDB.ref().child(API.firebaseEnv + "/messages/messageItem" + userData.account_id)
.on("value", snap => {
const firebaseChat = snap.val();
// console.log(firebaseChat)
dispatch(firebaseChatAction(firebaseChat))
});
};
}, []);
return(
<div> // code </div>
);
};
//Action
const firebaseChatAction = (firebaseChat) => ({
type: 'FIREBASE_MESSAGE',
firebaseChat
});
const mapDispatchToProps = (dispatch) => {
return {
data : () => {
dispatch(firebaseChatData())
}
}
};
export default connect(null, mapDispatchToProps)(MessageUiBody)
Reducer
export default function messages ( state = [], action = {}) {
switch (action.type) {
case 'FIREBASE_MESSAGE' :
state.data.messages.push(action.firebaseChat);
return {
...state
};
default:
return state
}
}
You'll have to change your code, because you're defining data as the prop function that will dispatch your action:
const mapDispatchToProps = dispatch => {
return {
data: (result) => dispatch(firebaseChatAction(result)),
}
}
After that change the line after the console log in your promise and use the data prop that you defined in your mapDispatch function:
const MessageUiBody = ( { data, messages, loading } ) => {
const userData = JSON.parse(localStorage.getItem("user-data"));
useEffect( () => {
const firebaseChatData = () => (dispatch) => {
firebaseDB.ref().child(API.firebaseEnv + "/messages/messageItem" + userData.account_id)
.on("value", snap => {
const firebaseChat = snap.val();
// here you call the data that will dispatch the firebaseChatAction
data(firebaseChat)
});
};
}, []);
return(
<div> // code </div>
);
};
Also is worth to notice that you don't have to push items in your state, you can't mutate the current state, so always try to generate new items instead of modifying the existing one, something like this:
export default function messages ( state = [], action = {}) {
switch (action.type) {
case 'FIREBASE_MESSAGE' :
return {
...state,
data: {
...state.data,
messages: [...state.data.messages, action.firebaseChat]
}
};
default:
return state
}
}
With the spread operator you are returning a new array that contains the original state.data.messages array and will add the firebaseChat item as well.
I am updating my redux state, and the state doesn't seem to be getting mutated, however the DOM is still not refreshing.
//update filters for events
setFilters = (name) => async () => {
const {onSetActiveEventTypes, authUser} = this.props;
let array = this.props.activeEventTypes
let index = array.indexOf(name);
if (index > -1) {
array.splice(index, 1);
}else {
array.push(name)
}
await Promise.resolve(onSetActiveEventTypes(array));
}
render() {
return <Accordion title="Filters" collapsed>
{
(this.props.eventTypes && this.props.activeEventTypes ?
<EventFilter eventTypes={this.props.eventTypes} activeEventTypes={this.props.activeEventTypes} action={this.setFilters}/>
: '')
}
</Accordion>
}
const mapStateToProps = (state) => ({
eventTypes: state.eventsState.eventTypes,
activeEventTypes: state.eventsState.activeEventTypes
});
const mapDispatchToProps = (dispatch) => ({
onSetEventTypes: (eventTypes) => dispatch({ type: 'EVENT_TYPES_SET',
eventTypes }),
onSetActiveEventTypes: (activeEventTypes) => dispatch({ type:
'ACTIVE_EVENT_TYPES_SET', activeEventTypes })
});
const authCondition = (authUser) => !!authUser;
export default compose(
withAuthorization(authCondition),
connect(mapStateToProps, mapDispatchToProps)
)(DashboardPage);
I have placed my code in my component above, it should be all that is needed to debug. I will put the reducer below
const applySetEventTypes = (state, action) => ({
...state,
eventTypes: action.eventTypes
});
const applySetActiveEventTypes = (state, action) => ({
...state,
activeEventTypes: action.activeEventTypes
});
function eventsReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case 'EVENT_TYPES_SET' : {
return applySetEventTypes(state, action);
}
case 'ACTIVE_EVENT_TYPES_SET' : {
return applySetActiveEventTypes(state, action);
}
default : return state;
}
}
export default eventsReducer;
Above is my reducer, I think I am following the correct patterns for managing redux state and maintaining immutability. What am I missing?
setFilters is a method that the checkboxes use to update active filters compared to all the filters available.
You are definitely mutating state:
const {onSetActiveEventTypes, authUser} = this.props;
let array = this.props.activeEventTypes
let index = array.indexOf(name);
if (index > -1) {
array.splice(index, 1);
}else {
array.push(name)
}
That mutates the existing array you got from the state, and then you are dispatching an action that puts the same array back into the state. So, you are both A) reusing the same array all the time, and B) mutating that array every time.
The approaches described in the Immutable Update Patterns page in the Redux docs apply wherever you are creating new state values, whether you're generating the new state in a reducer based on a couple small values, or before you dispatch the action.
//update filters for events
setFilters = (name) => async () => {
const {onSetActiveEventTypes, authUser} = this.props;
let array = []
this.props.activeEventTypes.map((type) =>{
array.push(type)
})
let index = array.indexOf(name);
if (index > -1) {
array.splice(index, 1);
}else {
array.push(name)
}
//use this once server sending active filters
// await eventTable.oncePostActiveEventTypes(authUser.email, array).then( data
=> {
// Promise.resolve(onSetActiveEventTypes(data));
// })
await Promise.resolve(onSetActiveEventTypes(array));
}