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)
Related
I am using react redux-thunk. I have a set of users data that I get from an API and this is the schema:
.
I've connected the "active" property with the checked attribute of a Switch MUI button, so naturally when calling the API I have some users with their switch button already on "true". What I am trying to do is to just make the switch functional, and just be able to click it and change its state, not necessarily doing anything with that.
Here's my toggleType.js:
export const TOGGLE = "TOGGLE";
Here's my toggleAction.js:
import { TOGGLE } from "./toggleType";
const statusToggleAction = () => {
return {
type: TOGGLE,
};
};
export const statusToggle = () => {
return (dispatch) => {
dispatch(statusToggleAction);
};
};
Here's my toggleReducer.js:
import { TOGGLE } from "./toggleType";
const initialState = {
status: false,
};
const toggleReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE:
status: true;
default:
return state;
}
};
export default toggleReducer;
Everything is under my userContainer.js, like that:
function UserContainer({ userData, fetchUsers }) {
useEffect(() => {
fetchUsers();
}, []);
return userData.loading ? (
<h2>Loading</h2>
) : userData.error ? (
<h2>{userData.error}</h2>
) : (
<Container maxWidth="lg" style={{ flexGrow: 1, height: "100%" }}>
<h2>User List</h2>
<div>
{userData &&
userData.users &&
userData.users.map((user) => (
<div key={user.id}>
<p>{user.name}</p>
<Switch checked={user.active} onChange={statusToggle()} />
</div>
))}
</div>
</Container>
);
}
const mapStateToProps = (state) => {
return { userData: state.user, statusToggle: state.status };
};
const mapDispatchToProps = (dispatch) => {
return {
fetchUsers: () => dispatch(fetchUsers()),
statusToggle: () => dispatch(statusToggle()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserContainer);
This is the error I am getting whenever I am clicking one of those switches:
Any ideas are welcome, I "learned" redux like 3 days ago!
toggleReducer function in toggleReducer.js, replace status: true; with return { status: true }.
Just return action in statusToggle function in toggleAction.js without dispatch as following.
export const statusToggle = () => {
return statusToggleAction();
};
Or just call statusToggleAction directly in userContainer.js as following.
export const statusToggle = () => {
return (dispatch) => {
dispatch(statusToggleAction());
};
};
I'm having some trouble figuring out how to save the state in my app. I have a component where a user adds a 'NAME' and a 'WEIGHT'. When the user clicks the submit button, it redirects them to the Home Page and the newly added name is displayed (the weight will be displayed elsewhere).
What I'm having trouble with is when I go back and add another 'NAME' and 'WEIGHT', the previous name disappears and is replaced with the new one. What I would like to happen is have the previous 'NAME' stay on the Home Page when I add a new one.
Here is my AddPage component:
const AddPage = () => {
const [name, setName] = useState('');
const [weight, setWeight] = useState(0);
const classes = useStyles();
const dispatch = useDispatch();
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.move.name,
weight: state.move.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 HomePage component:
const HomePage = () => {
const classes = useStyles();
const name = useSelector(state => state.move.name);
const displayMovementButtons = () => {
if (name) {
return (
<Button
className={classes.movementButtons}
onClick={() => history.push('/movement/:id')}
>
<div className={classes.movementName} >{name}</div>
</Button>
)
}
return <div className={classes.noMovementsMessage} >Click add button to begin</div>
}
return (
<div className={classes.homePageContent} >
<Header title={"Home Page" }/>
<div>{displayMovementButtons()}</div>
<div className={classes.fabDiv}>
<Fab
className={classes.fab}
onClick={() => history.push(`/add`)}>
<AddIcon />
</Fab>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.move.name,
}
};
const withConnect = connect(
mapStateToProps,
);
export default compose(withConnect)(HomePage);
Here is my reducer:
const initialState = []
const addMovementReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_MOVEMENT:
return [ ...state, {name: action.name, weight: action.weight} ]
default:
return state;
}
};
export default addMovementReducer;
Here is where my store is set up:
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(reduxThunk))
);
ReactDOM.render(
<Provider store={store} >
<App />
</Provider>,
document.querySelector('#root')
);
Any help would be appreciated!
you're misusing redux state by using it along side with hooks. it's an either or situation
may I see your store and reducer ?
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>
I'm doing something like this for my UI component in a React Redux app:
// Outer.js
import Inner from './path'
export const Outer = () => {
return (
<div>
...
<Inner />
...
</div>
)
}
// Inner.js
const param = ''
export const Inner = () => {
return (
<div>
<TextField
input={param}
onChange = {(param) => {
Function(param)
}}
/>
</div>
)
}
I also set up a Container component for Outer.js:
// OuterContainer.js
import Outer from './path'
const mapStateToProps = (state) => {
paramToBeUpdated: ???
}
const mapDispatchToProps = (dispatch) => {
Function: (param) => dispatch(Function(param))
}
export default connect(mapStateToProps, mapDispatchToProps)(Outer)
My action created for this step:
action/index.js
export const Function = (param) => (dispatch, getState) => {
dispatch({ type: 'FUNCTION', param })
}
And my reducer included the following function:
// reducer.js
export default reducer = (state="", action) => {
switch(action.type) {
case 'FUNCTION':
return {
...state,
param: action.param
}
...
}
}
I'm trying to update the variable paramToBeUpdated's value from the Inner UI component. But it didn't work.
Can Inner and Outer components share a container component connected with Outer?
How should I do it without making too much changes to my current setup? Is it possible to avoid creating a new Inner container, which will basically be a copy of the Outer container?
If you can't connect Inner with the state value and or the action then you must have done something wrong, here is a working example:
const { Provider, connect } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { produce } = immer;
const initialState = {
value: '',
};
//action types
const CHANGE = 'CHANGE';
//action creators
const change = (value) => ({
type: CHANGE,
payload: value,
});
const reducer = (state, { type, payload }) => {
if (type === CHANGE) {
return produce(state, (draft) => {
draft.value = payload;
});
}
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)
)
)
);
//components
const Inner = connect((state) => state.value, {
Function: change,
})(({ Function, value }) => {
return (
<input
type="text"
value={value}
onChange={(e) => Function(e.target.value)}
/>
);
});
const Outer = () => {
return (
<div>
<Inner />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<Outer />
</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>
<script src="https://unpkg.com/immer#7.0.5/dist/immer.umd.production.min.js"></script>
<div id="root"></div>
Only Outer is connected to the redux store.
If you want to dispatch an action from Inner you may do:
Connect Inner to the redux store
// Inner.js
const Inner = (props) => {
return (
<div>
<TextField
input={param}
onChange = {(param) => {
props.Function(param)
}}
/>
</div>
)
}
export default connect(null, mapDispatchToProps)(Inner)
no need to create any InnerContainer
Pass dispatch function from Outer (+ no need for Container)
// Outer.js
import Inner from './path'
export const Outer = (props) => {
return (
<div>
...
<Inner Function={props.Function} />
...
</div>
)
}
const mapStateToProps = (state) => {
paramToBeUpdated: ???
}
const mapStateToProps = (dispatch) => {
Function: (param) => dispatch(Function(param))
}
export default connect(mapStateToProps, mapDispatchToProps)(Outer)
// Inner.js
const Inner = (props) => {
return (
<div>
<TextField
input={param}
onChange = {(param) => {
props.Function(param)
}}
/>
</div>
)
}
I'm following egghead.io Redux course by Dan.
However I've no idea why my todo app is not working. It doesn't output any error and any warning, just doesn't work.
Can you please give me some hint.
This is my jsbin.
https://jsbin.com/cozecip/33/edit?js,output
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
};
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state;
}
return {
id: state.id,
text: state.text,
completed: !state.completed
};
default:
return state;
}
};
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
case 'TOGGLE_TODO':
return state.map(t =>
todo(t, action)
);
default:
return state;
}
};
const visibilityFilter = (state = 'SHOW_ALL', action) =>
{
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
};
const { combineReducers } = Redux;
const todoApp = combineReducers({
todos,
visibilityFilter
});
const { createStore } = Redux;
const store = createStore(todoApp);
const { Component } = React;
const FilterLink = ({
filter,
currentFilter,
children,
onClick
}) => {
if (filter === currentFilter){
return <span>{children}</span>
}
return (
<a href="#" onClick ={onClick}>{children}</a>
);
};
const Todo = ({
onClick,
completed,
text
}) => (
<li
onClick={onClick}
style={{
textDecoration:
completed ?
'line-through' :
'none'
}}
>
{text}
</li>
);
const TodoList = ({
todos,
onTodoClick
}) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
const AddTodo = ({
onAddClick
}) => {
let input;
return (
<div>
<input ref={node => {
input = node;
}} />
<button onClick={() => {
onAddClick(input.value)
input.value = '';
}}>
Add Todo
</button>
</div>
);
}
const Footer = ({
visibilityFilter,
onFilterClick
}) => (
<p>
Show:
{' '}
<FilterLink
filter='SHOW_ALL'
currentFilter={visibilityFilter}
onClick={onFilterClick}>
All
</FilterLink>
{' '}
<FilterLink
filter='SHOW_ACTIVE'
currentFilter={visibilityFilter}
onClick={onFilterClick}>
Active
</FilterLink>
{' '}
<FilterLink
filter='SHOW_COMPLETED'
currentFilter={visibilityFilter}
onClick={onFilterClick}>
Completed
</FilterLink>
</p>
)
const getVisibleTodos = (todos, filter) => {
switch(filter){
case 'SHOW_ALL':
return todos;
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed);
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed);
default:
return todos;
}
}
let nextTodoId = 0;
const TodoApp = ({
todos,
visibilityFilter
}) => {
return (
<div>
<AddTodo
onAddClick={
text =>
store.dispatch({
type: 'ADD_TODO',
text: this.input.value,
id: nextTodoId++
})
}
/>
<TodoList
todos={
getVisibleTodos(
todos,
visibilityFilter
)
}
onTodoClick={id =>
store.dispatch({
type: 'TOGGLE_TODO',
id
})
}
/>
<Footer
visibilityFilter={visibilityFilter}
onFilterClick={filter =>
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter
})
}
/>
</div>
);
}
const render = () => {
ReactDOM.render(
// Render the TodoApp Component to the <div> with id 'root'
<TodoApp
{...store.getState()}
/>,
document.getElementById('root')
);
};
store.subscribe(render);
render();
Make sure you have added the correct ID in index.js
it should look like this.
document.getElementById('your div id from index.html should go here')