Redux Dataflow from Reducer to Container - redux

As i am newly to redux, here is a critical question about dataflow in redux
As i understand, i have created a component as CountN:
import React from 'react'
import styles from '../../features/counter/Counter.module.css'
const CountN = (props) => {
const {countValue,actions} = props;
return (
<div>
<div className={styles.row}>
<button
className={styles.button}
aria-label="Increment value"
onClick={actions.increment}
>
+
</button>
<span className={styles.value}>{ countValue }</span>
<button
className={styles.button}
aria-label="Decrement value"
onClick={actions.decrement}
>
-
</button>
</div>
</div>
)
}
export default CountN
Then i use container to pass data to CountN
Container below:
import React from 'react';
import CountN from "../../components/countN"
import { connect } from 'react-redux'
import * as CountActions from '../../actions'
import { bindActionCreators } from 'redux';
const mapStateToProps = (state) =>({
countValue: state.value
})
const mapDispatchToProps = (dispatch) =>({
actions: bindActionCreators(CountActions,dispatch)
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(CountN)
And in order to manage states, i create the Reducer to set States:
Reducer below:
import * as types from '../constants/CountTypes';
const initialState = [{
value: 0,
payload: 0,
}]
const counter = (state=initialState,action)=> {
switch (action.type){
case types.Increment:
return [{
value: state.value + 1,
payload: 0,
}]
case types.Decrement:
return [
...state,
{
value: state.value - 1
}
]
case types.IncrementByAmount:
return [{
value: state.value + action.payload ,
payload: action.payload
}
]
default:
return state
}
};
export default counter;
Plus, i create a store with "CreateStore(reducer)" to store data,
Now the problem is that i get an error:
TypeError: Cannot read property 'increment' of undefined
Which i understand that the state is not defined,
Could some expert help me to figure out which part is wrong, why the data haven't been passed to Container via "props"???
Many thanks

The code you have should work but I did make some changes to the state, you defined it as an array but I don't see a reason why so I changed it to an object. Your mapStateToProps doesn't consider the state to be an array so that may have been a mistake. See comments in code below where I made changes.
const { Provider, connect } = ReactRedux;
const {
createStore,
applyMiddleware,
compose,
bindActionCreators,
} = Redux;
//I changed initialState to an object instead of an array
const initialState = {
value: 0,
payload: 0,
};
//action types
const types = {
Increment: 'Increment',
Decrement: 'Decrement',
IncrementByAmount: 'IncrementByAmount',
};
//action creators
const CountActions = {
increment: () => ({ type: types.Increment }),
decrement: () => ({ type: types.Decrement }),
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case types.Increment:
//changed to object
return {
value: state.value + 1,
payload: 0,
};
case types.Decrement:
//changed to object
return {
...state,
value: state.value - 1,
};
case types.IncrementByAmount:
//changed to object
return {
value: state.value + action.payload,
payload: action.payload,
};
default:
return state;
}
};
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
const CountN = (props) => {
const { countValue, actions } = props;
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={actions.increment}
>
+
</button>
<span>{countValue}</span>
<button
aria-label="Decrement value"
onClick={actions.decrement}
>
-
</button>
</div>
</div>
);
};
const mapStateToProps = (state) => ({
countValue: state.value,
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(CountActions, dispatch),
});
const App = connect(
mapStateToProps,
mapDispatchToProps
)(CountN);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>

Related

next-redux-wrapper HYDRATION failed

