React redux update nested Object property value - reactjs

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.

Related

How do I create a delete/clear button in REACT js?

Hi I'm new to REACT and I have a HW where I need to create a grocery shopping list and I need to create a clear button. The isPurchased key value pair is a boolean though. I need to create a button that when I click Purchased it clears that grocery item off my list. Any help would be appreciated.
class App extends Component {
state = {
grocery: grocery,
item: '',
brand: '',
units: Number,
quantity: Number,
isPurchased: Boolean
}
handleChange = (e) => {
this.setState({ [e.target.id]: e.target.value })
}
handleSubmit = (e) => {
e.preventDefault()
const addGrocery = {
item: this.state.item,
brand: this.state.brand,
units: this.state.units,
quantity: this.state.quantity,
}
this.setState({
grocery: [addGrocery, ...this.state.grocery],
item: '',
brand: '',
units: Number,
quantity: Number,
})
const removeGrocery = {
item: this.state.item
}
}
hey here is a full code for creating a to do list in react (it will be very similar to your problem):
**
Summary
** of the idea of creating a to-do list or shopping list is that each to-do will be an object, when we create a new object we will insert it into an array. once it is in the array by using the array.map() function we will convert each object to an HTML element to make the UI.
if something is unclear I am here to answer
file - App.js:
import React, { useState, useReducer } from "react";
import Todo from "./Todo";
export const ACTIONS = {
ADD_TODO: "add-todo",
TOGGLE_TODO: "toggle-todo",
DELETE_TODO: "delete-todo",
};
function reducer(todos, action) {
switch (action.type) {
case ACTIONS.ADD_TODO:
return [...todos, newTodo(action.payload.name)];
case ACTIONS.TOGGLE_TODO:
return todos.map((todo) => {
if (todo.id === action.payload.id) {
return { ...todo, complete: !todo.complete }; //change to complete if we found to id that toggled
}
return todo;
});
case ACTIONS.DELETE_TODO:
return todos.filter((todo) => todo.id !== action.payload.id);
default:
return todos;
}
}
function newTodo(name) {
return { id: Date.now(), name: name, complete: false };
}
const App = () => {
const [todos, dispatch] = useReducer(reducer, []); //useReducer return the state and the reducer function
const [name, setName] = useState("");
function handleSubmit(e) {
e.preventDefault();
dispatch({ type: ACTIONS.ADD_TODO, payload: { name: name } });
setName("");
}
return (
<>
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</form>
{todos.map((todo) => {
return <Todo key={todo.id} todo={todo} dispatch={dispatch} />;
})}
</>
);
};
export default App;
Another file (component) - Todo.js:
import React from "react";
import { ACTIONS } from "./App";
const Todo = ({ todo, dispatch }) => {
return (
<div>
<span style={{ color: todo.complete ? "#AAA" : "#000" }}>
{todo.name}
</span>
<button
onClick={() =>
dispatch({ type: ACTIONS.TOGGLE_TODO, payload: { id: todo.id } })
}
>
Toggle
</button>
<button
onClick={() =>
dispatch({ type: ACTIONS.DELETE_TODO, payload: { id: todo.id } })
}
>
Delete
</button>
</div>
);
};
export default Todo;

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.

How to cause a component to re render in react when using a hook which holds some state

I am trying to re render a component when a prop changes. My component uses a custom hook which holds the state of a form. In useEffect whenever the prop called refresh changes I want to re render the page. I have tried countless solutions including making a forceUpdate function and calling it when the prop changes, I tried changing state so that the component should re render, but the information in my for does not clear. Below is my code.
Component:
const CustomerInformationForm = (props) => {
const [triggerRefresh, setTriggerRefresh] = useState();
const initialState = {
name: {
value: "",
isValid: false,
},
phone: {
value: "",
isValid: false,
},
address: {
value: "",
isValid: true,
},
};
const initialValidity = false;
useEffect(() => {
console.log("customerinfo refreshing");
setTriggerRefresh(props.refresh);
}, [props.refresh]);
let [formState, inputHandler] = useForm(
initialState,
initialValidity
);
return (
<div>
<h3>Customer Information</h3>
<form className="customer-information_form">
<Input
id="name"
label="Name"
type="text"
element="input"
validators={[VALIDATOR_REQUIRE()]}
errorText="Please enter a valid name."
onInput={inputHandler}
onChange={props.customerInfo(formState)}
/>
<Input
type="text"
element="input"
id="phone"
label="Phone"
validators={[VALIDATOR_MINLENGTH(8)]}
errorText="Please enter a valid phone number."
onInput={inputHandler}
onChange={props.customerInfo(formState)}
/>
<Input
type="text"
element="input"
id="address"
label="Address"
validators={[]}
onInput={inputHandler}
onChange={props.customerInfo(formState)}
/>
</form>
</div>
);
};
export default CustomerInformationForm;
Custom hook:
import { useCallback, useReducer } from "react";
const formReducer = (state, action) => {
switch (action.type) {
case "INPUT_CHANGE":
let formIsValid = true;
for (const inputId in state.inputs) {
if (!state.inputs[inputId]) {
continue;
}
if (inputId === action.inputId) {
formIsValid = formIsValid && action.isValid;
} else {
formIsValid = formIsValid && state.inputs[inputId].isValid;
}
}
return {
...state,
inputs: {
...state.inputs,
[action.inputId]: { value: action.value, isValid: action.isValid },
},
isValid: formIsValid,
};
case "SET_DATA":
return {
inputs: action.inputs,
isValid: action.formIsValid,
};
default:
return state;
}
};
export const useForm = (initialInputs, initialValidity) => {
const [formState, dispatch] = useReducer(formReducer, {
inputs: initialInputs,
isValid: initialValidity,
});
const inputHandler = useCallback((id, value, isValid) => {
dispatch({
type: "INPUT_CHANGE",
value: value,
isValid: isValid,
inputId: id,
});
}, []);
const setFormData = useCallback((inputData, formValidity) => {
dispatch({
type: "SET_DATA",
inputs: inputData,
formIsValid: formValidity,
});
}, []);
return [formState, inputHandler, setFormData,];
};
Any solutions as to what I could do to get the form to re render empty??
I think the best way is to use the setFormData method returned by your hook (you have omitted it in your CustomerInformationForm component).
Then you can call it in the form, when some condition is met in an effect:
const initialState = useRef({
name: {
value: "",
isValid: false,
},
phone: {
value: "",
isValid: false,
},
address: {
value: "",
isValid: true,
},
});
let [formState, inputHandler, setFormData] = useForm(
initialState,
initialValidity
);
useEffect(() => {
console.log("customerinfo refreshing");
setTriggerRefresh(props.refresh);
setFormData(initialState.current);
}, [props.refresh]);
You can also store the initialState value with useRef, to avoid re-running the effect unnecessarily.

Update item array react redux

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

Edit action in react-redux

I am trying to perform an edit action in react-redux. First, I created a button on the index page.
<Link to = {`/standups/${list.id}`}>Edit</Link>
When I clicked on this button, it went to the edit page but no data was present.
In my edit page, I have this code:
class StandupEdit extends Component {
constructor(props){
super(props);
this.state = {
standups: {
user_id: this.props.standup ? this.props.standup.user_id : '',
day: this.props.standup ? this.props.standup.day : '',
work_done: this.props.standup ? this.props.standup.work_done : '',
work_planned: this.props.standup ? this.props.standup.work_planned : '',
blocker: this.props.standup ? this.props.standup.blocker : ''
},
submitted: false
};
}
handleSubmit = (event) => {
event.preventDefault();
const { standups } = this.state;
const { dispatch } = this.props;
if(standups.work_done && standups.work_planned && standups.blocker) {
dispatch(addStandup(this.state.standups))
} else {
this.setState({
submitted: true
})
}
}
componentWillReceiveProps = (nextProps) => {
debugger
this.setState({
standups: {
user_id: nextProps.user_id,
day: nextProps.day,
work_done: nextProps.work_done,
work_planned: nextProps.work_planned,
blocker: nextProps.blocker
}
});
}
componentDidMount(){
if(this.props.match.params.id){
this.props.editStandup(this.props.match.params.id)
}
}
handleChange = (event) => {
const {name, value} = event.target;
const {standups} = this.state;
this.setState({
standups: {
...standups,
[name]: value
}
});
}
render() {
const {standups, submitted, fireRedirect} = this.state
return (
<MuiThemeProvider theme={theme}>
<Paper>
<h1 className='header'>Add new standup</h1>
</Paper>
<Grid container spacing={16}>
<form onSubmit={this.handleSubmit}>
<Paper className='form'>
<TextField fullWidth name= "work_done"
value = {standups.work_done}
onChange= {this.handleChange}
type= "text"
placeholder= "What did you work on yesterday?"/>
{
submitted && !standups.work_done ?
<p>Work done is required</p> : ''
}
</Paper>
<Paper className='form'>
<TextField fullWidth name= "work_planned"
value = {standups.work_planned}
onChange= {this.handleChange}
type= "text"
placeholder= "What are you planning to work on today?"/>
{
submitted && !standups.work_planned ?
<p>Work planned is required</p> : ''
}
</Paper>
<Paper className='form'>
<TextField fullWidth name= "blocker"
value = {standups.blocker}
onChange= {this.handleChange}
type= "text"
placeholder= "Any impediments in your way?"/>
{
submitted && !standups.blocker ?
<p>Blocker required</p> : ''
}
</Paper>
<div className='button'>
<Button type="submit">Update</Button>
</div>
</form>
</Grid>
</MuiThemeProvider>
);
}
}
function mapStateToProps(state, props){
if (props.match.params.id) {
return {
standup: state.standup.standups.findIndex(item => item.id === props.match.params.id)
}
}
return {
standup: null
}
}
export default connect(mapStateToProps, {editStandup})(StandupEdit);
In action, I have this code:
export function editStandup(id) {
return dispatch => {
axios(`${ROOT_URL}/standups/${id}/${API_KEY}`, {
headers: authHeader(),
method: 'GET',
}).then( response => {
dispatch(success(response.data.standup))
})
}
function success(response) {
return {
type: 'STANDUP_EDIT',
payload: response
}
}
}
export function updateStandup(standup) {
const request = axios({
headers: authHeader(),
method: 'PUT',
url: `${ROOT_URL}/standups${API_KEY}`,
data: standup
})
return {
type: 'STANDUP_UPDATE',
payload: request
}
}
And I have following code in my reducer:
export function standup(state = {}, action){
switch (action.type) {
case 'STANDUP_UPDATE':
return state.map(item => {
if(item.id === action.payload.id)
return
standup: action.payload
return item;
});
case 'STANDUP_EDIT':
const index = state.standups.findIndex(item => item.id === action.payload.id);
if (index > -1){
return state.standups.map(item => {
if(item.id === action.payload.id)
return action.payload
});
}else{
return
standup: action.payload
}
default:
return state;
}
}
In my reducer findIndex(item => item.id === action.payload.id);, item => item.id contain error item.id is undefined.
Will anyone help me in solving this problem?
You aren't updating the state in reducer correctly, standups is not defined initially which you must do. Check the same code below
export function standup(state = {standups: []}, action){
switch (action.type) {
case 'STANDUP_UPDATE': {
const index = state.standups.findIndex(item => item.id === action.payload.id);
return {
...state,
standups: {
...state.standups.slice(0, index),
action.payload,
...state.standups.slice(index + 1),
}
}
}
case 'STANDUP_EDIT':
const index = state.standups.findIndex(item => item.id === action.payload.id);
if (index > -1){
return {
...state,
standups: {
...state.standups.slice(0, index),
action.payload,
...state.standups.slice(index + 1),
}
}
}
return {
...state,
standups: {
...state.standups
action.payload,
}
default:
return state;
}
}
Also your component name is StandupEdit and you are connecting StandupNew with the connect function
export default connect(mapStateToProps, {editStandup})(StandupEdit);

Resources