How to handle action dispatching for a nested React Redux component - redux

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

Related

How to add Spinner at specific route?

I get this code in question:nextjs getServerSideProps show loading
import Router from "next/router";
export default function App({ Component, pageProps }) {
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
const start = () => {
console.log("start");
setLoading(true);
};
const end = () => {
console.log("findished");
setLoading(false);
};
Router.events.on("routeChangeStart", start);
Router.events.on("routeChangeComplete", end);
Router.events.on("routeChangeError", end);
return () => {
Router.events.off("routeChangeStart", start);
Router.events.off("routeChangeComplete", end);
Router.events.off("routeChangeError", end);
};
}, []);
return (
<>
{loading ? (
<h1>Loading...</h1>
) : (
<Component {...pageProps} />
)}
</>
);
}
It work for me, but with all route, I just want to work with some route, and route have dynamic query, How can I do it?
First of all, you need to define a list of routes that you don't want to have a loading state. For example
//`/details/` can be a dynamic route like `/details/1`, `/details/2`, etc.
const EXCEPTED_ROUTES = ['/details/'] //routes based on definitions
And then you can do it with URL param in routeChangeStart event
const start = (url) => {
const isURLMatched = EXCEPTED_ROUTES.some(exceptedRoute => url.startsWith(exceptedRoute))
if(isURLMatched) { //the condition here is your choice
return //should not set loading state
}
console.log("start");
setLoading(true);
};

TypeError: dispatch is not a function when clicking the toggle button

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

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)

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;

redux reducer not called

Can someone help me figure out why this simple redux implementation is not calling the reducer on the anchor tag click?
The action gets initiated on each click, while the reducer only once when the program starts.
Any pointer appreaciated.
// action
const changeText = (text) => {
console.log('action changeDate')
return {
type: 'CHANGE_TEXT',
text
};
};
// reducer
const changeTextReducer = (state = [], action) => {
console.log('reducer changeTextReducer')
return [
...state,
{
text: 'Some Text'
}
]
}
class Sales extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this)
}
handleClick(e) {
e.preventDefault();
this.props.onClick('test')
}
render() {
return (
<div className="content">
<a href="" onClick={(e) => this.handleClick(e)} > click me </a>
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('mapStateToProps', state)
return {changeTextReducer: state.text}
};
const mapDispatchToProps = (dispatch) => {
return {
onClick: (dates) => {
dispatch(changeText(dates))
}
}
};
const SalesApp = connect(
mapStateToProps,
mapDispatchToProps
)(Sales);
export default SalesApp
// store
const store = createStore(
allReducers, composeWithDevTools(
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
createLogger // neat middleware that logs actions
),
)
);

Resources