Can you please give an example of how to swap values between two inputs, I made two inputs, and it is necessary that values come from two identical components and if the value of the first is greater than the second, they swap places, I did it, but I think this is not the right solution.
I did it, but I think this is not the right solution.
ActiveContainer.jsx :
export const ActiveContainer = ({ type, context, active }) => {
const [inputValue, setInputValue] = useState({ value1: 0, value2: 0 });
const [value1, setValue1] = useState(0);
const [value2, setValue2] = useState(0);
const validateInputed = () => {
if (inputValue.value1 > inputValue.value2) {
setValue2(inputValue.value1);
setValue1(inputValue.value2);
}
};
const setType = (containerType) => {
switch (containerType) {
case "checkbox":
return <CheckBoxFilter context={context} />;
case "inputs":
return (
<SubContainer>
<InputFilter
context={context}
value1={value1}
onValue={(value) => {
setInputValue((state) => {
return { ...state, value1: value };
});
}}
onChange={validateInputed}
placeholderText="from"
/>{" "}
-
<InputFilter
context={context}
value2={value2}
onValue={(value) => {
setInputValue((state) => {
return { ...state, value2: value };
});
}}
onChange={validateInputed}
placeholderText="to"
/>
{context.inputText ? context.inputText : ""}
</SubContainer>
);
case "dropdowns":
return (
<SubContainer>
<DropdownFilter context={context} placeholderText="from" />-
<DropdownFilter context={context} placeholderText="to" />
{context.inputText ? context.inputText : ""}
</SubContainer>
);
default:
throw new Error("Undefined type");
}
};
return <Container active={active}>{setType(type)}</Container>;
};
InputFilter.jsx :
export const InputFilter = ({
context,
placeholderText,
onValue,
onChange,
value1,
value2,
}) => {
const [value, setValue] = useState();
const setFiled = ({ target }) => {
let { value, min, max } = target;
value = Math.max(Number(min), Math.min(Number(max), Number(value)));
setValue(value);
onValue(value);
};
useEffect(() => {
setValue(value1);
}, [value1]);
useEffect(() => {
setValue(value2);
}, [value2]);
const validateInputedData = () => {
onChange();
};
return (
<Input
type="number"
onChange={setFiled}
onBlur={validateInputedData}
value={value || ""}
min={Math.min(Number(context.min), Number(context.min)) || ""}
max={Math.max(Number(context.max), Number(context.max)) || ""}
placeholder={placeholderText}
/>
);
};
Related
I created a form component using react hook forms. The component is composed from a group of checkboxes and a text input. The text input appears when user click on the last checkbox custom. The idea of this one is: when the user will click on it appears a text input and the user can add a custom answer/option. Ex: if user type test within the input then when the user will save the form, there should appear in an array test value, but custom text should't be in the array. In my application i don't have access to const onSubmit = (data) => console.log(data, "submit");, so i need to change the values within Component component. Now when i click on submit i get in the final array the custom value. Question: how to fix the issue described above?
const ITEMS = [
{ id: "one", value: 1 },
{ id: "two", value: 2 },
{ id: "Custom Value", value: "custom" }
];
export default function App() {
const name = "group";
const methods = useForm();
const onSubmit = (data) => console.log(data, "submit");
return (
<div className="App">
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<Component ITEMS={ITEMS} name={name} />
<input type="submit" />
</form>
</FormProvider>
</div>
);
}
export const Component = ({ name, ITEMS }) => {
const { control, getValues } = useFormContext();
const [state, setState] = useState(false);
const handleCheck = (val) => {
const { [name]: ids } = getValues();
const response = ids?.includes(val)
? ids?.filter((id) => id !== val)
: [...(ids ?? []), val];
return response;
};
return (
<Controller
name={name}
control={control}
render={({ field, formState }) => {
return (
<>
{ITEMS.map((item, index) => {
return (
<>
<label>
{item.id}
<input
type="checkbox"
name={`${name}[${index}]`}
onChange={(e) => {
field.onChange(handleCheck(e.target.value));
if (index === ITEMS.length - 1) {
setState(e.target.checked);
}
}}
value={item.value}
/>
</label>
{state && index === ITEMS.length - 1 && (
<input
{...control.register(`${name}[${index}]`)}
type="text"
/>
)}
</>
);
})}
</>
);
}}
/>
);
};
demo: https://codesandbox.io/s/winter-brook-sml0ww?file=/src/Component.js:151-1600
Assuming that the goal is to keep all the selections in the same group field, which must be an array that logs the selected values in provided order, with the custom input value as the last item if specified, perhaps ideally it would be easier to calculate the values in onSubmit before submitting.
But since the preference is not to add logic in onSubmit, maybe an alternative option could be hosting a local state, run the needed calculations when it changes, and call setValue manually to sync the calculated value to the group field.
Forked demo with modification: codesandbox
import "./styles.css";
import { Controller, useFormContext } from "react-hook-form";
import React, { useState, useEffect } from "react";
export const Component = ({ name, ITEMS }) => {
const { control, setValue } = useFormContext();
const [state, setState] = useState({});
useEffect(() => {
const { custom, ...items } = state;
const newItems = Object.entries(items).filter((item) => !!item[1]);
newItems.sort((a, b) => a[0] - b[0]);
const newValues = newItems.map((item) => item[1]);
if (custom) {
setValue(name, [...newValues, custom]);
return;
}
setValue(name, [...newValues]);
}, [name, state, setValue]);
const handleCheck = (val, idx) => {
setState((prev) =>
prev[idx] ? { ...prev, [idx]: null } : { ...prev, [idx]: val }
);
};
const handleCheckCustom = (checked) =>
setState((prev) =>
checked ? { ...prev, custom: "" } : { ...prev, custom: null }
);
const handleInputChange = (e) => {
setState((prev) => ({ ...prev, custom: e.target.value }));
};
return (
<Controller
name={name}
control={control}
render={({ field, formState }) => {
return (
<>
{ITEMS.map((item, index) => {
const isCustomField = index === ITEMS.length - 1;
return (
<React.Fragment key={index}>
<label>
{item.id}
<input
type="checkbox"
name={name}
onChange={(e) =>
isCustomField
? handleCheckCustom(e.target.checked)
: handleCheck(e.target.value, index)
}
value={item.value}
/>
</label>
{typeof state["custom"] === "string" && isCustomField && (
<input onChange={handleInputChange} type="text" />
)}
</React.Fragment>
);
})}
</>
);
}}
/>
);
};
Ok, so after a while I got the solution. I forked your sandbox and did little changes, check it out here: Save Form values in ReactJS using checkboxes
Basically, you should have an internal checkbox state and also don't register the input in the form, because this would add the input value to the end of the array no matter if that value is "".
Here is the code:
import "./styles.css";
import { Controller, useFormContext } from "react-hook-form";
import { useEffect, useState } from "react";
export const Component = ({ name, ITEMS }) => {
const { control, setValue } = useFormContext();
const [state, setState] = useState(false);
const [checkboxes, setCheckboxes] = useState(
ITEMS.filter(
(item, index) => index !== ITEMS.length - 1
).map(({ value }, index) => ({ value, checked: false }))
);
useEffect(() => {
setValue(name, []); //To initialize the array as empty
}, []);
const [inputValue, setInputValue] = useState("");
const handleChangeField = (val) => {
const newCheckboxes = checkboxes.map(({ value, checked }) =>
value == val ? { value, checked: !checked } : { value, checked }
);
setCheckboxes(newCheckboxes);
const response = newCheckboxes
.filter(({ checked }) => checked)
.map(({ value }) => value);
return state && !!inputValue ? [...response, inputValue] : response;
};
const handleChangeInput = (newInputValue) => {
const response = checkboxes
.filter(({ checked }) => checked)
.map(({ value }) => value);
if (state) if (!!newInputValue) return [...response, newInputValue];
return response;
};
return (
<Controller
name={name}
control={control}
render={({ field, formState }) => {
return (
<>
{ITEMS.map((item, index) => {
return (
<>
<label>
{item.id}
<input
type="checkbox"
name={`${name}[${index}]`}
onChange={(e) => {
if (index === ITEMS.length - 1) {
setState(e.target.checked);
return;
}
field.onChange(handleChangeField(e.target.value));
}}
value={item.value}
/>
</label>
{state && index === ITEMS.length - 1 && (
<input
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
field.onChange(handleChangeInput(e.target.value));
}}
type="text"
/>
)}
</>
);
})}
</>
);
}}
/>
);
};
I am building a to-do/notes app in order to learn the basics of Redux, using React hooks and Typescript.
A note is composed of an ID and a value. The user can add, delete or edit a note.
The add / delete mechanics work fine. But the edit one is trickier for me, as I'm questionning how it should be implemented.
I think my reducer's code is fine. The problem lies between my component (Note.tsx) and its parent one (App.tsx).
When i'm logging the value, I can see that the new updated/edited value of the note is not sent to the reducer. As a result, my note is not edited with the new value.
I've tried "cloning" the redux store and making my changes here, but it seems tedious and unnatural to me. Should I just call the edit method from my Note.tsx component ?
Is there a clean / conventional way to do this ?
Here is my code :
App.tsx
function App() {
const notes = useSelector<NotesStates, NotesStates['notes']>(((state) => state.notes));
const dispatch = useDispatch();
const onAddNote = (note: string) => {
dispatch(addNote(note));
};
const onDeleteNote = (note: NoteType) => {
dispatch(deleteNote(note));
};
const onEditNote = (note: NoteType) => {
dispatch(updateNote(note));
};
return (
<div className="home">
<NewNoteInput addNote={onAddNote} />
<hr />
<ul className="notes">
{notes.map((note) => (
<Note
updateNote={() => onEditNote(note)}
deleteNote={() => onDeleteNote(note)}
note={note}
/>
))}
</ul>
</div>
);
}
Note.tsx
interface NoteProps {
deleteNote(): void
updateNote(noteValue: string | number): void
note: NoteType
}
const Note: React.FC<NoteProps> = ({ deleteNote, updateNote, note: { id, value } }) => {
const [isEditing, setIsEditing] = useState(false);
const [newNoteValue, setNewNoteValue] = useState(value);
const onDeleteNote = () => {
deleteNote();
};
const onUpdateNote = () => {
updateNote(newNoteValue);
setIsEditing(false);
};
const handleOnDoubleClick = () => {
setIsEditing(true);
};
const renderBody = () => {
if (!isEditing) {
return (
<>
{!value && <span className="empty-text">Note is empty</span>}
<span>{value}</span>
</>
);
}
return (
<input
value={newNoteValue}
onChange={(e) => setNewNoteValue(e.target.value)}
onBlur={onUpdateNote}
/>
);
};
return (
<li className="note" key={id}>
<span className="note__title">
Note n°
{id}
</span>
<div className="note__body" onDoubleClick={handleOnDoubleClick}>
{renderBody()}
</div>
<button type="button" onClick={onDeleteNote}>Delete</button>
</li>
);
};
export default Note;
and the notesReducer.tsx
export interface NotesStates {
notes: Note[]
}
export interface Note {
id: number
value: string
}
const initialState = {
notes: [],
};
let noteID = 0;
export const notesReducer = (state: NotesStates = initialState, action: NoteAction): NotesStates => {
switch (action.type) {
case 'ADD_NOTE': {
noteID += 1;
return {
...state,
notes: [...state.notes, {
id: noteID,
value: action.payload,
}],
};
}
case 'UPDATE_NOTE': {
return {
...state,
notes: state.notes.map((note) => {
if (note.id === action.payload.id) {
return {
...note,
value: action.payload.value,
};
}
return note;
}),
};
}
case 'DELETE_NOTE': {
return {
...state,
notes: [...state.notes
.filter((note) => note.id !== action.payload.id)],
};
}
default:
return state;
}
};
Thanks to #secan in the comments I made this work, plus some changes.
In App.tsx :
<Note
updateNote={onEditNote}
deleteNote={() => onDeleteNote(note)}
note={note}
/>
In Note.tsx :
interface NoteProps {
deleteNote(): void
updateNote(newNote: NoteType): void // updated the signature
note: NoteType
}
// Now passing entire object instead of just the value
const onUpdateNote = (newNote: NoteType) => {
updateNote(newNote);
setIsEditing(false);
};
const renderBody = () => {
if (!isEditing) {
return (
<>
{!value && <span className="empty-text">Note is empty</span>}
<span>{value}</span>
</>
);
}
return (
<input
value={newNoteValue}
onChange={(e) => setNewNoteValue(e.target.value)}
// modifying current note with updated value
onBlur={() => onUpdateNote({ id, value: newNoteValue })}
/>
);
};
I am learning react and I reached a problem I can't get past.
On the upmost component I have a lot of functions that depend on state and that modify state. These will get passed on to children components and get linked to event handlers.
The file gets really large and I would like to somehow separate the functions and not clutter all of them in one file.
I created a demo here,you can see the App component gets really cluttered with functions.
What options do I have to separate the functions?
const {
useState,
useEffect,
useRef,
useCallback
} = React;
const useStateWithCallback = initialState => {
const [state, setState] = useState({
value: initialState,
callback: undefined
});
useEffect(() => {
if (state.callback) {
state.callback();
}
}, [state]);
const setStateWithCallback = (newValue, callback) => {
const value =
typeof newValue === "function" ? newValue(state.value) : newValue;
setState({
value,
callback
});
};
return [state.value, setStateWithCallback];
};
const Day = ({
input1,
input2,
handleInputChange,
isLocked,
index,
className
}) => {
return (
<div className={className}>
<input
name="input1"
value={input1}
placeholder="lorem"
onChange={handleInputChange}
readOnly={isLocked}
data-index={index}
/>
<input
name="input2"
value={input2}
placeholder="ipsum"
onChange={handleInputChange}
readOnly={isLocked}
data-index={index}
/>
</div>
);
};
const Menu = ({
handleSelectChange,
clearInputs,
submitInputs,
handleLock,
isLocked,
handleDateChange
}) => {
return (
<React.Fragment>
<select name="date" onChange={handleDateChange}>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
</select>
<select name="word" onChange={handleSelectChange}>
<option value="lorem">Lorem</option>
<option value="ipsum">Ipsum</option>
</select>
<button onClick={clearInputs}>Clear</button>
<button onClick={submitInputs}>Submit</button>
{isLocked ? (
<button onClick={handleLock}>Unlock</button>
) : (
<button onClick={handleLock}>Lock</button>
)}
</React.Fragment>
);
};
const Month = ({
inputs1,
inputs2,
handleInputChange,
isLocked,
mobile
}) => {
return ( < React.Fragment >
<
Day input1 = {
inputs1[0]
}
input2 = {
inputs2[0]
}
handleInputChange = {
handleInputChange
}
isLocked = {
isLocked
}
index = {
0
}
className = {
mobile ? "mobile" : "day"
}
/> <
Day input1 = {
inputs1[1]
}
input2 = {
inputs2[1]
}
handleInputChange = {
handleInputChange
}
isLocked = {
isLocked
}
index = {
1
}
className = {
mobile ? "mobile" : "day"
}
/> < /React.Fragment >
);
};
const App =()=>{
const [inputs1, setInputs1] = useStateWithCallback(
Array.from({ length: 2 }, () => "")
);
const [inputs2, setInputs2] = useStateWithCallback(
Array.from({ length: 2 }, () => "")
);
const [word, setWord] = useState("?");
const [isLocked, setIsLocked] = useStateWithCallback(false);
const [mobile, setMobile] = useState(true);
const [date, setDate] = useState(new Date().getDate());
const handleSelectChange = event => {
setWord(event.target.value);
};
const handleInputChange = event => {
if (event.target.name === "input1") {
const newInputs1 = [...inputs1];
newInputs1[event.target.dataset.index] =
event.target.value.length === 3
? event.target.value + word
: event.target.value;
setInputs1(newInputs1);
} else if (event.target.name === "input2") {
const newInputs2 = [...inputs2];
newInputs2[event.target.dataset.index] =
event.target.value.length === 4
? event.target.value + word + "%%"
: event.target.value;
setInputs2(newInputs2);
}
};
const clearInputsOnServer = () => {
return new Promise(resolve => setTimeout(resolve, 800))
.catch(() => console.log(`couldn't clear`))
.then(console.log("succesfully cleared inputs on server"));
};
const clearInputs = () => {
setInputs1(
Array.from({ length: 2 }, () => ""),
setInputs2(Array.from({ length: 2 }, () => ""), clearInputsOnServer)
);
};
const submitInputs = () => {
return new Promise(resolve => setTimeout(resolve, 800))
.catch(() => console.log(`couldn't update`))
.then(console.log("submitted inputs to server"));
};
const updateLockOnServer = () => {
return new Promise(resolve => setTimeout(resolve, 800))
.catch(() => console.log(`couldn't update`))
.then(console.log("updated lock status on server"));
};
const handleLock = () => {
setIsLocked(wasLocked => !wasLocked, updateLockOnServer);
};
let timeout = useRef();
const handleResize = useCallback(() => {
clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
const newMobile = window.innerWidth <= 600 ? true : false;
if (mobile !== newMobile) setMobile(newMobile);
}, 300);
}, [mobile]);
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [handleResize]);
const handleDateChange = event => {
setDate(event.target.value);
};
const getValuesFromServer = useRef(() => {
return new Promise(resolve => setTimeout(resolve, 800))
.then(console.log("succesfully updated inputs from server"))
.catch(() => {});
});
useEffect(() => {
getValuesFromServer.current();
}, [date]);
return (
<React.Fragment>
<Menu
handleSelectChange={handleSelectChange}
clearInputs={clearInputs}
submitInputs={submitInputs}
handleLock={handleLock}
isLocked={isLocked}
handleDateChange={handleDateChange}
/>
<Month
inputs1={inputs1}
inputs2={inputs2}
handleInputChange={handleInputChange}
isLocked={isLocked}
mobile={mobile}
/>
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render( <
React.StrictMode >
<
App/ >
<
/React.StrictMode>,
rootElement
);
.App {
font-family: sans-serif;
text-align: center;
}
.mobile input {
background-color: yellow;
font-size: 20px;
}
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
you can abstract scopes from your code into separate hooks folder than call into you App component extracting only what you need. for example all your lock state logic could be a hook like:
// useLockHandler.js at your hooks folder
const useLockHandler = () => {
const [isLocked, setIsLocked] = useStateWithCallback(false);
const updateLockOnServer = () => {
fetch(`server`, {
method: "put",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
isLocked
})
})
.catch(() => console.log(`couldn't update`))
.then(console.log("updated lock status on server"));
};
const handleLock = () => {
setIsLocked(wasLocked => !wasLocked, updateLockOnServer);
};
// expose what you need at your component.you can return as array or object
return { isLocked, handleLock }
}
export default useLockHandler
than at your App you would import useLockHandler and extract the variables you need:
export default function App() {
// all other states setting
const { isLocked, handleLock } = useLockHandler()
Guys I need help to make an API search request that should be done when page is ready.
This is state object:
const [state, setState] = useState({
s: "",
results: [],
selected: {}
});
const apiurl = ....;
This is how my search input actually works:
const search = (e) => {
if (e.key === "Enter") {
axios(apiurl + "&s=" + state.s).then(({data}) => {
let results = data.Search;
setState(prevState => {
return {...prevState, results: results }
});
});
}
}
const handleInput = (e) => {
let s = e.target.value;
setState(prevState => {
return { ...prevState, s: s }
});
}
My components:
return (
<div className='basic'>
<Header />
<Search handleInput={handleInput} search={search} />
<Container>
<Results results={state.results} openPopup={openPopup} />
{(typeof state.selected.Title != "undefined") ? <Popup selected={state.selected} closePopup={closePopup} /> : false }
</Container>
</div>
);
Search.js:
function Search ({ handleInput, search})
return (
<Container bg="dark" variant="dark">
<section className="searchbox-wrap">
<input
type="text"
placeholder="Поиск фильма"
className="searchbox"
onChange={handleInput}
onKeyPress={search}
/>
</section>
</Container>
)
Results.js:
function Results ({ results, openPopup }) {
return (
<section className="results">
{results.map(result => (
<Result key={result.imdbID} result={result} openPopup={openPopup} />
))}
</section>
);
}
So how can I make search request (for example: Superman) be done when page is loaded? Thank you!
You can do that with useEffect hook, that is equivalent to componentDidMount() lifecycle method when an empty array is passed as a second argument. So modified code would look like the following:
import React, { useState, useEffect, useCallback } from 'react';
function SearchComponent() {
const [state, setState] = useState({
s: "Superman",
results: [],
selected: {}
});
const apiurl = "";
const makeSearchRequest = useCallback(
searchString => {
axios(apiurl + "&s=" + searchString)
.then(({ data }) => data.Search)
.then(results => setState(prevState => ({ ...prevState, results })));
},
[setState]
);
// This will be invoked only on component mount
useEffect(() => makeSearchRequest(state.s), []);
const handleInput = useCallback(
e => {
e.persist();
setState(prevState => ({ ...prevState, s: e.target.value }));
},
[setState]
);
const search = useCallback(
e => {
if (e.key === "Enter") {
makeSearchRequest(state.s);
}
},
[makeSearchRequest, state.s]
);
return (
<input
type="text"
value={state.s}
onChange={handleInput}
onKeyPress={search}
/>
);
}
I'm trying to debounce my Component but the setValue prop function returns undefined in my child component, otherwise it shows fine.
const FormsyInput = React.memo(props => {
const [value, setValue] = useState(props.value);
const useStyles = getStyles(props.sheetType);
const classes = useStyles();
const onChange = event => {
setValue(event.currentTarget.value);
//THIS setValue returns undefined if i debounce in my parent.
props.setValue(event.currentTarget.value);
};
//rest is ommitted
});
My parent function that handles data:
const WorkExperience = React.memo(props => {
const [workExperienceArr, setWorkExperienceArr] = useState([1]);
const [clinics, setClinics] = useState([]);
const classes = useStyles();
const onAddWorkExperience = () => {
setWorkExperienceArr(
workExperienceArr.concat([workExperienceArr.length + 1])
);
};
const handleValuesA = (index, type) => val => {
let values;
switch (type) {
case 'clinic':
values = [...clinics];
values[index] = val;
setClinics(values);
break;
default:
break;
}
};
//THIS causes undefined
const handleValues = useCallback(_.debounce(handleValuesA, 250), []);
const onRemove = index => event => {
let clinicValues = [...clinics];
clinicValues.splice(index, 1);
let workExperienceArrCopy = [...workExperienceArr];
workExperienceArrCopy.splice(index, 1);
setClinics(clinicValues);
setWorkExperienceArr(workExperienceArrCopy);
};
return (
<Fragment>
{workExperienceArr.map((num, index) => (
<div key={index} style={{ display: 'grid' }}>
{index > 0 ? (
<Button
component="span"
className={classes.removeButton}
onClick={onRemove(index)}
>
Remove
</Button>
) : null}
<div className={classes.inputContainer1}>
<FormsyInput
ref={inputRef}
name={'clinic' + index}
label="Clinic/Hospital name"
value={clinics[index]}
setValue={handleValues(index, 'clinic')}
delegateToParent
required={props.withWorkExperience}
/>
</div>
</div>
))}
</Fragment>
);
});