How to write a Redux ReactJS modal component correctly? - reactjs

On one page I use 2 or more modals with different content. Now for each modal window, I create a reducer that stores isOpen: true / false. Is this a normal solution? It confuses me that I have to create a reducer for a new modal window in applications.
I would like to of course use only 1 reducer so that I can reuse the modal window component. And I ran into a problem when, when the modal window was opened, the rest were opened along with it.

Is hard to help with no example code. But based on what you described it looks that you'll need two states. isOpenModal1 and isOpenModal2. You can combine them in a single reducer like this:
import { createStore } from 'redux';
const rootReducer = (state,action)=>{
switch(action.type){
case 'OPEN_MODAL_1':
return {...state, isOpenModal1:true}
case 'CLOSE_MODAL_1':
return {...state, isOpenModal1:false}
case 'OPEN_MODAL_2':
return {...state, isOpenModal2:true}
case 'OPEN_MODAL_2':
return {...state, isOpenModal2:false}
default:
return state
}
}
const initState = {
isOpenModal1: false,
isOpenModal2: false
}
const store = createStore(rootReducer,initState);
export default store;
And when you need to close/open the modal you can use use useDispatch:
const Open1 = () => { dispatch({ type: "OPEN_MODAL_1" }) };

Related

I can't figure out how to use visibilityFilters in react redux todo app

