Update item array react redux - reactjs

I have a list of items with a checkbox and I need to update the state in redux, just in the 'checked' item.
In this case, when it is 'checked' it is true and when it is not checked it is false.
My reducer code:
const initialState = {
esportes: [
{
externos: [
{
label: 'Futebol de campo',
checked: false,
name: 'futebolcampo',
},
{
label: 'Vôlei de areia',
checked: false,
name: 'voleiareai',
},
],
},
{
internos: [
{
label: 'Vôlei de quadra',
checked: false,
name: 'voleiquadra',
},
{
label: 'Futebol de salão',
checked: false,
name: 'futebosalao',
},
],
},
],
};
const EsportesReducer = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_ESPORTES':
return {};
default:
return state;
}
};
export default EsportesReducer;
My return page:
import React from 'react';
import {
Grid,
Paper,
Typography,
FormControlLabel,
Checkbox,
} from '#material-ui/core';
import { useSelector, useDispatch } from 'react-redux';
import { Area } from './styled';
const Esportes = () => {
const dispatch = useDispatch();
const esportes = useSelector(state => state.EsportesReducer.esportes);
const handleChangeCheckbox = event => {
const { checked } = event.target;
const { name } = event.target;
const id = parseInt(event.target.id);
dispatch({
type: 'UPDATE_ESPORTES',
payload: checked,
name,
id,
});
};
return (
<Area>
{console.log(esportes)}
<Paper variant="outlined" className="paper">
<Grid container direction="row" justify="center" alignItems="center">
<Typography>Esportes externos</Typography>
{esportes[0].externos.map((i, k) => (
<FormControlLabel
control={
<Checkbox
checked={i.checked}
onChange={handleChangeCheckbox}
name={i.name}
id="0"
color="primary"
/>
}
label={i.label}
/>
))}
<Typography>Esportes internos</Typography>
{esportes[1].internos.map((i, k) => (
<FormControlLabel
control={
<Checkbox
checked={i.checked}
onChange={handleChangeCheckbox}
name={i.name}
id="1"
color="primary"
/>
}
label={i.label}
/>
))}
</Grid>
</Paper>
</Area>
);
};
export default Esportes;
I know that here:
const EsportesReducer = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_ESPORTES':
return {};
default:
return state;
}
};
on return I need to make a map to get only the item I want to update. I tried in several ways, but I couldn't.

You need to find the item by the passed id in the action.payload.
const EsportesReducer = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_ESPORTES':
var chosen = state.esportes.find(esporte => esporte.id === action.payload.id)
return {chosen};
default:
return state;
}
};

Related

Loop in useEffect?

