2 fields in each element of FieldArray but fields.get(index) can't get second field - redux-form

I have 2 fields in each element of FieldArray,
but I use fields.get(),
it does not return the second field.
In a AvailableShopSelectFields component and isSelected function, I print as...
console.log('isSelected', fields.get(fields.length - 1));
The log will display only 'isSelect { no: 1 }
(I expected it logs { no: 1, id: 'something that I selected' }
Parent and Child components
let ParentComponent = props => {
return (
.
.
<FieldArray name="shopIds" component={AvailableShopSelectFields} />
.
.
)
}
// Child component
const AvailableShopSelectFields = props => {
const { fields } = props;
const [isLastAvailableShopNotSelected, setIsLastAvailableShopNotSelected] = useState(true);
const isSelected = (value) => {
const shopId = fields.get(fields.length - 1).id;
console.log('isSelected', shopId === value, shopId, value, fields.get(fields.length - 1));
!shopId ?
setIsLastAvailableShopNotSelected(true) :
setIsLastAvailableShopNotSelected(false);
};
return (
<>
{fields.map((shopFields, idx) => (
<Row key={idx}>
<Col className="left-panel" span={2}>
<Field name={`${shopFields}.no`} label="No." component={InputField} disabled />
</Col>
<Col className="center-panel" span={12}>
<Field name={`${shopFields}.id`}
label="Shop Name"
component={AvailableShopSelectField}
validate={[VALIDATION.required]}
input={{onChange: isSelected}}
/>
</Col>
<Col className="right-panel">
<Button style={{ borderRadius: '50%', height: 40, width: 40, padding: 0 }}
onClick={() => fields.remove(idx)}
type="reset"
>
-
</Button>
</Col>
</Row>
)
)}
<Row>
<Col align="center" span={16}>
<Button style={{ borderRadius: '50%', height: 40, width: 40, padding: 0 }}
type="reset"
onClick={() => fields.push({ no: fields.length + 1 })}
disabled={isLastAvailableShopNotSelected}
>
+
</Button>
</Col>
</Row>
</>
);
};
AvailableShopSelectField.js
const AvailableShopSelectField = ({ input, label, meta }) => {
const [availableShops, setAvailableShops] = useState([]);
const { error, touched } = meta;
const validate = {
validateStatus: '',
help: ''
};
if (touched && error) {
validate.validateStatus = 'error';
validate.help = error;
}
useEffect(() => {
(async () => await fetchAvailableShops())()
}, []);
const fetchAvailableShops = async () => {
const { data: { data } = {} } = (await API.getMemberCardAvailableShop());
setAvailableShops(data.shops);
};
return (
<FormItem
{...validate}
label={label}
>
<FormSelect
{...input}
onChange={value => input.onChange(value)}
showSearch
style={{ width: "100%" }}
optionFilterProp="children"
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
>
{availableShops.map(shop => (
<Option key={shop.id} value={shop.id}>{shop.name}</Option>
))}
</FormSelect>
</FormItem>
);
};
How can I get the second field?
UI
Redux State (is not update)

Related

Todo list refreshing there is a list item with empty name and buttons

When I'm refreshing or first opening there is a list item with empty name and its buttons. Moreover i can't disappear the empty line when i'm using && this in todolist still shows.when im console.log(todos) i got array with 1 item on it.
How can i solve this problem?
const App = () => {
const [todos, setTodos] = useState([{
userInput: null,
isDone: false,
}])
const addItem = (userInput) => {
const newTodos = [...todos, {userInput}]
setTodos(newTodos)
}
const markItem = index =>{
const newTodos = [...todos];
newTodos[index].isDone = true;
setTodos(newTodos);
}
const removeItem = index => {
const newTodos = [...todos];
newTodos.splice(index,1);
setTodos(newTodos)
}
return (
<div className=>
<Calender />
<TodoInput addItem={addItem} />
{(todos?.length > 0 ) && <TodoList todos={todos} removeItem={removeItem} markItem={markItem} />}
</div>
);
}
export default App;
const TodoList = ({ todos,removeItem,markItem }) => {
return (
<div>
<ul>
{todos?.map((todo,index) => {
return (
<li key={index} >
<TodoItem todo={todo} index={index} removeItem={removeItem} markItem={markItem} />
</li>
)
})}
</ul>
</div>
)
}
const TodoItem = ({todo,index,removeItem,markItem}) => {
return (
<div>
<span className={(todo.isDone ? "line-through" : "")}>{todo.userInput}</span>
<div>
<button onClick={()=>markItem(index)}>✔</button>
<button onClick={()=>removeItem(index)}>X</button>
</div>
</div>
)
}
export default TodoItem
Why there is empty task like in the image:
You have assigned 1 item in useState
const [todos, setTodos] = useState([{
userInput: null,
isDone: false,
}])
You should declare it empty
const [todos, setTodos] = useState([])

Adding Leading Zero's in React 'duration' component

I have created a 'working' duration slider, but I'm having trouble inserting 'leading zeros' into the hours and minutes:
const [hours, setHours] = useState([parseFloat(defaultValue.split(':')[0])])
const [minutes, setMinutes] = useState([parseFloat(defaultValue.split(':')[1])])
The above code is working, but will output (9hours & 5mins) as 9:5 (needs to be 09:05)
The code below (commented out in the full code) successfully puts in the leading zero's, but the code errors on line 61 and 97 where it's trying to handle the value
var leadingZeroHour = ('0000'+[parseFloat(defaultValue.split(':')[0])]).slice(-2)
const [hours, setHours] = useState(leadingZeroHour)
var leadingZeroMin = ('0000'+[parseFloat(defaultValue.split(':')[1])]).slice(-2)
const [minutes, setMinutes] = useState(leadingZeroMin)
The full code is below. If you can help me out and point me in the right direction, I'd be very grateful. Many thanks!
import React, { FunctionComponent, useState, useEffect } from 'react'
import { Range } from 'react-range'
type Props = {
className?: string,
defaultValue: string,
title?: string,
onUpdate: (value: string) => void;
[x: string]: any;
}
const defaultProps: Props = {
className: '',
defaultValue: '00:00',
title: '',
onUpdate: (value: any) => {},
}
const DurationInput: FunctionComponent<Props> = ({ className, defaultValue, title, onUpdate, ...rest }) => {
const [hours, setHours] = useState([parseFloat(defaultValue.split(':')[0])])
const [minutes, setMinutes] = useState([parseFloat(defaultValue.split(':')[1])])
// var leadingZeroHour = ('0000'+[parseFloat(defaultValue.split(':')[0])]).slice(-2)
// const [hours, setHours] = useState(leadingZeroHour)
// var leadingZeroMin = ('0000'+[parseFloat(defaultValue.split(':')[1])]).slice(-2)
// const [minutes, setMinutes] = useState(leadingZeroMin)
// console.log(hours)
useEffect(() => {
const duration = `${hours[0]}:${minutes[0]}`
onUpdate(duration)
}, [hours, minutes])
return (
<div className={`w-full ${className}`}>
{title ? <div className="text-base sm:text-xl mb-4 text-center">{title}</div> : <></>}
{/* <div className="grid grid-cols-3 gap-3 mb-8">
<div></div> */}
<div className="mx-auto w-40 sm:w-80 mb-8">
<div className="border border-orange rounded py-3 text-center text-4xl bg-white">
{hours}:{minutes}
</div>
<div></div>
</div>
<div className="mb-4 w-4/5 sm:w-3/5 mx-auto">
<div className="font-bold text-center mb-6">Hours</div>
<Range
step={1}
min={0}
max={23}
values={hours}
onChange={(values) => setHours(values)}
renderTrack={({ props, children }) => (
<div
{...props}
className="bg-orange rounded-full"
style={{
...props.style,
height: '6px',
width: '100%',
}}
>
{children}
</div>
)}
renderThumb={({ props }) => (
<div
{...props}
className="bg-white rounded-full border-4 border-orange"
style={{
...props.style,
height: '42px',
width: '42px',
}}
/>
)}
/>
</div>
<div className=" w-4/5 sm:w-3/5 mx-auto">
<div className="font-bold text-center mb-6">Minutes</div>
<Range
step={5}
min={0}
max={59}
values={minutes}
onChange={(values) => setMinutes(values)}
renderTrack={({ props, children }) => (
<div
{...props}
className="bg-orange rounded-full"
style={{
...props.style,
height: '6px',
width: '100%',
}}
>
{children}
</div>
)}
renderThumb={({ props }) => (
<div
{...props}
className="bg-white rounded-full border-4 border-orange"
style={{
...props.style,
height: '42px',
width: '42px',
}}
/>
)}
/>
</div>
</div>
)
}
DurationInput.defaultProps = defaultProps
export default DurationInput

value on autocomplete not changing when state updated

Im having difficulties on changing the autocomplete value, every time I click swap button.
I have application with 2 autocomplete, from and to. After click the swap button, Inside the swap function I will toggle the state from and to
The toggle of the state works but the value in autocomplete input not change. How can I achieved this? Thank you
below is the code and as well codeSandbox https://codesandbox.io/s/combobox-material-demo-forked-yup8d?file=/demo.js
export default function ComboBox() {
const [from, setFrom] = useState({});
const [to, setTo] = useState({});
const onChangeTo = (e, value) => {
setTo(value);
};
const onChangeFrom = (e, value) => {
setFrom(value);
};
const swap = () => {
setFrom(to);
setTo(from);
};
return (
<>
<Autocomplete
disablePortal
id="combo-box-demo"
options={top100Films}
value={from.label}
sx={{ width: 300 }}
onChange={(e, value) => onChangeFrom(e, value)}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
<Autocomplete
disablePortal
id="combo-box-demo"
options={top100Films}
value={to.label}
sx={{ width: 300 }}
onChange={(e, value) => onChangeTo(e, value)}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
<Button variant="outlined" onClick={() => swap()}>
SWAP
</Button>
</>
);
}
You can provide valid, defined initial state so the inputs remain as controlled inputs.
const [from, setFrom] = useState({ label: '' });
const [to, setTo] = useState({ label: '' });

reducer case set value delayed response

When I dispatch "REMOVE_TODO" on button click it does what I want it to do, the problem I'm having is that when it executes. It doesn't return the correct current array length.
Now when I click an item, it will dispatch "TOGGLE_TODO" which will change the font color and put a line-through the text.
Now while toggled and I click the "Clear Completed" button, it toggles "REMOVE_TODO" and works fine. It removes the items toggled. The problem I'm having is that The number doesn't reflex the current amount of items left in the list when I click the button once..
However if I click the button once more (or however many more times) the number updates to the correct total
This is my app code
import React, { useState, useReducer } from 'react';
import { Reducer } from './reducers/reducer';
import './App.css';
function App() {
const [{ todos, todoCount }, dispatch] = useReducer(Reducer, {
todos: [],
todoCount: 0,
completedCount: 0
});
const [text, setText] = useState("");
return (
<div className="App">
<header className="App-header">
<div>ToDo List [ <span style={{color: '#61dafb', margin: '0px', padding: '0px'}}>{ todoCount }</span> ]</div>
<div>
{ todos.map((todo, index) => (
<div
key={index}
onClick={() => dispatch(
{ type: "TOGGLE_TODO", index }
)}
style={{
fontFamily: 'Tahoma',
fontSize: '1.5rem',
textDecoration: todo.completed ? 'line-through' : "",
color: todo.completed ? '#61dafb' : 'dimgray',
cursor: 'pointer'
}}
>
{ todo.text }
</div>
))
}
<form
onSubmit={e => {
e.preventDefault();
text.length === 0 ? alert("No Task To Add!") : dispatch({ type: "ADD_TODO", text });
setText("");
}}
>
<input
type="text"
name="input"
value={ text }
onChange={e => setText(e.target.value)}
/><br />
<button>
Add
</button>
</form>
<button onClick={() => {dispatch({ type: "REMOVE_TODO" })}}>
Clear Completed
</button>
</div>
</header>
</div>
);
}
export default App;
and this is my reducer code
export const Reducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
todos: [...state.todos, { text: action.text, completed: false, id: Date.now() }],
todoCount: state.todoCount + 1,
completedCount: 0
};
case 'TOGGLE_TODO':
return {
todos: state.todos.map((todo, index) => index === action.index ? { ...todo, completed: !todo.completed } : todo),
todoCount: state.todoCount,
completedCount: 0
};
case 'REMOVE_TODO':
return {
todos: state.todos.filter(t => !t.completed),
todoCount: state.todos.length
}
default:
return state;
};
};
Does anyone have any idea what I'm doing wrong, or what I'm not doing? Thanks in advance!
Remove "todoCount" from reducer, then derive count using "todos":
<div>
ToDo List [{" "}
<span style={{ color: "#61dafb", margin: "0px", padding: "0px" }}>
{todos.filter((todo) => !todo.completed).length}
</span>{" "}
]
</div>
View in CodeSandbox here