I have a todo app that does all 4 crud operations but I can't filter them based on their current status here's the app on codesandbox.
import { SET_VISIBILITY_FILTER } from "../actionTypes";
const initialState = {
filters: ["SHOW_ALL"]
};
const visibilityFilter = (state = initialState, { type, payload }) => {
switch (type) {
case SET_VISIBILITY_FILTER:
return { payload };
default:
return state;
}
};
export default visibilityFilter;
Any explanations will be appreciated.
I have also checked other react redux todo app github repos but most of them are old and it didn't look like they were writing in the best possible way, so I am trying to find a better way (and so far failing at it)
A few issues
filters is an array in the initial state, but you send single values there after in your action, and you also use it a single value when filtering with it.
you expect payload in your reducer but the data you dispatch does not wrap things in payload
dispatch({
type: SET_VISIBILITY_FILTER,
filter
});
in continuation to the above you should use the already defined action setFilter for setting a filter, which correctly wrap the data in a payload property.
fixing these 3 issues, you get https://codesandbox.io/s/problems-with-redux-forked-hv36h which is working as intended.
What you are doing is an anti-pattern when you mutate the redux state variable inside the component like this:
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;
}
};
Instead what you should do, listen to the SET_VISIBILITY_FILTER action on toDoReducer.js:
//import SET_VISIBILITY_FILTER action
case SET_VISIBILITY_FILTER:
let toDoClone = [...state.todos]
//if(filter = something)
toDoClone.filter(t => //your condition)
return {
...state,
todos: toDoClone
}

attach a loading component on submit using react

I have a work around that I have been trying to attach a loader or progress bar component after clicking the submit form. I have the loading declared in redux initial state but don't know how to proceed from here.
Yes, I know how to attach the loading component while fetching data from an API using GET request but don't know how to utilize with POST request. So, the form should load a component which clicking to submit and then it will send the data to the store.
Following is the sandbox link:
https://codesandbox.io/s/brave-lewin-bp3w6?file=/src/Form.jsx
Any help would be highly appreciated.
Create an additional action startLoading that you can dispatch before the API call:
export const START_LOADING = "START_LOADING";
export const startLoading = () => {
return {
type: START_LOADING
};
};
export default function(state = initialState, action) {
switch (action.type) {
case START_LOADING:
return {
...state,
loading: true
};
case ADD_USERS:
return {
...state,
users: [...state.users, action.payload],
loading: false
};
default:
return state;
}
}
Then in your onSubmit you can do:
props.startLoading();
props.addUsers(values);

Redux MapDispatchToProps not functioning

So I'm new to Redux and I'm trying to get this base model working so I can quickly work on a small personal project, I set everything up and have no errors but I'm trying to test and my function doesn't work so I was hoping someone could point out what I've missed.
I've followed multiple different tutorials and each has a different approach so that has me lost a bit so I apologize for that.
My store.js looks like so
import rootReducer from "./reducers";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
I've used a combineReducers in my index.js in reducers folder and the auth: points to the authReducer.js file, which is this
const INIT_STATE = {
email: "",
password: "",
isLoggedIn: "false"
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
isLoggedIn: action.value
};
default:
return state;
}
};
Now What I'm aiming for is to have a button that changes that "IsLoggedIn" initial state to a true string instead of a false, I've went into my actions folder and made an authActions.js which looks like so
import { IS_LOGGED_IN_CHANGE } from "../actions/types";
import store from "../store";
export const isLoggedInChange = value => {
return dispatch => {
dispatch({
type: IS_LOGGED_IN_CHANGE,
value
});
};
};
And Finally I want to show you my component page which is showing all this, It's looking like so
import { connect } from "react-redux";
import styles from "./Landing.module.css";
import { isLoggedInChange } from "../../actions/authActions";
class Landing extends Component {
makeTrue = () => {
isLoggedInChange("true");
};
constructor(props) {
super(props);
this.state = {
email: "",
password: ""
};
}
render() {
return (
<div className={styles.background}>
<button onClick={this.makeTrue}>MAKE TRUE</button>
{this.props.isLoggedIn}
</div>
);
}
}
const mapStateToProps = state => ({
isLoggedIn: state.auth.isLoggedIn
});
const mapDispatchToProps = dispatch => ({
isLoggedInChange: value => dispatch(isLoggedInChange(value))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Landing);
Can you tell if I dropped anything making this? why is the button not changing the store state? TIA
Two problems here. You're calling your action creator directly not props.isLoggedInChange
makeTrue = () => {
this.props.isLoggedInChange("true");
};
And you need to spread the old state inside your action
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
...state,
isLoggedIn: action.value
};
Isn't the point of my mapDispatchToProps to be able to use the function right away as I was doing
Yes, the problem is mapDispatchToProps inject a function (or multiple functions) wrapped in dispatch into your props.
import { actionCreator } from './actions
const mapDispatchToProps = dispatch =>({
actionCreator : () => dispatch(actionCreator)
})
Now you have two actionCreator, one globally available in the scope (which is your action creator) and props.actionCreator which is the original action creator wrapped in dispatch. So when you call actionCreator() from inside your component it won't throw any errors (cause there is a function named actionCreator in the scope, but you will be calling the wrong function, the right one is located at props.actionCreator.
Why do I need to spread the state?
A reducer is a pure function which receives a state and action and returns the new state. When you just return
return {
isLoggedIn : true
}
You're actually overwriting the original state (which contains other properties), so first you need to spread the original state to maintain it's structural integrity and them overwrite the properties you want
return{
...state,
isLoggedIn : !state.isLoggedIn
}
Redux state is immutable so you need to return a brand new instance of state, change your reducer state to the below.
export default (state = INIT_STATE, action) => {
switch (action.type) {
case IS_LOGGED_IN_CHANGE:
console.log(action);
return Object.assign({}, state, {
isLoggedIn: action.value
});
default:
return state;
}
};
The key difference there being the
return Object.assign({}, state, {
isLoggedIn: action.value
});
Object.assign in the way I'm using it here combines the state object into a brand new object. Check out immutability within redux reducers and I'd recommend adding redux-immutable-state-invariant as a dev package, it can detect when you're directly modifying state and help point out errors like this
Return the state with the new value for isLoggedIn. Use the reducer like this:
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
...state,
isLoggedIn: action.value
};

Redux State Change: UI only updates if i'm on rootPath