I'm trying to select a date and the data should be reloaded every time a specific date is selected. But with my code, it would loop.
Currently I'm using syncfusion's schedule component. Redux toolkit.
Here is my code.
const Schedules = () => {
const { facilities } = useSelector((store) => store.allFacilities);
const { fieldsScheduler } = useSelector((store) => store.allFields);
const { timeSlots, isLoadingReservation } = useSelector(
(store) => store.allReservation
);
const [calRef, setCalRef] = useState();
const [time, setTime] = useState("");
useEffect(() => {
if (calRef && time) {
console.log("TIME", time);
dispatch(getAllReservation(time)); <--- Loop
}
}, [time, calRef]);
useEffect(() => {
dispatch(getFacilities());
dispatch(getFields());
}, []);
const dispatch = useDispatch();
if (isLoadingReservation) {
return <Loading />;
}
const headerInfo = (props) => {
return (
<Grid container>
<Grid item xs={12}>
<h6>{props.subject}</h6>
</Grid>
</Grid>
);
};
const bodyInfo = (props) => {
return (
<Grid container>
<Grid item xs={12}>
<h6>{`Giá tiền cụ thể`}</h6>
</Grid>
</Grid>
);
};
const footerInfo = (props) => {
return (
<Grid container>
<Grid item xs={12}>
<h6>{`Footer here`}</h6>
</Grid>
</Grid>
);
};
const eventTemplate = (props) => {
return (
<>
<div>
<Typography variant="p">
{`${props.subject}: ${props.rentalFee}`}K
</Typography>
</div>
<div>
<Typography variant="p">{`${moment(props.startTime, "HHmm").format(
"HH:mm"
)} - ${moment(props.endTime, "HHmm").format("HH:mm")} `}</Typography>
</div>
</>
);
};
const onDataBinding = () => {
// var scheduleObj = document.querySelector(".e-schedule").ej2_instances[0];
// var currentViewDates = scheduleObj.getCurrentViewDates();
// dispatch(setCurrentDate(moment(startDate).format("YYYY-MM-DD[T]HH:mm:ss")));
// dispatch(
// getAllReservation(moment(startDate).format("YYYY-MM-DD[T]HH:mm:ss"))
// );
var currentViewDates = calRef.getCurrentViewDates();
var startDate = currentViewDates[0];
var endDate = currentViewDates[currentViewDates.length - 1];
console.log("Start date", startDate);
// setTime(moment(startDate).format("YYYY-MM-DD[T]HH:mm:ss"));
};
return (
<MDBox
width="100%"
height="100%"
minHeight="100vh"
borderRadius="lg"
shadow="lg"
bgColor="white"
sx={{ overflowX: "hidden" }}
>
<ScheduleComponent
cssClass="timeline-resource-grouping"
width="100%"
height="100%"
locale="vi"
readonly={true}
currentView="TimelineDay"
allowDragAndDrop={false}
dataBinding={onDataBinding}
// onChange={onDataBinding}
ref={(t) => setCalRef(t)}
// quickInfoTemplates={{
// header: headerInfo.bind(this),
// content: bodyInfo.bind(this),
// footer: footerInfo.bind(this),
// }}
//Data get all event in here. then mapping in ResourceDirective (field)
eventSettings={{
dataSource: timeSlots,
fields: {
subject: { name: "subject" },
id: "reservationId",
endTime: { name: "endTime" },
startTime: { name: "startTime" },
rentalFee: { name: "rentalFee", title: "Phí thuê sân" },
},
template: eventTemplate.bind(this),
}}
group={{ resources: ["Facilities", "Fields"] }}
>
<ResourcesDirective>
<ResourceDirective
field="facilityId"
title="Chọn cơ sở"
name="Facilities"
allowMultiple={false}
dataSource={facilities}
textField="facilityName"
idField="id"
></ResourceDirective>
<ResourceDirective
field="fieldId"
title="Sân"
name="Fields"
allowMultiple={true}
dataSource={fieldsScheduler}
textField="fieldName"
idField="id"
groupIDField="facilityId"
colorField="color"
></ResourceDirective>
</ResourcesDirective>
<ViewsDirective>
<ViewDirective option="TimelineDay" />
<ViewDirective option="Day" />
</ViewsDirective>
<Inject
services={[
Day,
Week,
TimelineViews,
TimelineMonth,
Agenda,
Resize,
DragAndDrop,
]}
/>
</ScheduleComponent>
</MDBox>
);
};
export default Schedules;
This is my slice.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { toast } from "react-toastify";
import customFetch from "utils/axios";
const initialState = {
isLoadingReservation: false,
timeSlots: [],
currentDate: "",
};
const authHeader = (thunkAPI) => {
return {
headers: {
Authorization: `Bearer ${thunkAPI.getState().user.user.accessToken}`,
},
};
};
export const getAllReservation = createAsyncThunk(
"allReservation/getAllReservation",
async (currentDay, thunkAPI) => {
console.log("Log:", currentDay);
try {
const response = await customFetch.post(
`/reservation-slots`,
{ date: currentDay },
authHeader(thunkAPI)
);
return response.data.timeSlots;
} catch (error) {
return thunkAPI.rejectWithValue(error.response.data);
}
}
);
const allReservationSlice = createSlice({
name: "allReservation",
initialState,
reducers: {
setCurrentDate: (state, action) => {
console.log("Payload", action.payload);
state.currentDate = action.payload;
},
},
extraReducers: {
[getAllReservation.pending]: (state, action) => {
state.isLoadingReservation = true;
},
[getAllReservation.fulfilled]: (state, action) => {
state.isLoadingReservation = false;
state.timeSlots = action.payload;
},
[getAllReservation.rejected]: (state, action) => {
state.isLoadingReservation = false;
toast.error(action.payload);
},
},
});
export const { setCurrentDate } = allReservationSlice.actions;
export default allReservationSlice.reducer;
But there is no way to do that. I used useEffect to update the UI again, but it still loop again.
I don't know if there is a way to solve my problem?
I sat for 3 days straight and the situation did not improve much.

How to mock userReducer with jest?