I am trying to integrate next-redux-wrapper to Next.js RTK project. When invoking async action from getServerSideProps, I am getting state mismatch error (see the image below).
When I dispatch action from client side (increment/decrement), everything works well. I think issue is related to HYDRATION but so far all my efforts have failed.
I tried mapping redux state to props, storing props in component state, added if statements to check values but nothing seem to work. I've been stuck on this for 2 weeks. I'm not sure what else to try next.
"next": "12.3.1",
"next-redux-wrapper": "^8.0.0",
"react":
"18.2.0",
"react-redux": "^8.0.4"
store/store.js
import { configureStore, combineReducers } from "#reduxjs/toolkit";
import counterReducer from "./slices/counterSlice";
import { createWrapper, HYDRATE } from "next-redux-wrapper";
const combinedReducer = combineReducers({
counter: counterReducer,
});
const reducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
return nextState;
} else {
return combinedReducer(state, action);
}
};
export const makeStore = () =>
configureStore({
reducer,
});
export const wrapper = createWrapper(makeStore, { debug: true });
store/slices/counterSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const initialState = {
value: 0,
data: { quote: "" },
pending: false,
error: false,
};
export const getKanyeQuote = createAsyncThunk(
"counter/kanyeQuote",
async () => {
const respons = await axios.get("https://api.kanye.rest/");
return respons.data;
}
);
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(getKanyeQuote.pending, (state) => {
state.pending = true;
})
.addCase(getKanyeQuote.fulfilled, (state, { payload }) => {
state.pending = false;
state.data = payload;
})
.addCase(getKanyeQuote.rejected, (state) => {
state.pending = false;
state.error = true;
});
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
pages/index.js
import React, { useState } from "react";
import { useSelector, useDispatch, connect } from "react-redux";
import {
decrement,
increment,
getKanyeQuote,
} from "../store/slices/counterSlice";
import { wrapper } from "../store/store";
function Home({ data }) {
const count = useSelector((state) => state.counter.value);
// const { data, pending, error } = useSelector((state) => state.counter);
const dispatch = useDispatch();
const [quote, setQuote] = useState(data.quote);
return (
<div style={{ display: "flex", flexDirection: "column" }}>
{/* <span>{pending && <p>Loading...</p>}</span>
<span>{error && <p>Oops, something went wrong</p>}</span> */}
<div>{quote}</div>
<span>Count: {count}</span>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
);
}
export const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ({ req, res, ...etc }) => {
console.log(
"2. Page.getServerSideProps uses the store to dispatch things"
);
await store.dispatch(getKanyeQuote());
}
);
function mapStateToProps(state) {
return {
data: state.counter.data,
};
}
export default connect(mapStateToProps)(Home);
Errors in console
This might stem from a known issue where next-redux-wrapper 8 hydrates too late. Please try downgrading to version 7 for now and see if that resolves the problem.

Refactoring with createSlice reduxtoolkit

