How to highlight multiple selection in react using redux? - reactjs

I am trying to make multiple selection and highlight them. So suppose if I am displaying 5 buttons and if user clicks on a button it will be higlighted and if the user clicks on it again then it will become normal.
I am using redux store the state of my button. These button are getting created dynamically based on how many they are.
Redcuer
CurrentReducer(state = {court:{}}, action){
switch (action.type){
case 'COURT_SELECTED':{
return {...state,
court: action.payload
}
}}
Dispatcing action on onClick
this.props.dispatch(actions.selected({type:'COURT_S', payload: id}))
I had thought of storing id's in court by making it as an array or storing id with true or false in an object.
can anyone give me a simple working solution which is super easy

You can do something like this:
reduser
const selectedCourts = (state = {}, action) => {
switch (action.type) {
case 'COURT_TOGGLE': {
return {
...state,
[action.id]: !state[action.id]
};
}
}
return state;
};
action
dispatch({type: 'COURT_TOGGLE', id});
So you'll have piece of state like this:
selectedCourts: {
1: true,
2: false,
3: false,
...
}
I recommend you to use combineReducers to split your reducer into smaller parts so each subreducer would handle a simple part of your state like an array or one level object or boolean variable.
CurrentReducer.js
export default combineReducers({
selectedCourts,
...
});

Related

Should we have a different action type for every time we update a react context state?

Say we have built everything in a large Context and we are using this context for state management (maybe better suited with redux... but regardless...)
Do we want to have a new action type for every time we want to update one property of the state? Or should we have a generic action like UPDATE_STATE in the reducer that handles the times we have a simple change that doesn't really have any reduction logic.
For example:
switch(action.type) {
case "SET_MODAL":
return {
...state,
isModalOpen: action.isModalOpen
}
case "SET_ERROR_MSG":
return {
...state,
errMsg: action.errMsg
}
case "SET_HAS_CLICKED_THING":
return {
...state,
clickedThing: action.clickedThing
}
// ***ALOT MORE OF THESE^^^***
// ***ALOT MORE OF THESE^^^***
case "GET_ITEMS_SUCCESS":
const { items } = action
const newItems = items.map(*some reduction change logic that is not standard*)
return {
...state,
items: newItems
}
}
vs
switch(action.type) {
case "UPDATE_STATE":
return {
...state,
...action.state
}
case "GET_ITEMS_SUCCESS":
const { items } = action
const newItems = items.map(*some reduction change logic that is not standard*)
return {
...state,
items: newItems
}
}
It seems like we will have a large list of actions that Im not sure is adding value. Really we just want to store a value in the state. Do we really need an action for every time we just want to update the state?
It's up to you. But I would choose the first way. It seems more readable and much better to see data flow. Defining actions helps you to debug easily and find out what happens in the app.

Strange behavior with Redux action and reducer when creating action to change keys in state

My apologies for the title.
Anyway, I'm seeing strange behavior in my Redux Dev Tool when I create a certain type of action. The only way to show what I mean is via an example.
Let's say I have a state with this structure:
const INITIAL_STATE = {
isFile: false,
isUploaded: false,
isUser: true
};
Since there are simply keys with no nested objects inside the state, I figure I can just create one action to change all of the keys instead of a different action for each particular key.
So the reducer becomes:
const CHANGE_KEY = "CHANGE_KEY";
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CHANGE_KEY:
return {
...state,
[action.payload.key]: action.payload.value,
};
default:
return state;
}
};
const changeKeyInState = (key, value) => ({ type: CHANGE_KEY, payload: { key, value } });
Now all of this works. So I can call one action to change simple key-value pairs in the state. However, let's say I have different reducers that handle different areas of the state with their own key changing action. When I look at Redux Dev tools, it shows keys added to states that shouldn't be there, which doesn't make sense. For example, if I had a reducer for a logon that has an action that changes isLoggedIn key, that key is also added to the above reducer, which doesn't make any sense.
Here is the code for the different reducer:
const CHANGE_LOGIN_KEY = "CHANGE_LOGIN_KEY";
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CHANGE_LOGIN_KEY:
return {
...state,
[action.payload.key]: action.payload.value,
};
default:
return state;
}
};
const changeKeyInLoginState = (key, value) => ({ type: CHANGE_LOGIN_KEY, payload: { key, value } });
I only see this in Redux Dev tools. I don't know what's going on. Can anyone help me figure out what the issue is?