I have the below code:
import ReactDOM from "react-dom";
const initialTodos = [
{
id: 1,
title: "Todo 1",
complete: false,
},
{
id: 2,
title: "Todo 2",
complete: false,
},
];
const reducer = (state, action) => {
switch (action.type) {
case "COMPLETE":
return state.map((todo) => {
if (todo.id === action.id) {
return { ...todo, complete: !todo.complete };
} else {
return todo;
}
});
default:
return state;
}
};
function Todos() {
const [todos, dispatch] = useReducer(reducer, initialTodos);
const handleComplete = (todo) => {
dispatch({ type: "COMPLETE", id: todo.id });
};
return (
<>
{todos.map((todo) => (
<div key={todo.id}>
<label>
<input
type="checkbox"
checked={todo.complete}
onChange={() => handleComplete(todo)}
/>
{todo.title}
</label>
</div>
))}
</>
);
}
ReactDOM.render(<Todos />, document.getElementById("root"));
I am trying to test this component with jest. How to mock the useReducer hook here so that I can change the state of the component manually rather than changing it by clicking the checkbox.
I have tried some examples but unfortunately it did not work. Please suggest.

On button click check the checkbox and add the data into cart in react redux?

In this problem what can I do so that the on clicking the button, both the function add to the cart and select the checkbox executes together? In the current scenario add to cart is working when the button is clicked but the checkbox isn't selected. I removed all the styles so that the actual code is readable
YourItems.js
import React from "react";
import { connect } from "react-redux";
import { addOn } from "./data";
import { addOnhandleChange,addOnSelector} from "./AddOnActions";
const YourItems = ({ addOnhandleChange, addOnSelector, selectedId }) => {
return (
<div>
{addOn.map(({ id, name, img, price, unit }) => {
return (
<div key={id}>
<div>
<img src={img} alt={name} />
<p>{name}</p>
<span>Rs. {price}</span>
<input
type="checkbox"
checked={id === selectedId}
onChange={() => addOnhandleChange(id)}
/>
</div>
<button onClick={() =>addOnSelector({id, name,img,price,unit, })}>
Add
</button>
</div>
)})}
</div>
);
};
const mapStateToProps = (state) => {
return {
selectedId: state.addOn.selectedId,
};
};
export default connect(mapStateToProps, { addOnSelector,addOnhandleChange})(YourItems);
AddOnAction.js
export const addOnhandleChange = (id) => (dispatch) => {
dispatch({
type: "SELECTED_ID",
payload: id,
});
};
export const addOnSelector = ({ id, name, img, price, unit }) => (dispatch) => {
dispatch({
type: "ADD_TO_CART",
payload: { id, name, img, price, unit },
});
};
reducer.js
const initialState = {
selectedId: "",
};
export default function SelectorReducer(
state = initialState,
action
) {
switch (action.type) {
case "SELECTED_ID":
return {
...state,
selectedId: action.payload,
};
default:
return state;
}
}
data.js
export const addOn = [
{
id: 12654,
img: "https://images.pexels.com/photos/1132047/pexels-photo-1132047.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
name: "Banana",
price: 10,
unit: 1,
},
{
id: 2256435,
img: "https://images.pexels.com/photos/1132047/pexels-photo-1132047.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
name: "Mango",
price: 20,
unit: 1,
},
{
id: 3429684,
img: "https://images.pexels.com/photos/1132047/pexels-photo-1132047.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
name: "Grape",
price: 30,
unit: 1,
},
];
Add a case for "ADD_TO_CART" action type and use the id packed in action.payload in the reducer.
export default function SelectorReducer(
state = initialState,
action
) {
switch (action.type) {
case "SELECTED_ID":
return {
...state,
selectedId: action.payload,
};
case "ADD_TO_CART":
return {
...state,
selectedId: action.payload.id, // <-- use the payload.id
};
default:
return state;
}
}

Is it a valid way to write redux actions and reducer?