I'm having trouble refactoring with createSlice, I'm a beginner with redux-toolkit and have looked through the documentation but still having problems.if someone could point me in the right direction that would be fantastic. This is the working code
const SET_ALERT = 'setAlert';
const REMOVE_ALERT = 'alertRemoved';
export const setAlert =
(msg, alertType, timeout = 5000) =>
(dispatch) => {
const id = nanoid();
dispatch({
type: SET_ALERT,
payload: { msg, alertType, id },
});
setTimeout(() => dispatch({ type: REMOVE_ALERT, payload: id }), timeout);
};
const initialState = [];
export default function alertReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_ALERT:
return [...state, payload];
case REMOVE_ALERT:
return state.filter((alert) => alert.id !== payload);
default:
return state;
}
}
Your current setAlert action creator creates a thunk action (an action which takes dispatch as an argument) so it cannot be an action creator that is automatically generated by createSlice.
createSlice
You can keep the setup very similar to what you have now. You would have two separate actions for setting and removing an alert and a thunk for dispatching both. The underlying basic actions can be created with createSlice.
import { createSlice, nanoid } from "#reduxjs/toolkit";
const slice = createSlice({
name: "alerts",
initialState: [],
reducers: {
addAlert: (state, action) => {
// modify the draft state and return nothing
state.push(action.payload);
},
removeAlert: (state, action) => {
// replace the entire slice state
return state.filter((alert) => alert.id !== action.payload);
}
}
});
const { addAlert, removeAlert } = slice.actions;
export default slice.reducer;
export const setAlert = (msg, alertType, timeout = 5000) =>
(dispatch) => {
const id = nanoid();
dispatch(addAlert({ msg, alertType, id }));
setTimeout(() => dispatch(removeAlert(id)), timeout);
};
CodeSandbox
createAsyncThunk
This next section is totally unnecessary and overly "tricky".
We can make use of createAsyncThunk if we consider opening the alert as the 'pending' action and dismissing the alert as the 'fulfilled' action. It only gets a single argument, so you would need to pass the msg, alertType, and timeout as properties of an object. You can use the unique id of the thunk which is action.meta.requestId rather than creating your own id. You can also access the arguments of the action via action.meta.arg.
You can still use createSlice if you want, though there's no advantage over createReducer unless you have other actions. You would respond to both of the thunk actions using the extraReducers property rather than reducers.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
export const handleAlert = createAsyncThunk( "alert/set", (arg) => {
const { timeout = 5000 } = arg;
return new Promise((resolve) => {
setTimeout(() => resolve(), timeout);
});
});
export default createReducer(initialState, (builder) =>
builder
.addCase(handleAlert.pending, (state, action) => {
const { alertType, msg } = action.meta.arg;
const id = action.meta.requestId;
// modify the draft state and don't return anything
state.push({ alertType, msg, id });
})
.addCase(handleAlert.fulfilled, (state, action) => {
const id = action.meta.requestId;
// we are replacing the entire state, so we return the new value
return state.filter((alert) => alert.id !== id);
})
);
example component
import { handleAlert } from "../store/slice";
import { useSelector, useDispatch } from "../store";
export const App = () => {
const alerts = useSelector((state) => state.alerts);
const dispatch = useDispatch();
return (
<div>
{alerts.map((alert) => (
<div key={alert.id}>
<strong>{alert.alertType}</strong>
<span>{alert.msg}</span>
</div>
))}
<div>
<button
onClick={() =>
dispatch(
handleAlert({
alertType: "success",
msg: "action was completed successfully",
timeout: 2000
})
)
}
>
Success
</button>
<button
onClick={() =>
dispatch(
handleAlert({
alertType: "warning",
msg: "action not permitted"
})
)
}
>
Warning
</button>
</div>
</div>
);
};
export default App;
CodeSandbox

Redux accessing state in another component

I'm having trouble accessing state in my components. I have one component (Add Page) where a user adds a 'name' and a 'weight'. What I would like to happen is for the 'name' and 'weight' that were added to be displayed on another component (Home Page) when the user clicks submit. When I console log the state in my home page, I get undefined. My DevTools shows that the state is updating with the added name and weight, but I can't figure out how to access it.
Here are my actions:
export const getMovements = (name) => {
return {
type: constants.GET_MOVEMENTS,
name,
}
};
export const addMovement = (name, weight) => {
history.push('/')
return {
type: constants.ADD_MOVEMENT,
name,
weight,
}
};
Here are my reducers:
const initialState = {
name: [],
weight: [],
};
const addMovementReducer = (state = initialState , action) => {
switch (action.type) {
case ADD_MOVEMENT:
return { ...state, name: action.name, weight: action.weight }
default:
return state;
}
};
const getMovementsReducer = (state = {}, action) => {
switch (action.type) {
case GET_MOVEMENTS:
return { ...state, name: action.name, weight: action.weight }
default:
return state;
}
};
Here is my Add Page component:
const AddPage = () => {
const [name, setName] = useState('');
const [weight, setWeight] = useState(0);
const classes = useStyles();
const dispatch = useDispatch();
console.log(name, weight);
return (
<div>
<Header title="Add Page" />
<div className={classes.addPage}>
<div className={classes.addMovementDiv}>
<TextField
className={classes.movementName}
key="name"
label="Enter Movement Name"
InputProps= {{className: "textBoxColor"}}
variant="outlined"
onChange={event => {
const { value } = event.target;
setName(value);
}}
/>
<TextField
className={classes.movementWeight}
key="weight"
label="Enter Movement Weight"
type="number"
variant="outlined"
onChange={event => {
const { value } = event.target;
setWeight(value);
}}
InputProps= {{endAdornment: <InputAdornment position="end">lb</InputAdornment>, className: "textBoxColor"}} />
<Button
className={classes.addButton}
variant="outlined"
onClick={() => dispatch(addMovement(name, weight))}
>
<AddCircleIcon />
</Button>
</div>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.name,
weight: state.weight,
}
};
const mapDispatchToProps = (dispatch) => {
return({
addMovement: (name, weight) => dispatch(addMovement(name, weight))
})
};
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
export default compose(withConnect)(AddPage);
Here is my Home Page component:
const HomePage = (props) => {
const classes = useStyles();
const newMovements = props.name;
return (
<div>
<Header title={"Home Page" }/>
{newMovements}
<div className={classes.fabDiv}>
<Fab
className={classes.fab}
onClick={() => history.push(`/add`)}>
<AddIcon />
</Fab>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.name
}
};
const mapDispatchToProps = (dispatch) => {
return({
getMovements: (name) => dispatch(getMovements(name))
})
};
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
export default compose(withConnect)(HomePage);
Any help would be appreciated!
instead of
const [name, setName] = useState('');
const [weight, setWeight] = useState(0);
which is non-Redux, component-local state, you would have to use your connected props.name and props.weight.
Since you are using function components and hooks, you can also use the react-redux hooks useSelector and useDispatch which make this a lot easier than using connect.
So skip all the connect, mapStateToProps and props.name and just do
const name = useSelector(state => state.name)
const weight = useSelector(state => state.weight)