How to use redux saga in editable table efficiently

I have a multi page react application in which one endpoint has to show data in tabular form. Show I take GET_INFO action on componentWillMount of that endpoint. Now I have a reducer called table_info which has table_data array and shouldTableUpdate boolean in it.
My table is editable with edit and delete icon in every row. I am facing problem in update, on update I call reducer with action UPDATE_TABLE_ROW and if success than I do something like following :
//reducer.js
const initialState = {
table_data:{}, shouldTableUpdate:false;
}
export default function myReducer(state=initialState, action){
switch(action.type){
case UPDATE_SUCCESS:
// how to handle edited row here?
// also when I print my state of this reducer
// state becomes nested, so if one does lots of updates
// it will be become very heavy...
return {...state, shouldTableUpdate:true}
}
}
Can you tell how to handle update, delete, add on table using redux saga efficiently ? On googling I get naive examples only, so came to SO.
Note: Can't show the actual code as it's for my company project. Sorry for that.
Thanks.
Can you tell how to handle update, delete, add on table using redux saga efficiently ?
Well you can plainly manipulate the state object using a reducer only.
Comments:
table_data is a list and not an object.
I don't think you'll be needing shouldTableUpdate since state change in store will trigger a component update if state field is mapped in mapStateToProps.
So here's a basic template of adding, updating and deleting items via reducer.
const initialState = {
table_data: [],
};
export default function myReducer(state=initialState, action){
switch(action.type) {
case ADD_ITEM:
return {
...state,
table_data: [
...state.table_data,
action.item, // item to be added
]
};
case UPDATE_ITEM:
let updatedItem = action.item;
// do something with updatedItem
return {
...state,
table_data: table_data.map(e => (
e.id === updatedItem.id ? updatedItem : e
)),
};
case DELETE_ITEM:
const index = state.table_data.findIndex(e => e.id === action.item.id);
const numItems = state.table_data.length;
return {
...state,
table_data: [
// exclude index
...table_data.slice(0, index),
...table_data.slice(index+1, numItems),
]
};
default:
return state;
}
}

React-redux - state overwrites itself

I am using react-redux (for the first time). I have a component into which users put a 'startDate' and an 'endDate'. These should then be stored in the redux store, so that they persist.
I have the following action creator:
export const setDates = dates => ({
type: "SET_DATES",
payload: dates
});
The following reducer:
const dates = (state = {}, action) => {
switch (action.type) {
case "SET_DATES":
return action.payload;
default:
return state;
}
};
export default dates;
The state is set conditionally (i.e. only if the start and end dates actually make sense) like this:
handleSubmit = () => {
if (this.state.startDate <= this.state.endDate) {
store.dispatch(setDates([this.state.startDate, this.state.endDate]));
window.location = `/search/${
this.state.location
}&${this.state.startDate.format("DDMMYYYY")}&${this.state.endDate.format(
"DDMMYYYY"
)}&${this.state.guestCount}&${this.state.offset}&${this.state.count}`;
} else {
console.log("HANDLE ERROR");
}
};
The problem, according to the chrome redux dev-tools, is that when the submit is triggered, the store does indeed change to the new dates, but it then seems to be immediately overwritten to the empty state. By modifying the reducer to take state = {dates: 'foo'} as its first argument, I can get the store to persist 'dates:foo'. This suggests to me that, for some reason, the reducer is being called twice - once with an action of type "SET_DATES", which works, and then again, immediately, with an action of unknown type (confirmed by console.log-ging action.type), which causes it to return the default state.
So I'm pretty sure I know what the problem is, but I have no idea why it would do this.
I Already commented, but anyways. The problem is that you reload the page. It reloads redux, and it boots up from initial state, which is probably an empty array. Here is a great video from one of the brains behind redux.
https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage
It all boils down to subscribing to the store state changes, and saving it / loading the state back from storage of your choise.
Try changing you reducer like this
const dates = (state = {}, action) => {
switch (action.type) {
case "SET_DATES":
return Object.assign({}, state, {
action.payload
});
default:
return state;
}
};
export default dates;

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

Resources