I've built a cards war game. I'm new to redux and wonder if I use it the correct way, especially when I declare actions in the Game and Main components, and use action's payloads as callbacks to update the state. Also, It feels like a lot of code for a small app. Maybe you can help guys and give me some insights if i'm doing it the wrong way and why, thanks. I put here the relevant components and the full code is here:
https://github.com/morhaham/cards-war-redux
store.js:
import { createStore } from "redux";
const state = {
game_ready: false,
cards: [],
player: { name: "", cards: [], points: 0 },
computer: { name: "computer", cards: [], points: 0 },
};
const reducer = (state, action) => {
switch (action.type) {
case "INIT_GAME_CARDS":
return action.payload(state);
case "UPDATE_PLAYER_NAME":
return action.payload(state);
case "SET_GAME_READY":
return action.payload(state);
case "DIST_CARDS":
return action.payload(state);
case "SET_NEXT_CARDS":
return action.payload(state);
case "INCREASE_POINTS":
return action.payload(state);
case "RESET_GAME":
return action.payload(state);
default:
return state;
}
};
const store = createStore(reducer, state);
export default store;
Main.js:
import React, { useEffect } from "react";
import { Button } from "#material-ui/core";
import { useHistory } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { NUM_OF_CARDS, MAX_CARD_VALUE } from "./constants";
import { shuffle } from "./helpers";
// action creator to initialize the game
const initGameCards = () => ({
type: "INIT_GAME_CARDS",
payload: (state) => {
// creates an array of size 52 filled with 1..13 four times
const cards = Array(NUM_OF_CARDS / MAX_CARD_VALUE)
.fill(
Array(13)
.fill()
.map((_, i) => i + 1)
)
.flat();
// shuffle the cards
shuffle(cards);
return {
...state,
cards,
};
},
});
// action creator to control the player's name
const updatePlayerName = (name) => ({
type: "UPDATE_PLAYER_NAME",
payload: (state) => ({
...state,
player: { ...state.player, name: name },
}),
});
const setGameReady = () => ({
type: "SET_GAME_READY",
payload: (state) => ({
...state,
game_ready: true,
}),
});
function Main() {
const history = useHistory();
const dispatch = useDispatch();
const player = useSelector(({ player }) => player);
// const game_ready = useSelector(({ game_ready }) => game_ready);
const handleClick = React.useCallback(
(e) => {
e.preventDefault();
if (player.name) {
dispatch(setGameReady());
history.replace("./game");
}
},
[dispatch, player.name]
);
useEffect(() => {
dispatch(initGameCards());
}, []);
const handleChange = React.useCallback((e) => {
const target = e.target;
const val = target.value;
switch (target.id) {
case "playerName":
dispatch(updatePlayerName(val));
break;
default:
break;
}
});
return (
<div>
{/* check for valid input */}
<form>
<label htmlFor="playerName">
<h1 className="text-blue-800 text-5xl text-shadow-lg mb-3">
Ready for war
</h1>
</label>
<input
className="border focus:ring-2 focus:outline-none"
id="playerName"
required
onChange={handleChange}
placeholder="Enter your name"
type="text"
value={player.name}
/>
{!player.name ? (
<p className="text-red-700">Please fill the field</p>
) : (
""
)}
<Button
onClick={handleClick}
type="submit"
color="primary"
variant="contained"
>
Start
</Button>
</form>
</div>
);
}
export default Main;
Game.js:
import { Button } from "#material-ui/core";
import React from "react";
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useHistory } from "react-router-dom";
import { NUM_OF_CARDS } from "./constants";
import { shuffle } from "./helpers";
// action creator to distribute the cards at the beginning of the game
const distCards = () => ({
type: "DIST_CARDS",
payload: (state) => {
const cards = [...state.cards];
shuffle(cards);
const computer_cards = cards.slice(0, NUM_OF_CARDS / 2);
const player_cards = cards.slice(NUM_OF_CARDS / 2);
const computer_current_card = computer_cards.pop();
const player_current_card = player_cards.pop();
return {
...state,
cards,
// distributes cards evenly
computer: {
...state.computer,
cards: computer_cards,
current_card: computer_current_card,
points: 0,
},
player: {
...state.player,
cards: player_cards,
current_card: player_current_card,
points: 0,
},
};
},
});
const setNextCards = () => ({
type: "SET_NEXT_CARDS",
payload: (state) => {
let [computer_cards, player_cards] = [
[...state.computer.cards],
[...state.player.cards],
];
const [computer_next_card, player_next_card] = [
computer_cards.pop(),
player_cards.pop(),
];
return {
...state,
player: {
...state.player,
cards: player_cards,
current_card: player_next_card,
},
computer: {
...state.computer,
cards: computer_cards,
current_card: computer_next_card,
},
};
},
});
const pointsIncreament = () => ({
type: "INCREASE_POINTS",
payload: (state) => {
const [player_current_card, computer_current_card] = [
state.player.current_card,
state.computer.current_card,
];
return {
...state,
player: {
...state.player,
points:
player_current_card > computer_current_card
? state.player.points + 1
: state.player.points,
},
computer: {
...state.computer,
points:
player_current_card < computer_current_card
? state.computer.points + 1
: state.computer.points,
},
};
},
});
function Game() {
const player = useSelector(({ player }) => player);
const computer = useSelector(({ computer }) => computer);
const game_ready = useSelector(({ game_ready }) => game_ready);
const dispatch = useDispatch();
const history = useHistory();
const handleReset = React.useCallback(() => {
dispatch(distCards());
}, [dispatch]);
useEffect(() => {
if (game_ready) {
dispatch(distCards());
} else {
history.replace("/");
}
}, [game_ready]);
useEffect(() => {
if (player.current_card && computer.current_card) {
dispatch(pointsIncreament());
}
}, [player.current_card, computer.current_card]);
const handleClick = React.useCallback(() => {
dispatch(setNextCards());
});
return (
<div className="flex justify-center">
<div className="flex flex-col">
<div>
<div>{player.name}</div>
<div>Points: {player.points}</div>
<div>{player.current_card}</div>
</div>
<div>
<div>{computer.current_card}</div>
<div>Points: {computer.points}</div>
<div>{computer.name}</div>
</div>
{!player.cards.length || !computer.cards.length ? (
<Button
onClick={handleReset}
type="submit"
color="primary"
variant="contained"
>
Again?
</Button>
) : (
<Button
onClick={handleClick}
type="submit"
color="primary"
variant="contained"
>
next
</Button>
)}
<div>
{!player.cards.length || !computer.cards.length ? (
player.points === computer.points ? (
<h2>It's a tie</h2>
) : player.points > computer.points ? (
<h2>{player.name} won!</h2>
) : (
<h2>{computer.name} won!</h2>
)
) : (
""
)}
</div>
</div>
</div>
);
}
export default Game;