Redux-reducer not getting called

I'm trying to learn Redux. I'm trying a test app and I'm stuck on this error and have no idea why the reducer isn't updating the state. I've looked at the common problems that cause this and can't seem to find those errors in mine, my reducer doesn't change the state
logItem.js
import React from 'react';
import {Link} from 'react-router-dom';
import {connect} from 'react-redux';
import {addCurrent} from '../../actions/logAction';
const LogItem = (props) => {
const {message, tech, date, attention} = props.singleLog;
console.log(props.currentLog)
const updateLog = (singleLog) => {
updateLog(singleLog, singleLog._id)
}
const current = (singleLog) => {
console.log(singleLog)
addCurrent(singleLog)
}
return (
<div>
<h3>{message}</h3>
<p className={`text ${attention}`}>was last updated by {tech} on {date}</p>
<Link onClick={() => current(props.singleLog)} to='/addLog'>update</Link>
<button>delete</button>
</div>
)
}
const mapStateToProps = (state) => ({
currentLog : state.log.current
})
export default connect(mapStateToProps, {addCurrent})(LogItem);
logaction.js
export const addCurrent = (singleLog) => {
console.log(singleLog)
return({type: SET_CURRENT, payload: singleLog})
}
import { SET_LOADING, GET_LOGS, LOGS_ERROR, ADD_LOG, UPDATE_LOG, SET_CURRENT } from '../actions/types';
const initialState = {
logs: null,
loading: true,
current: null,
errors: null
}
logReducer.js
import { SET_LOADING, GET_LOGS, LOGS_ERROR, ADD_LOG, UPDATE_LOG, SET_CURRENT } from '../actions/types';
const initialState = {
logs: null,
loading: true,
current: null,
errors: null
}
export default (state = initialState, action) => {
console.log(action.type)
switch(action.type) {
case SET_CURRENT:
console.log("5")
console.log(action.payload)
return {
...state,
current: action.payload,
errors:null
}
default:
return state;
}
}
Your action does not get dispatched, not sure why you claim the reducer doesn't do anything when obviously the action isn't even getting dispatched. Please use redux devtools next time so you at least know what's going on and can articulate a better question.
You should replace addCurrent(singleLog) with props.addCurrent(singleLog)
Try to replace
export const addCurrent = (singleLog) => {
console.log(singleLog)
return({type: SET_CURRENT, payload: singleLog})
}
with
export const addCurrent = (singleLog) => dispatch => {
console.log(singleLog)
dispatch({type: SET_CURRENT, payload: singleLog})
}

Redux is not getting any data