How to update only selected component with react hooks

I'm coding a to-do list using React hooks.
Every added item has two dropdown list where the user can decide how urgent the task (urgency value) is and how long the thing to do will take (speed value).
Updating either list will add their value into a 'score' property.
By clicking a "Sort" button I can sort the entries based on the score.
Right now the problem is that if I have more then one to-do item with different urgency and speed value, the score will always be the same for both components.
Can somebody help? Thanks
function ToDo(){
const [ input, setInput ] = React.useState('')
const [ toDo, setToDo ] = React.useState([])
const [ score, setScore ] = React.useState(0)
const [ speed, setSpeed ] = React.useState(0)
const [ urgency, setUrgency ] = React.useState(0)
return(
<div>
<h2>List of things to do</h2>
<input
value={ input }
onChange={ (e) => setInput( e.target.value ) }/>
<button
onClick={ () => {
setToDo( toDo.concat(input))
setInput('')
}}>Add
</button>
<ul>
{ toDo.map(( task, idTask ) => {
return (
<li
key={idTask}
score={ speed + urgency }>
{task}<br/>
<select onChange={(e) => { setSpeed(Number(e.target.value)) }}>
<option value={1}>slow</option>
<option value={2}>medium</option>
<option value={3}>fast</option>
</select><br/>
<select onChange={(e) => { setUrgency(Number(e.target.value)) }}>
<option value={1}>non-urgent</option>
<option value={3}>urgent</option>
</select>
<span
onClick={
(index) => {
const newTodos = [...toDo]
newTodos.splice(index, 1);
setToDo( newTodos)
}}>
[-------]
</span>
</li>
)
})
}
</ul>
<button onClick={
() => {
const sortMe = [...toDo].sort((a, b) => b - a)
setToDo( sortMe )
}}>Sort!</button>
</div>
)
}
ReactDOM.render(<ToDo/>, document.getElementById('app'));
You should implement a different data model to achieve that. You should hold an array of objects for your todos (each todo will be an object) and each object should have an urgency property so you can set that individually.
Something like this:
function App() {
const [todos,setTodos] = React.useState([
{ id: 'todo1', text: 'This is todo1', urgency: 0 },
{ id: 'todo2', text: 'This is todo2', urgency: 1 }
]);
function handleClick(id) {
setTodos((prevState) => {
let aux = Array.from(prevState);
aux = aux.map((todo) => {
if (todo.id === id) {
todo.urgency === 0 ? todo.urgency = 1 : todo.urgency = 0;
}
return todo;
});
return aux;
});
}
const todoItems = todos.map((todo) =>
<li
key={todo.id}
className={todo.urgency === 1 ? 'urgent' : 'normal'}
onClick={()=>handleClick(todo.id)}
>
{todo.text}
{!!todo.urgency && '<--- This is urgent'}
</li>
);
return(
<React.Fragment>
<div>
Click on the todos!
</div>
<ul>
{todoItems}
</ul>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
li {
cursor: pointer;
}
.urgent {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Resources