React redux update nested Object property value

I'm try to make fields validation in mine project and here is mine fields state model
const initialState = {
fields: {
title: {
value: '',
isCorrectValue: false
},
amount: {
value: '',
isCorrectValue: false
}
}
}
I'm trying to update mine field state isCorrectValue if value.length lower then 1 here is regex what I'm using to check fields value length
const checkValue = (value) => {
return (/^.{1,}$/).test(value);
};
here is mine reducer where I'm trying to update mine state cannot understand why i cannot to grasp
isCorrectValue
export default function fieldsReducer(state = initialState, action) {
switch (action.type) {
case ONCHANGE:
return {
...state,
fields: {
...state.fields,
[`${action.payload.name}`]: {
...[`${action.payload.name}`],
value: action.payload.value
}
}
}
case VALIDATEFIELDS:
return {
...state,
fields: Object.keys(state.fields).reduce((acc, curr) => {
!checkTitle(state.fields[curr].value)
? Object.assign(acc, state.fields,
{
curr: { ...state.fields, [state.fields[curr].isCorrectValue]: !state.fields[curr].isCorrectValue }
}
)
: acc = state.fields;
return acc;
}, {})
}
default: return state;
}
}
here is mine component where reducer is working
const AddTransaction = () => {
const state = useSelector(state => state.fieldsReducer);
const dispatch = useDispatch();
console.log(state.fields.title.isCorrectValue)
return (
<div className='add-transaction-wrapper'>
<div className='add-expense-inputs-wrapper'>
<TextField
id='title'
label='title'
value={state.fields.title.value}
onChange={e => dispatch(onHandleChange(e, e.target.id))}
/>
<TextField
id="amount"
label="expense amount"
type="number"
InputLabelProps={{
shrink: true,
}}
value={state.fields.amount.value}
onChange={e => dispatch(onHandleChange(e, e.target.id))}
error={state.fields.amount.isCorrectValue}
/>
<button onClick={() => dispatch(fieldsValidation())}>click</button>
</div>
</div>
)
}
You can try the following in your reducer:
//not part of your reducer
const state = {
fields: {
title: {
value: '',
isCorrectValue: false
},
amount: {
value: '',
isCorrectValue: false
}
}
}
//just a sample action
const action = {
payload:{
name:'amount',
value:true
}
}
//this is what you should do in your reducer
const newState = {
...state,
fields: {
...state.fields,
[action.payload.name]: {
...state.fields[action.payload.name],
value: action.payload.value
}
}
}
console.log('new state:',newState)
VALIDATEFIELDS is kind of a mess and I have no idea what you want to do there.

Resources