I'm new to Redux. It's really confusing to understand basic syntax. None of the bugs are found so It's hard to figure out what's wrong with my code.
It worked well last week, I don't remember what I have changed.
//child component
import React, { Component } from 'react';
import { SingleDatePicker } from 'react-dates';
import moment from 'moment';
class InputForms extends Component {
state = {
inputs: ['input-0'],
title: '',
tag: '',
createdAt: moment(),
imageLinks: [''],
calendarFocused: false,
error: '',
}
appendInput(e) {
const newInput = `input-${this.state.inputs.length}`;
this.setState({ inputs: this.state.inputs.concat([newInput]) });
}
onTitleChange = (e) => {
const title = e.target.value;
this.setState(() => ({ title }));
};
onTagChange = (e) => {
const tag = e.target.value;
this.setState(() => ({ tag }));
};
onImageLinkChange = (e) => {
const imageLinks = e.target.value;
this.setState(() => ({ imageLinks: this.state.imageLinks.concat([imageLinks]) }));
};
onDateChange = (createdAt) => {
if (createdAt) {
this.setState(() => ({ createdAt }));
}
};
onFocusChange = ({ focused }) => {
this.setState(() => ({ calendarFocused: focused }));
};
onSubmit = (e) => {
e.preventDefault();
if (!this.state.title || !this.state.imageLinks) {
this.setState(() => ({ error: '제목과 이미지링크를 입력해주세요' }));
} else {
this.setState(() => ({ error: '' }));
this.props.onSubmit({
title: this.state.title,
tag: this.state.tag,
createdAt: this.state.createdAt.valueOf(),
imageLinks: this.state.imageLinks,
});
}
}
render() {
return (
<div>
<form onSubmit={this.onSubmit}>
<input
type="text"
placeholder="제목을 입력하세요"
required
value={this.state.title}
onChange={this.onTitleChange}
/>
<input
type="text"
placeholder="태그를 입력하세요"
value={this.state.tag}
onChange={this.onTagChange}
/>
<SingleDatePicker
date={this.state.createdAt}
onDateChange={this.onDateChange}
focused={this.state.calendarFocused}
onFocusChange={this.onFocusChange}
numberOfMonths={1}
isOutsideRange={() => false}
/>
{this.state.inputs.map((input, key) => {
return <input
key={input}
type="text"
required
value={this.state.imageLinks}
onChange={this.onImageLinkChange}
placeholder={`이미지링크 ${key + 1}`}
/>
})}
<button>저장</button>
</form>
</div>
)
}
}
export default InputForms;
//parent component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import configureStore from '../store/configureStore';
import InputForms from './InputForms';
import { addPost } from '../actions/posts';
const store = configureStore();
class CreatePost extends Component {
onSubmit = (post) => {
this.props.addPost(post);
this.props.history.push('/');
};
render(){
return(
<div>
<InputForms onSubmit={this.onSubmit}/>
</div>
)
}
}
const mapDispatchToProps = (dispatch, props) => ({
addPost: (post) => dispatch(addPost(post))
});
export default connect(undefined, mapDispatchToProps)(CreatePost);
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import registerServiceWorker from './registerServiceWorker';
import AppRouter from './routers/AppRouter';
import 'normalize.css/normalize.css';
import './style/style.css';
import 'react-dates/lib/css/_datepicker.css';
import 'react-dates/initialize';
const store = configureStore();
const jsx = (
<Provider store={store}>
<AppRouter />
</Provider>
);
ReactDOM.render(jsx, document.getElementById('root'));
registerServiceWorker();
//action
import database from '../firebase/firebase';
//Add Posts
export const addPost = (post) => ({
type: 'ADD_POST',
post
});
//reducer
const postReducerDefaultState = [];
export default (state = postReducerDefaultState, action) => {
switch (action.type) {
case 'ADD_POST':
return [
...state,
action.post
];
default:
return state;
}
};
In your reducer, you return as below
return [ ...state, action.post];
Reducer doesnt return array, but instead returning objects. Secondly, action.post is a value, you need to assign this to key, something like below:
return { ...state, post: action.post };

Resources