React-Redux how to save state - redux

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 ?

Related

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)

How can I create a helper method that takes a param to filter data from a redux store?

In my react/redux/redux-thunk app I have a reducer that manages state containing a list of stuff similar to:
state = {
stuff: [
{
id: 1,
color: "blue",
shape: "square"
},
{
id: 2,
color: "red",
shape: "circle"
},
{
id: 3,
color: "yellow",
shape: "square"
},
]
};
I want to create helper functions that I can use across my app that return filtered lists of stuff from the store, based on an argument passed into the function. For example:
getStuffByShape("square"); // returns array with stuff 1 and 3
getStuffByColor("red"); // returns array with stuff 2
I've read that I can create a singleton store that I can import as needed into different files, but that it's not recommended. I'm not doing any server-side rendering at the moment but I don't want to limit my options in the future.
I've read about creating selectors and the reselect package, but the examples only show the functions taking a state parameter and it's not clear to me if I can pass in an additional, arbitrary parameter.
I can pass state as an argument from a connected component, but I may want to use these functions in other places, e.g. other helper functions.
You can create a parameterized selector, my preferred method is a curried one that you can memoize:
const { Provider, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
const initialState = {
stuff: [
{
id: 1,
color: 'blue',
shape: 'square',
},
{
id: 2,
color: 'red',
shape: 'circle',
},
{
id: 3,
color: 'yellow',
shape: 'square',
},
],
};
const reducer = (state) => state;
//helper
const createFilterBy = (field, value) => (item) =>
value ? item[field] === value : true;
//selectors
const selectStuff = (state) => state.stuff;
const createSelectFiltered = (filterFn) =>
createSelector([selectStuff], (stuff) =>
stuff.filter(filterFn)
);
const createSelectByColor = (color) =>
createSelector(
[createSelectFiltered(createFilterBy('color', color))],
(x) => x
);
const createSelectByShape = (shape) =>
createSelector(
[createSelectFiltered(createFilterBy('shape', shape))],
(x) => x
);
//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 List = React.memo(function List({ items }) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{JSON.stringify(item)}</li>
))}
</ul>
);
});
const SelectList = React.memo(function SelectList({
label,
value,
setter,
options,
}) {
return (
<label>
{label}
<select
value={value}
onChange={({ target: { value } }) =>
setter(value === 'all' ? undefined : value)
}
>
<option value="all">all</option>
{options.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</label>
);
});
const colors = ['blue', 'red', 'yellow'];
const shapes = ['square', 'circle'];
const App = () => {
const [color, setColor] = React.useState();
const [shape, setShape] = React.useState();
const selectByColor = React.useMemo(
() => createSelectByColor(color),
[color]
);
const selectByShape = React.useMemo(
() => createSelectByShape(shape),
[shape]
);
const byColor = useSelector(selectByColor);
const byShape = useSelector(selectByShape);
return (
<div>
<div>
<SelectList
label="color"
value={color}
setter={setColor}
options={colors}
/>
<SelectList
label="shape"
value={shape}
setter={setShape}
options={shapes}
/>
</div>
<div>
<h4>color</h4>
<List items={byColor} />
<h4>shape</h4>
<List items={byShape} />
</div>
</div>
);
};
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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>

How to handle action dispatching for a nested React Redux component

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>
)
}

React Redux Input handle