I have a Reddit alike application. Where I'm trying to build out a voting function. I thought I had solved it because it workes great when on /
However, if I'm entering a different path /:category :/category/:id
I can see a dispatch being sent on click but here I'll have to "force update" (f5) to see a UI change.
API file
export const submitPostVote = (option, id) => {
return axios.post(`${API_URL}/posts/${id}`, {option}, { headers })
}
Action Creator (using redux-thunk)
export function postPostVote(option, id) {
const request = API.submitPostVote(option, id)
return (dispatch) => {
request.then(({data}) => {
dispatch({type: SUBMIT_POST_VOTE, payload: data})
});
};
}
Reducer
export default function(state = {}, action) {
const {payload} = action
switch (action.type){
case SUBMIT_POST_VOTE:
return {...state, [payload.id]: payload}
Component that use it
import { postPostVote } from '../actions';
<span
className='fa fa-angle-up voteArrow'
onClick={() => this.props.postPostVote('upVote', id)}
></span>
export default connect(null, {postPostVote})(PostComponent);
Imported as following in other components
import PostComponent from './Post_PostComponent';
<div>
<Container>
<PostComponent
key={id}
id={id}
title={title}
author={author}
voteScore={voteScore}
category={category}
timestamp={timestamp}
redirect={false}
/>
</Container>
</div>
Repo
Readable Repo
I think i see your problem, you are using the same reducer to both of the pages.
The page that holds a list of items, in this case the reducer shape
is an object that each key is an id of item and it's an object
as well that holds all the data of this item.
{
'6ni6ok3ym7mf1p33lnez': {
author: "thingone",
body: "Just kidding. It takes more than 10 minutes to learn technology.",
category: "redux",
deleted: false,
id: "6ni6ok3ym7mf1p33lnez",
timestamp: 1468479767190,
title: "Learn Redux in 10 minutes!",
voteScore: 6
}
}
The page that holds a single item, in this case the very same reducer
needs to deal with a different shape of object where all of the
item's properties are spread.
{
author: "thingone",
body: "Just kidding. It takes more than 10 minutes to learn technology.",
category: "redux",
deleted: false,
id: "6ni6ok3ym7mf1p33lnez",
timestamp: 1468479767190,
title: "Learn Redux in 10 minutes!",
voteScore: 6
}
Just for example, if you will change the shape of the object that your reducer_posts.js returns:
From this:
case SUBMIT_POST_VOTE:
return {...state, [payload.id]: payload}
To this:
case SUBMIT_POST_VOTE:
return {...state, ...payload}
You will notice that now the first page with the list not working well but the second page that shows the single item is working as expected.
So you should re-think the shape of your reducers or split this reducer into two.
EDIT
I was curious on what will be the best structure to handle this scenario so i took the liberty of changing some stuff for you.
So I've decided to split your reducer_posts.js into 2 reducers:
posts and post (plural and singular).
I've added another reducer reducer_post.js.
and this is the code:
import { POST_GET_POST, SUBMIT_POST_VOTE } from '../actions/action_constants';
export default function (state = {}, action) {
const { payload } = action
switch (action.type) {
case SUBMIT_POST_VOTE:
return { ...state, ...payload }
case POST_GET_POST:
return payload;
default:
return state;
}
}
And the old reducer reducer_posts.js now looks like this:
import _ from 'lodash';
import { POST_GET_POSTS, SUBMIT_POST_VOTE } from '../actions/action_constants';
export default function(state = {}, action) {
const {payload} = action
switch (action.type){
case SUBMIT_POST_VOTE:
return {...state, [payload.id]: payload}
case POST_GET_POSTS:
return _.mapKeys(payload, 'id');
default:
return state;
}
}
And of course don't forget to add it to the rootReducer:
export const rootReducer = combineReducers({
routing: routerReducer,
posts: PostsReducer,
post: PostReducer, // our new reducer
comments: CommentReducer,
categories: CategoriesReducer,
});
Basically one will handle multiple posts object shape and one will handle a single post object shape.
Now, the only thing you should change is the mapStateToProps in the Post_DetailedPost.js component.
Instead of using the posts reducer it will use the post reducer:
function mapStateToProps(state) {
return {post: state.post}
}
This should fix your problem.
I think the issue is this: The reducer logic for the vote action assumes that the state has some specific structure to it ( an object with posts, with their id as the key.), but then the reducer logic for the getPost action breaks this assumption.
Looked at the code in the repo, specifically for the "DetailedPost". This looked a bit off, but I may be wrong:
// in reducer_posts.js
case POST_GET_POST:**strong text**
return payload;
I would think that you get one specfic post from the API. If so, this return would basically get rid of everything else in the state. (All other posts). Maybe that's what you want, but I've never seen a reducer change the structure of the state in that way. I would have expected it to be something like
case POST_GET_POST:
return {...state, [payload.id]: payload}
(maybe without the ...state if you really want the rest of the posts to disappear.).
But then you'd have to change the mapStateToProps for the detailed view as well. You should be able to access the Router props from the second argument (Note: exact path on ownProps may be different. I don't remember exactly how the url params where stored)
// in Post_DetailedPost.js
function mapStateToProps(state, ownProps) {
const idFromURL = ownProps.id; // or maybe ownProps.match.params.id
return {post: state.posts[idFromURL]};
}
This could actually very well be the issue, because you upvote a post, you get the new (updated) post back, and you change the state like this.
case SUBMIT_POST_VOTE:
return {...state, [payload.id]: payload}
But that's not the state structure that your used to pass the post down down to your component. So you wouldn't get the updated props passed down to the details view. (But with the suggested edits to the mapStateToProps it might work.)

Reusing actions across multiple reducers in redux

So I'm working on a large react/redux app and have been very happy with how simple it is manage state in redux. We are using a "ducks" style approach to reducers/action/action Creators to keep things relatively domain based. In addition, we try to keep ui state associated with the domain and most of the reducers have a similar structure. It looks something like this :
export default function home(state = initialState, action = {}) {
switch (action.type) {
case OPEN:
return {
...state,
ui: {
...state.ui,
[action.key]: true
}
};
case CLOSE:
return {
...state,
ui: {
...state.ui,
[action.key]: false
}
};
case SELECT_TAB:
return {
...state,
ui: {
selectedTab: action.selected
}
};
default:
return state;
}
}
We end up having the same actions repeated over and over for the ui, mainly toggles and setting what is displayed. Is there a way to keep the domain based ui within a reducer without having to add in the OPEN and CLOSE type statements for the ui in every reducer. Maybe what I'm thinking of is an anti pattern in redux but curious to see if anyone has run into this kind of issue before?
UPDATE:
I like the solution listed below but how would you extend the reducer to sort of contain a ui reducer within it.
combineReducers({
home: {createUiReducer('home', initialState), ...home}
})
That way you can have nested domain based ui without repeating all of the actions. Not sure how you would go about that because you would essentially be dynamically adding CASE statements.
Well you would need to namespace them with a prefix. If you are, like the most developers are, lazy, then create a helper function which would generate the reducers for you ;)
Like this one..
const createOpenCloseReducer = (prefix, initialState) => {
const = ui = prefix + "_UI";
return (state = initialState, action) => {
switch(action.type) {
case (prefix + "_CLOSE"):
return { ...state, [ui]: "CLOSED" }
case (prefix + "_OPEN"):
return { ...state, [ui]: "OPENED" }
default:
return state
}
}
}
import { combineReducers, createStore } from 'redux';
const rootReducer = combineReducers({
home: combineReducers({
ui: createOpenCloseReducer("HOME", { "HOME_UI": "CLOSED" }),
data: someOtherHomeDataReducer
}),
someOther: createOpenCloseReducer("OTHER", { "OTHER_UI": "CLOSED" })
})
store = createStore(rootReducer)
// use it..
store.dispatch({type: "HOME_CLOSE"})
store.dispatch({type: "OTHER_OPEN"})
// your state..
store.getState().home.ui // {HOME_UI: "CLOSED"}
store.getState().someOther // {OTHER_UI: "OPENED"}

Resources