reducers/counter.js
export type counterStateType = {
+ctr: number,
+counter: boolean
};
type actionType = {
+type: string,
+value: any
};
export default function counter(state: counterStateType = { ctr: 0, counter: true}, action: actionType) {
console.log("Reducer called with");
console.log(state);//has valid value ie { ctr: 0, counter: true}
switch (action.type) {
case TOGGLE:
state.counter = !state.counter;
return state;
case UPDATE:
state.ctr = action.value;
return state;
default:
return state;
}
}
counterPage.js
function mapStateToProps(state) {
console.log("mapStateToProps called with");
console.log(state.counter);
return {
ctr: state.counter.ctr,//<= undefined
counter: state.counter.counter
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(CounterActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
PS: The above is on router LOCATION_CHANGE
The issue was in reducer, I had forgotten about immutablity of redux states. modifying,
switch (action.type) {
case TOGGLE:
state.counter = !state.counter;
return state;
case UPDATE:
state.ctr = action.value;
return state;
default:
return state;
}
to
switch (action.type) {
case TOGGLE:
return {ctr: state.ctr, counter: !state.counter};
case UPDATE:
return {ctr: action.value, counter: state.counter};
default:
return state;
}
solved it.
Related
I am getting the [Error: Objects are not valid as a React child (found: object with keys {counter, num}). If you meant to render a collection of children, use an array instead.] error for the below code :
const initialState = {
counter: 0,
num : 0
}
const counterReducer = (state = initialState, action) => {
switch(action.type){
case "INCREMENT":
{
return {
...state,
counter: state.counter + action.payLoad
}
}
case "DECREMENT":
return {
...state,
counter: state.counter - action.payLoad
}
default:
{
return state;
}
}
}
export default counterReducer;
If I do like below everything working fine:
const counterReducer = (state = 0, action) => {
switch(action.type){
case "INCREMENT":
return state + action.payLoad;
case "DECREMENT":
return state - action.payLoad;
default:
{
return state;
}
}
}
export default counterReducer;
You probably try to render somewhere the counter? Your default case returns the entire object, instead of just state.counter.
Try it like this:
const counterReducer = (state = initialState, action) => {
switch(action.type){
case "INCREMENT":
{
return {
...state,
counter: state.counter + action.payLoad
}
}
case "DECREMENT":
return {
...state,
counter: state.counter - action.payLoad
}
default:
{
return state.counter;
}
}
}
Or in the component where you render it access the object property state.counter
There's nothing wrong with the reducer you've written.
Without the corresponding component code and based on the error you're seeing, it seems that you're using the entire state object ({ counter: 0, num: 0 }, for example) within the React component that's using the state from this reducer.
Replacing the object ({ counter: 0, num: 0 }, from the above example) with just the counter value (obj.counter) should get it working
So here is the problem, I need some modifications to always to be done after each and every action except one. So in order to achieve this I have done the following. Is there a better way to achieve this?
export const reducer = (state, action) => {
switch (action.type) {
case 'AddCheckboxJson': {
return ReducerWrapper({ ...state,objCheckBox:action.payload });
}
case 'insertAt':return ReducerWrapper({
...state,objCheckBox:{...state.objCheckBox,
Values:insertAt(action.payload.index,action.payload.entry,"iDisplayOrder",state.objCheckBox)
}
})
case 'INSERT_ABOVE':
return ReducerWrapper({
...state, objRadio: {
...state.objRadio,
Values: action.payload
}
})
case 'INSERT_BELOVE':
return ReducerWrapper({
...state, objRadio: {
...state.objRadio,
Values: action.payload
}
}),
case 'REPLACE':return { ...action.payload }
default: {
return state;
}
}
};
Here the function ReducerWrapper does the common modification
export const ReducerWrapper = State => {
return {...State,
//modifications done here
}
}
This is really a lot simpler than it probably seems, just return for the two scenarios you don't want to apply the additional state i.e.
export const reducer = (state, action) => {
switch (action.type) {
case 'AddCheckboxJson': {
state = {
...state,
objCheckBox: action.payload
};
}
case 'insertAt': {
state = {
...state,
objCheckBox:{
...state.objCheckBox,
Values: insertAt(
action.payload.index,
action.payload.entry,
"iDisplayOrder",
state.objCheckBox
)
}
}
}
case 'INSERT_ABOVE': {
state = {
...state,
objRadio: {
...state.objRadio,
Values: action.payload
}
}
}
// presumably this should be INSERT_BELOW?
case 'INSERT_BELOVE': {
state = {
...state,
objRadio: {
...state.objRadio,
Values: action.payload
}
}
},
// return the state early in both these scenarios
case 'REPLACE': return { ...action.payload }
default: return state;
}
// run additional state change
return ReducerWrapper(state);
};
I want to update the state of activitiesData when ACTIVITIES_SEND_SUCCESS is executed by appending the new data to the end of the object activitiesData.
ActivitiesReducer.js
import {
ACTIVITIES_FETCH_SUCCESS,
ACTIVITIES_SEND_SUCCESS,
SUBACTIVITY_SEND_SUCCESS
} from '../actions/types';
const INITIAL_STATE = { activitiesData: {}, activityCreated: {}, listActivity: {} };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case ACTIVITIES_FETCH_SUCCESS:
return { ...state, activitiesData: action.payload };
case ACTIVITIES_SEND_SUCCESS:
return { ...state, activityCreated: action.payload };
case SUBACTIVITY_SEND_SUCCESS:
return { ...state, listActivity: action.payload };
default:
return state;
}
};
You can append the data using spread operator syntax like
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case ACTIVITIES_FETCH_SUCCESS:
return { ...state, activitiesData: action.payload };
case ACTIVITIES_SEND_SUCCESS:
return { ...state, activityCreated: action.payload, activitiesData: {...state.activitesData, ...action.payload } };
case SUBACTIVITY_SEND_SUCCESS:
return { ...state, listActivity: action.payload};
default:
return state;
}
};
When triggering an action updateLog, it seems it resets other state items. In my case updateLog should manipulate log and that works just fine. The thing is it also resets tasks to the default values. What am I doing wrong here?
Component:
class Generator extends Component {
render() {
return (
<div className="generator">
<Inputs />
<button onClick={this.generate.bind(this)}>Go!</button>
<Log />
</div>
);
}
generate() {
this.props.updateLog("ANYTHING!");
}
}
function mapStateToProps(state) {
return {
tasks: state.tasks
};
}
function matchDispatchToProps(dispatch){
return bindActionCreators({updateLog: updateLog}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(Generator);
Action:
export const updateLog = (message) => {
return {
type: 'LOG_UPDATED',
payload: message
}
};
Logreducer:
const initialLog = "";
export default function (state = initialLog, action) {
switch (action.type) {
case 'LOG_UPDATED':
return state + "\n" + action.payload
break;
}
return state;
}
All reducers:
const allReducers = combineReducers({
tasks: taskReducer,
log: logReducer
});
export default allReducers
taskReducer:
export default function (state = null, action) {
switch (action.type) {
case 'TASK_UPDATED':
var tasks = Object.assign({}, action.payload);
return tasks;
break;
}
// Default task properties
return {
CreateDatabaseTask: {
enabled: false,
type: "sqlite"
}
}
}
The problem lies in your task reducer. If the action type matches none of the ones defined in the switch statement, you should return the current state. Instead, you are returning the initial state.
Try changing it to return the current state instead:
const initialState = {
CreateDatabaseTask: {
enabled: false,
type: "sqlite"
}
}
export default function (state = initialState, action) {
switch (action.type) {
case 'TASK_UPDATED':
var tasks = Object.assign({}, action.payload);
return tasks;
break;
default:
return state;
}
}
I'm using React and Redux to try to increase the value of a prop everytime a button is clicked. However the prop is being treated as a NaN for some reason.
This is my reducer:
export default function(){
return 0;
}
And the other reducer:
export default function (state = null, action) {
switch (action.type) {
case 'VALUE_INCREMENTED':
return action.payload;
break;
}
return state;
}
This is my action:
export const incrementValue = (val) =>{
console.log("You incrementend the value to: ", val+1);
return {
type: 'VALUE_INCREMENTED',
payload: val+1
}
};
And this is is the container:
class Thermo extends Component{
getValue(){
return this.props.val;
}
render(){
return(
<div>
<Thermometer
min={0}
max={90}
width={10}
height={230}
backgroundColor={'gray'}
fillColor={'pink'}
current={this.props.val}
/>
<input type = "submit" onClick={() => this.props.incrementValue(this.props.val)}value="+"/>
</div>
)
}
}
function mapStateToProps(state){
return{
val: state.val
};
}
function matchDispatchToProps(dispatch){
return bindActionCreators({incrementValue: incrementValue}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(Thermo);
Here you are setting initial state to null;
export default function (state = null, action) {
switch (action.type) {
case 'VALUE_INCREMENTED':
return action.payload;
break;
}
return state;
}
if you change your reducer to the following you will then have a default state object with a key for val and value of 0. Then when you map state to props it will get the correct value and not undefined.
var initialState = {
val: 0
}
export default function (state = initialState, action) {
switch (action.type) {
case 'VALUE_INCREMENTED':
return {...state, val: action.payload};
break;
}
return state;
}
You need an initial state in your reducer e.g.
export default function(state=0){
return state;
}