I have a redux app that, amongst other things, updates a single string, hence the state could be reflected as:
{
theDataString: "someString",
otherData: { ...some other data... },
someListItems: [ ...a data array... ]
}
Hence I have the following reducer:
function updateDataString(state = {}, action) {
switch (action.type) {
case UPDATE_DATA_STRING:
return Object.assign({}, ...state, {theDataString: action.theDataString});
default:
return state;
}
}
However, when dispatch the UPDATE_DATA_STRING action, the theDataString value in the state becomes double nested:
{
theDataString: {theDataString: "someString"},
otherData: {... some other data... },
someListItems: [ ... a data array ... ]
}
This same problem has already been encountered here. However, the solution for them was that they were calling combineReducers on a single reducer when that was unnecessary. In my case, I'm calling combineReducers on multiple reducers - so their answer doesn't work for me. Also note that the same nesting problem does not occur for other data, only the top-level string gets double nested.
What is going wrong here?
EDIT:
I'm connecting the component that updates theDataString like this:
import {connect} from 'react-redux';
import {updateDataString} from './actions/actions';
import SomeList from './components/someList';
const mapStateToProps = (state) => {
return {someListItems: state.someListItems}
};
const mapDispatchToProps = (dispatch, placeHolder) => {
return {
updateDataString: (aString) => dispatch(updateDataSting(aString))
}
};
export default SomeListConnected = connect(mapStateToProps, mapDispatchToProps)(SomeList)
The action is set up as follows:
export const UPDATE_DATA_STRING = 'UPDATE_DATA_STRING';
export function updateDataString(aString) {
return {type: UPDATE_DATA_STRING, theDataString: aString}
}
EDIT2: Changing the way the reducer updates the state is a natural place to look for answers. However I have tried various permutations here with little effect:
Object.assign({}, ...state, {theDataString: action.theDataString});
Object.assign({}, state, {theDataString: action.theDataString});
{...state, {theDataString: action.theDataString}};
None of the above fix the problem.
Assuming that you've used combineReducers, your updateDataString reducer should be treating the state as a string, not an object:
function updateDataString(state = "", action) {
switch(action.type) {
case UPDATE_DATA_STRING: return action.newString;
default; return state;
}
}
The slice reducer will only see the string value as its "state", so you need to treat it that way.
See Using combineReducers for some further information on the topic.
Try this
return { ...state, theDataString: action.newString};
instead of this
return Object.assign({}, ...state, {theDataString: action.newString});
in your reducer.
I think you are using ... spread operator in the wrong way. And no need to use Object.assign() here.
Read more about spread operator here
Related
I have a general question about Reducers. In my application, I have many users (child, parent, and teacher). I have created a reducer to load tasks from the database for the parent/teacher and in my initial state I have an array "Tasks".
Do I have to create another reducer to load tasks of children from the database and I have to, can I use the same array name "Tasks"?
TasksReducer.js:
const initialState = {
tasks: [],
image: null
}
const tasks = (state = initialState, action) => {
switch (action.type) {
case 'LOAD_TASKS_FROM_SERVER':
return {
...state,
tasks: action.payload
};
case 'ADD_TASK':
return {
...state,
tasks: [action.payload, ...state.tasks]
}
default:
return state;
}
};
TasksChildReducer.js : (I only have in common the "LOAD_TASKS_FROM_SERVER")
const initialState = {
tasksChildren: [],
}
I think that the easiest way is to have different reducers mounted each in a different key of the root redux state.
So their own keys can be named the same, and you will access them like so
const getChildrenTasks = state => state.children.tasks
const getParentTasks = state => state.parent.tasks
etc.
To avoid code duplication, you could also abstract some of the reducer operation in common functions.
I'm new to Redux and having a bit of trouble with it, so what I've done so far might very well be wrong, but bear with me. Overall, I want a very simple redux state for my app, with only two keys, "user" and "landing". The way I have everything set up is defaulting the state to "user: {user: null}", and "landing: {landing: true}". I'm wondering why it's duplicated like that, and it's not just "user: null" and "landing: true"?
My reducers:
const initialUserState = {
user: null
};
const initialUiState = {
landing: true
};
export function userReducer(state, action){
if (typeof state === "undefined"){
return initialUserState;
}
switch(action.type){
case SIGN_IN:
return {...state, user: action.userName};
case SIGN_OUT:
return {...state, user: null};
default:
return state;
}
}
export function uiReducer(state, action){
if (typeof state === "undefined"){
return initialUiState;
}
switch(action.type){
case LANDING:
return {...state, landing: true};
case NOT_LANDING:
return {...state, landing: false};
default:
return state;
}
}
and the combination of the reducers:
import {userReducer, uiReducer} from "./reducers";
import {combineReducers} from "redux";
export default combineReducers({
user: userReducer,
landing: uiReducer
});
Again, I'm hoping to see just "user: null" and "landing: true", but I'm console logging out the state in a component that's wrapped in connect and I'm seeing "user: {user: null}" and "landing: {landing: true}".
The first hierarchy level in the state is defined by the name of the reducers in the call to combineReducers. This is necessary to separate the sub-state that each single reducer is responsible for. The structure of the state inside the first hierarchy level is then completely defined by the corresponding reducers.
In your case, if you really want to manage only a single value, you could define your reducers as follows:
const initialUserState = null;
export function userReducer(state, action){
if (typeof state === "undefined"){
return initialUserState;
}
switch(action.type){
case SIGN_IN:
return action.userName;
case SIGN_OUT:
return null;
default:
return state;
}
}
And similarly for the uiReducer.
But this would be rather uncommon and won't scale well if you need to add further attributes in the future.
Using redux, I have a bunch of actions and a bunch of reducers that coincide with each of the action types.
Each of the actions map to a different part of the state being updated (all the action creators are primarily for fetching data from an API, for example, which maps to some part of the state). Our reducers currently look rather silly (simplified to A, B, C, for the sake of example):
export const rootReducer = combineReducers({
somePartOfStateA: someAReducer,
somePartOfStateB: someBReducer,
somePartOfStateC: someCReducer,
... (and many other reducers)
});
function someAReducer(state = [], action) {
switch (action.type) {
case FETCH_SOME_A:
return action.payload;
default:
return state;
}
}
function someBReducer(state = [], action) {
switch (action.type) {
case FETCH_SOME_B:
return action.payload;
default:
return state;
}
}
function someCReducer(state = [], action) {
switch (action.type) {
case FETCH_SOME_C:
return action.payload;
default:
return state;
}
}
// and a bunch of pretty much identical reducers continue below
From my understanding, the purpose of having them split is so that each reducer handles a part of the state's namespaces. The resulting reducers are simple, but pretty much the same thing over and over. Is there a recommended way to consolidate all of these reducers per piece of state?
Reducer is just a function. You could use higher order function to make reducer for you.
const makeSingleActionReducer = type => (state, action) =>
action.type === type ? action.payload : state
export const rootReducer = combineReducers({
somePartOfStateA: makeSingleActionReducer(FETCH_SOME_B)
...
})
Also you could go further by creating a config {stateKey: actionType, ...} and loop over it.
I have the following two #ngrx/store reducers:
import {ActionReducer, Action} from '#ngrx/store';
import {UserAccount} from '../shared/models/useraccount.model';
export const SET_CURRENT_USER_ACCOUNT = 'SET_CURRENT_USER_ACCOUNT';
export const UPDATE_CURRENT_USER_ACCOUNT_FIRST_NAME = 'UPDATE_CURRENT_USER_ACCOUNT_FIRST_NAME';
export const currentUserAccountReducer: ActionReducer<UserAccount> = (state: UserAccount, action: Action) => {
console.log('currentUserAccountReducer:', state, action);
switch (action.type) {
case SET_CURRENT_USER_ACCOUNT: {
return action.payload;
}
case UPDATE_CURRENT_USER_ACCOUNT_FIRST_NAME: {
state.firstName = action.payload;
return state;
}
}
};
export const SET_AUTHENTICATED = 'SET_AUTHENTICATED';
export const SET_UNAUTHENTICATED = 'SET_UNAUTHENTICATED';
export const authenticatedReducer: ActionReducer<boolean> = (state: boolean, action: Action) => {
console.log('authenticatedReducer:', state, action);
switch (action.type) {
case SET_AUTHENTICATED: {
return true;
}
case SET_UNAUTHENTICATED: {
return false;
}
}
};
However, for some reason when I issue a dispatch for the 1st reducer (i.e. currentUserAccountReducer) then it changes the state for the 2rd reducer (i.e. authenticatedReducer)...
Here is the dispatch causing this issue:
this.store.dispatch({type: SET_CURRENT_USER_ACCOUNT, payload: currentUserAccount});
Here is how I initialize the store in the imports section:
StoreModule.provideStore(
{
currentUserAccount: currentUserAccountReducer,
authenticated: authenticatedReducer
})
Can someone please provide advice?
edit: The issue is that authenticated ends up undefined!!
The switch statements in your reducers do not contain default cases. You need to add default cases that return the state, as the reducers will be called for all actions - the store has no way of knowing which reducer should be called for a particular action type, so each dispatched action is passed to every reducer.
Recently I've been looking into react and redux. I read up the official documentation and tried some ToDo List tutorials. Part 1 is just about react and this is part 2 about redux:
http://www.theodo.fr/blog/2016/03/getting-started-with-react-redux-and-immutable-a-test-driven-tutorial-part-2/
So basically he just sets up a store and initially adds an array of a few todos. Now I don't want my data to be local and I want to fetch it from an API. I'm having a hard time understanding how this actually works. So the code I would use in my action_creators.js is:
export function fetchData() {
return dispatch => {
fetch('http://127.0.0.1:8000/example')
.then(res => res.json())
.then(res => dispatch({
type: FETCH_DATA,
data: res
}))
}
}
Now in the example code for example adding a 'todo':
export function addItem(text) {
return {
type: 'ADD_ITEM',
text
}
}
You aren't dispatching anything, the tutorial does this in the reducer? But when you return dispatch your fetch, does this automatically get dispatched to your store?
If so I have no clue what I should write in my reducer ..
This is the code I have for adding a 'todo':
import {Map} from 'immutable';
function setState(state, newState) {
return state.merge(newState);
}
function fetchData(state) {
return state;
}
function addItem(state, text) {
const itemId = state.get('hostnames').reduce((maxId, item) => Math.max(maxId,item.get('id')), 0) + 1;
const newItem = Map({id: itemId, text: text, status: 'active'});
return state.update('hostnames', (hostnames) => hostnames.push(newItem));
}
export default function(state = Map(), action) {
switch (action.type) {
case 'SET_STATE':
return setState(state, action.state);
case 'ADD_ITEM':
return addItem(state, action.text);
case 'FETCH_DATA':
return fetchData(state);
}
return state;
}
So basically my question is, how do I fetch the data ( if the fetch is wrong now ) and how do I add the fetched data from my api to the store in my reducer.
I just find react and redux pretty complicated so sorry if I'm asking a really noob question or just making big mistakes in the way I want to do something.
Thanks in advance for any help.
imagine your json
{
"data": {
"apple": 1,
"banana": 3,
},
"status": 200,
}
your actions
export function fetchData() {
return dispatch => {
fetch('http://127.0.0.1:8000/example')
.then(res => res.json())
.then((responseData) => {
if(responseData.status === 200){
dispatch(setData(responseData));
}
})
}
}
export function setData(responseData) {
return {type: SET_DATA, data: responseData.data }
}
your reducer
const initialState = { data: null };
export default function(state = initialState, action) {
switch (action.type) {
case 'SET_DATA':
return Object.assign({}, state, {
data: action.data,
})
default:
return state;
}
}
then your state will become
{ data: {
apple: 1,
banana: 3,
}
}
Actually, all your reducers should be pretty dumb and pure (without any side effects). So their only concern is to modify the state and nothing else. Fetching data from the server or any kind of orchestration should be implemented in redux middleware. Look at redux-thunk or redux-saga if you need something more complicated. Hope that helps.