I'm trying to handle simple input value using react-redux and then trying to display it. I know how to display it but i have no idea how to submit input value from component to redux store. I searched web and found nothing. Can someone explain how to do this? I'm totally new to react-redux
import React from "react";
import "./App.css";
import { connect } from "react-redux";
import { useState } from "react";
import { updateValue, addValue } from "./actions/inputActions";
function App(props) {
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<div className="App">
<form onSubmit={(value) => props.submitValue(value)}>
<input onChange={handleChange} value={value} type="text" />
<button type="submit">Add</button>
</form>
<h1>{props.value}</h1>
</div>
);
}
const mapStateToProps = (state) => {
return {
value: state.value,
};
};
const mapDispatchToProps = (dispatch) => {
return {
submitValue: (e, value) => {
e.preventDefault();
dispatch(addValue(value));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Update your onSubmit function with the value stored in your local state, like this:
<form onSubmit={(e) => {
e.preventDefault();
props.submitValue(value)
}}>
<input onChange={handleChange} value={value} type="text" />
<button type="submit">Add</button>
</form>
And your mapDispatchToProps function like this:
const mapDispatchToProps = (dispatch) => {
return {
submitValue: (value) => {
dispatch(addValue(value));
},
};
};

Passing value of input tag to a search button tag - ReactJS

Any input on how I can fix this...
My goal here is for the user to search 'Jane Smith' into the search bar, click the search button and display the cards that contain 'Jane Smith'
Currently with what I have, the user searches for 'Jane Smith' which is saved as the 'term' and once the user clicks the search button the 'fetchStudents' function fails saying it doesn't have 'term'
I'm having trouble passing a value of 'term' from SearchBarInput tag over to the SearchBarButton tag, so in the student container I can use it in the mapDispatchToProps function.
My searchBar component consists of
const SearchBar = ({ onClick, value }) => (
<SearchBarWrapper>
<div>
<FontAwesomeIcon icon="search" className="searchIcon" />
<SearchBarInput
name="searchBarInput"
placeholder=" Search for something..."
label="searchBar"
defaultValue={value}
/>
<SearchBarButton onClick={onClick}>Search</SearchBarButton>
</div>
</SearchBarWrapper>
);
export default SearchBar;
In my student container I have
const Students = ({ studentsPayload = [], onClick }) => (
<StudentsContainer>
<SearchBarContainer>
<SearchBar onClick={onClick} />
</SearchBarContainer>
{studentsPayload.length > 0 ? (
<StudentsCard data={studentsPayload} />
) : null}
</StudentsContainer>
);
const mapDispatchToProps = dispatch => ({ onClick: ({ target: { value } }) => dispatch(fetchStudents(value)) });
const mapStateToProps = ({ students: { studentsPayload } }) => ({ studentsPayload });
/**
* Connects component to redux store
*/
const enhancer = connect(
mapStateToProps,
mapDispatchToProps,
)(StudentSearch);
export default enhancer;
My fetchStudents in actions looks like this
export const fetchStudents = term => async dispatch => {
try {
const { data: { items } } = await fetchData(
`${process.env.REACT_APP_STUDENTS_URLP1}${term}${process.env.REACT_APP_STUDENTS_URLP2}`);
dispatch({ type: FETCH_STUDENTS, studentsPayload: items });
} catch (error) {
throw new Error(error);
}
};
Thanks in advance!
You could convert the SearchBar component to utilize the useState hook. Then you can pass an onChange handler to your SearchInput that takes in the event and updates the SearchBar state based off of the event.target.value. Then when you hit submit, you can pass in the input value that you have stored in state.
Thank you for the help! I solved the problem by making the following changes :)
In the Search bar component
const SearchBar = ({ onClickButton, value, onValueChange }) => (
<SearchBarWrapper>
<div>
<FontAwesomeIcon icon="search" className="searchIcon" />
<SearchBarInput
name="searchBarInput"
label="searchBar"
placeholder="Search for something"
value={value}
onChange={onValueChange}
/>
<SearchBarButton onClick={() => onClickButton(value)}>Search</SearchBarButton>
</div>
</SearchBarWrapper>
);
export default SearchBar;
In the Student container
const Students = ({ studentsPayload = [], onClickButton }) => {
const [value, setValue] = useState('');
const handleChange = ({ target: { value } }) => setValue(value);
return (
<StudentsContainer>
<SearchBarContainer>
<SearchBar
onClickButton={onClickButton}
onValueChange={handleChange}
value={value}
/>
</SearchBarContainer>
{studentsPayload.length > 0 ? (
<StudentsCard data={studentsPayload} />
) : null}
</StudentsContainer>
);
};
const mapDispatchToProps = dispatch => ({ onClickButton: value => dispatch(fetchStudents(value)) });
const mapStateToProps = ({ students: { studentsPayload } }) => ({ studentsPayload });
/**
* Connects component to redux store
*/
const enhancer = connect(
mapStateToProps,
mapDispatchToProps,
)(Students);
export default enhancer;

Resources