I'm new to React Redux.
It's fantasic so that
I want to see about tasks state of Store.
but don't see it.
result
tasks: [undefined]
Ask
so how can I?
here code:
import { createStore } from 'redux'
const initalState = {
tasks: []
}
function tasksReducer(state = initalState, action) {
switch (action.type) {
case 'ADD_TASK':
return {
...state,
tasks: state.tasks.concat([action.task])
};
default:
return state;
}
}
const addTask = (task) => ({
type: 'ADD_TASK',
payload: {
task
}
})
const store = createStore(tasksReducer)
store.dispatch(addTask('like it'))
console.log(store.getState()) <-- here
The problem is your action. It's adding a task with a key of payload, but you're trying to concat it with action.task (Which doesn't exist).
The object that would be sent into your reducer would look like this:
{
type: 'ADD_TASK',
payload: {
task: 'like it'
}
}
You can see clearly here, action.task doesn't exist, but action.payload.task does. Either change the object around, or modify it so you can access it at action.task:
const addTask = (task) => ({
type: 'ADD_TASK',
task
})
Or, modify your reducer:
function tasksReducer(state = initalState, action) {
switch (action.type) {
case 'ADD_TASK':
return {
...state,
tasks: state.tasks.concat([action.payload.task])
};
default:
return state;
}
}
In the future: a bit of debugging would have gone a long way here (And avoided this question altogether). A simple console.log(action) at the top of your reducer, would log the above object, and you could infer based on why it's trying to add undefined why it wouldn't work.
Related
I have two dashboards which contains similar data and properties that I want to implement in redux. See below.
Dashboard 1 : {filters, widgets, custom}
Dashboard 2: {filters, widgets, custom}
I want to create my redux state like so:
{
dashboards: {
dashboard1:{
filter:{ // filter related stuff},
widgets:{ // widget related state},
custom:{ // custom state}
},
dashboard2: {
filter:{ // filter related state},
widgets:{ // widget related state},
custom:{ // custom state}
}
},
auth: {// auth related stuff},
...// other state keys
}
In order to achieve this I am trying to use combine reducers like this for dashboards
// xyz.reducer.js
combineReducers({
filters: filterReducers,
widgets: widgetReducers,
custom: CustomReducers
})
now I have created a dashboard reducer, which is called every time user navigates to a certain dashboard. I want to assign this state based on dashboardID. something like -
const dashboardReducer = (state = defaultState, action) => {
switch (action.type) {
case CREATE_DASHBOARD: {
const { payload } = action;
return {
...state,
[payload.dasboardId]: // all the combined reducer state here
};
}
default:
return state;
}
};
Can someone suggest me how can I achieve this? I am also open for suggestion if someone have a better
way of maintaing this kind of state structure.
Sure, you can do this as long as you have a way to identify all actions that are relevant to the xyz reducer and have away to identify the dashboardId.
For your example, assuming the nested reducer from xyz.reducer.js is called xyz:
import xyz from './xyz.reducer.js'; // or whatever
const dashboardReducer = (state = defaultState, action) => {
switch (action.type) {
case CREATE_DASHBOARD: {
const { payload } = action;
return {
...state,
[payload.dashboardId]: xyz(state[payload.dashboardId], action),
};
}
default:
return state;
}
};
Let's say you know all of the action types that the dashboard might need to respond to you and they all have dashboardId as a key in the payload you can simply pass all of those along.
const dashboardReducer = (state = defaultState, action) => {
switch (action.type) {
case CREATE_DASHBOARD:
case UPDATE_DASHBOARD_FILTER:
case ADD_DASHBOARD_WIDGET:
//... etc
{
const { payload } = action;
return {
...state,
[payload.dashboardId]: xyz(state[payload.dashboardId], action),
};
}
default:
return state;
}
};
This is a little brittle so you may want to do something like assume that any payload with a dashboard id should be passed on:
const dashboardReducer = (state = defaultState, action) => {
const {payload = {}} = action;
if (typeof payload.dashboardId !== 'undefined') {
return {
...state,
[payload.dashboardId]: xyz(state[payload.dashboardId], action),
};
}
return state;
};
Now you don't need to keep up the list of action types, you just need to ensure that all relevant action creators include a dashboardId in their payload.
I am trying to implement Redux on a React Hooks project, but it doesnt seems to work good. Am I doing something wrong here?
reducer.js
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS: {
state.educations = action.payload;
return state;
}
default:
return state;
}
}
action.js
import * as types from '../constans/home';
export const getEducations = () => {
return dispatch => {
const edus = [
{value: 1, name: 'Bachelor'},
{value: 2, name: "Master"}
]
dispatch({
type: types.GET_EDUCATIONS,
payload: edus
})
}
}
component
import React, {useEffect} from 'react';
import {connect} from 'react-redux';
import {getEducations} from '../../redux/actions/home';
function Header({educations, getEducations}) {
useEffect(() => {
getEducations(); //calling getEducations()
}, [])
useEffect(() => {
console.log(educations) //console educations after every change
})
return (
<div className="main-header">
</div>
)
}
const mapStateToProps = (state) => {
return {
educations: state.home.educations
}
}
const mapDispatchToProps = (dispatch) => {
return {
getEducations: () => { dispatch(getEducations())}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
And the education property in Header function is always an empty array, as in initialState.
While when I check on browser with Redux Devtools, it shows that the state contains those two object in array.
So no matter if I change the redux state or not, the properties of the component are going to stay as initialState.
In redux, you should avoid directly mutating the state of your reducer. Refrain from doing something like state.reducers = blah. In order for redux to know that you are trying to make an update to state, you need to return a completely new state object. Following these principles, your reducers will update correctly and your components will get the new data.
Reducer.js
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS: {
return {
...state,
educations: action.payload
};
}
default:
return state;
}
}
In the code above, we return a new state object. It will include everything from the existing state, hence ...state, and we just update the educations property with the action.payload.
Can try with the reducer written this way :
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS:
return {
...state, educations:action.payload
}
default:
return state;
}
}
It looks like you’re mutating the state in the reducer. The reducer should always return a new state object if something updated.
You could do what the answers above suggest, but i would recommend using a package like immer (https://www.npmjs.com/package/immer) or immutable.js to prevent any bugs down the line. Using the spread syntax can be dangerous if your state object has some deeply nested properties, and it’s hard to be 100% sure that you haven’t accidentally mutated something, especially as your app grows in size.
It looks like you have solved this while I was getting this typed up - I decided to post it regardless, as it may be helpful.
On top of what Christopher Ngo already mentioned, the following example outlines how you can interact with your store to create new educations and then view them, in separate components..
Cheers!
I encounter this all the time and resolved it with CLEAR then GET/SET state. This ensures a reset of the state call.
Reducers.js
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS: {
return {
...state,
educations: action.payload
};
}
case CLEAR_EDUCATIONS: {
return initialState;
}
default:
return state;
}
}
Hooks.js
...
const clearEducation = () => {
dispatch({ type: CLEAR_EDUCATION });
}
const getEducations = (payload) => {
clearEducation(); // this clearing of the state is key fire re-render
dispatch({ type: GET_EDUCATIONS, payload });
};
}
I'm having trouble understanding how the redux state assigns the state objects based on the action payload and the reducer functions. Below is my sample code. I've made notes and asked questions along the different sections, but in summary these are my questions:
Why does Option 2 below not work?
Why do I have to map my state to my competitionList prop using state.competitions and not state.items?
Any resources to get a good grasp of how react and redux connect and mapping functions work. I've already gone through the official docs and done some googling, but perhaps someone has a reference that they found easier to understand all the different options and ways of mapping state and dispatch.
My Action code:
function getAll() {
return dispatch => {
dispatch(request());
myService.getAll()
.then(
competitions => dispatch(success(competitions)),
error => dispatch(failure(error))
);
};
function request() { return { type: constants.GETALL_REQUEST } }
function success(competitions) { return {type: constants.GETALL_SUCCESS, competitions}}
function failure(error) { return {type: constants.GETALL_FAILURE, error}}
}
My reducer code:
import { constants } from '../_constants';
const initialState = {items: [], loading: false, selected: null}
export function competitions(state = initialState, action) {
switch (action.type) {
case constants.GETALL_REQUEST:
return {
loading: true
};
case constants.GETALL_SUCCESS:
console.log("the action value: ", action)
return {
items: action.competitions
};
case constants.GETALL_FAILURE:
console.log("the failed action value: ", action)
return {
error: action.error
};
default:
return state
}
}
In my component I have a mapStateToProp function which I pass to connect. The first one does not work. Why?
Option 1 - Not working
function mapStateToProps(state) {
const { selected, ...competitions } = state.competitions;
return {
competitionList: competitions,
isLoading: state.loading
};
}
export default connect(mapStateToProps)(Dashboard);
This one works, but I would like the competitionList variable to have the returned items array instead of the whole state object, so I tried to do something like this competition: state.competitions.items but it raises an error.
Option 2 - Partially working (I want to only assign the competition items)
const mapStateToProps = (state) => ({
competitionList: state.competitions,
isLoading: state.loading
});
export default connect(mapStateToProps)(Dashboard);
I cannot do:
const { competitionList } = this.props;
{competitionList.map(competition =>
<tr key={competition.competitionId}>
<td>{competition.competitionName}</td>
</tr>
)}
I have to do:
const { competitionList } = this.props;
{competitionList.items.map(competition =>
<tr key={competition.competitionId}>
<td>{competition.competitionName}</td>
</tr>
)}
I think the point that you are missing is when you combine your reducers, each one will have a key because they are objects.
In the file you combine your reducers, you probably have something like that:
import { combineReducers } from 'redux'
import todos from './todos'
import competitions from './competitions'
export default combineReducers({
todos,
competitions
})
After that, your state will look like this:
{
todos:{},
competitions:{
items: [],
loading: false,
selected: null
}
}
Explained that I think everything will be easier.
Option 1 - Not working: It is not working because you don't havecompetitions attribute inside the competitions state. Even if you have, you should not use the ... before it. If you replace the competitions for items, it is going to work, because items are inside the competitions state:
function mapStateToProps(state) {
const { selected, items } = state.competitions;
return {
competitionList: items,
isLoading: state.loading
};
}
export default connect(mapStateToProps)(Dashboard);
Or we can improve it, to make it shorter:
function mapStateToProps(state) {
const { selected, items } = state.competitions;
return {
items,
selected
isLoading: state.loading
};
}
export default connect(mapStateToProps)(Dashboard);
Doing this way, you can use this part of your code:
const { items } = this.props;
{items.map(competition =>
<tr key={competition.competitionId}>
<td>{competition.competitionName}</td>
</tr>
)}
There is another point I would like to point, Probably your isLoading variable is not working either, because you are trying to read it directly from the state, instead of from a reducer in the state.
Edited: I missed another point. Your reducer always has to return the whole state instead of just an attribute of it.
import { constants } from '../_constants';
const initialState = {items: [], loading: false, selected: null, error: null}
export function competitions(state = initialState, action) {
switch (action.type) {
case constants.GETALL_REQUEST:
/*return {
loading: true
};*/
//returning that I will overwrite your competition state with this object.
// this will keep all the competition state and will gerenate a new object changing only the loading attribute
return {
...state,
loading:true
}
case constants.GETALL_SUCCESS:
console.log("the action value: ", action)
return {
...state,
items: action.competitions
};
case constants.GETALL_FAILURE:
console.log("the failed action value: ", action)
return {
...state,
error: action.error
};
default:
return state
}
}
Let's say there is a tree in the state of my react-redux app and tree belongs to myReducer. In many cases I need this tree flattened, so I've got a selector:
const getTree = state => state.myReducer.tree;
export const getFlatNodes = createSelector(
[getTree],
(tree) => flattenTree(tree)
);
And now I need to access the flattened tree in an action
import { getFlatNodes } from 'path/to/selectors';
function myReducer(state = defaultState, action) {
switch (action.type) {
case SOME_ACTION:
const flattenedTree = getFlatNodes(state);
return {
...state,
smth: getSmthFromFlattenedTree(flattenedTree)
};
}
}
This code will not work as expected since state in the reducer is just a slice of the app state. A simple workaround I came up with is to wrap state to make the passed parameter compatible:
import { getFlatNodes as _getFlatNodes } from 'path/to/selectors';
const getFlatNodes = myReducer => _getFlatNodes({ myReducer });
The code works, although it looks quite hacky and I'm not sure if it won't cause any issues.
Does anybody have any idea why and how it can be done better or my approach is good enough already?
Maybe if you make that in your action
const someAction = () => (dispatch, getState) => ({
type: 'SOME_ACTION',
payload: getFlatNodes(getState())
// payload: getSmthFromFlattenedTree(getFlatNodes(getState()))
});
then in your reducer
function myReducer(state = defaultState, action) {
switch (action.type) {
case SOME_ACTION:
return {
...state,
smth: getSmthFromFlattenedTree(action.payload)
};
}
}
I'm quite new to Redux and from what I understand, a reducer should be created for each type of object. E.g. for user interaction a user reducer should be created. My question is: How do you handle cases where you require the object for different purposes?
Scenario: Imagine having a user reducer which returns the current user. This user would be required in the entire application and needed for general controls on every page.
Now what happens when you need to load another user which is used for different purposes. E.g. profile page: loading a user to display information.
In this case there would be a conflict if the user reducer would be used. What would be the correct way to handle this in Redux? In case a different reducer would have to be created, what would be the naming convention for the new reducer?
First, you've mentioned:
a user reducer which loads the current user
I don't know if I got you correctly, but if this means you want to fetch (from an API, for example) the current user inside the reducer, this is a wrong approach.
Reducers are intended to be pure functions. You can call them with the same arguments multiple times and they will always return the same expected state.
Side effects like that should be handled by action creators, for example:
actions/user.js
export const FETCH_ME = 'FETCH_ME'
export const FETCH_ME_SUCCESS = 'FETCH_ME_SUCCESS'
// it's using redux-thunk (withExtraArgument: api) module to make an async action creator
export const fetchMe = () => (dispatch, getState, api) => {
dispatch({ type: FETCH_ME })
return api.get('/users/me').then(({ data }) => {
dispatch({ type: FETCH_ME_SUCCESS, data })
return data
})
}
Inside your reducer you can simple get the data and set a new state (note that if you send the action with the same data multiple times, the state will always be the same).
reducers/user.js
import { FETCH_ME, FETCH_ME_SUCCESS } from '../actions/user'
const initialState = {
item: null,
loading: false
}
export const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ME:
return {
...state,
loading: true
}
case FETCH_ME_SUCCESS:
return {
...state,
loading: false,
item: action.data
}
default:
return state
}
}
Now, for your scenario:
Now what happens when you need to load another user which is used for different purposes. E.g. profile page: loading a user to display information.
You will just write another action creator for that:
actions/user.js
export const FETCH_ME = 'FETCH_ME'
export const FETCH_ME_SUCCESS = 'FETCH_ME_SUCCESS'
export const FETCH_USER = 'FETCH_USER'
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS'
export const fetchMe = () => (dispatch, getState, api) => {
dispatch({ type: FETCH_ME })
return api.get('/users/me').then(({ data }) => {
dispatch({ type: FETCH_ME_SUCCESS, data })
return data
})
}
export const fetchUser = (id) => (dispatch, getState, api) => {
dispatch({ type: FETCH_USER })
return api.get(`/users/${id}`).then(({ data }) => {
dispatch({ type: FETCH_USER_SUCCESS, data })
return data
})
}
Then you adapt your reducer to manage more sets:
reducers/user.js
import { combineReducers } from 'redux'
import { FETCH_ME, FETCH_ME_SUCCESS, FETCH_USER, FETCH_USER_SUCCESS } from '../actions/user'
const initialState = {
item: null,
loading: false
}
const meReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ME:
case FETCH_ME_SUCCESS:
return userReducer(state, action)
default:
return state
}
}
const activeReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER:
case FETCH_USER_SUCCESS:
return userReducer(state, action)
default:
return state
}
}
const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER:
case FETCH_ME:
return {
...state,
loading: true
}
case FETCH_USER_SUCCESS:
case FETCH_ME_SUCCESS:
return {
...state,
loading: false,
item: action.data
}
default:
return state
}
}
export default combineReducers({
activeUser: activeReducer,
me: meReducer
})
Your final user state should be something like:
{
me: {
item: null,
loading: false
},
active: {
item: null,
loading: false
}
}