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>
);
});
Related
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}
/>
);
};
I'm working on a React Notes Application and my App.js contains all the necessary functions props which are passed down to several components.
As a result I'm doing prop drilling a lot where I'm passing down around 10-20 props/functions in the components where it isn't needed.
I tried using useContext Hook but I guess it doesn't work with callback functions in the value parameter.
App.js
const App = () => {
const [ notes, setNotes ] = useState([]);
const [ category, setCategory ] = useState(['Notes']);
const [ searchText, setSearchText ] = useState('');
const [ alert, setAlert ] = useState({
show:false,
msg:'',
type:''
});
const [isEditing, setIsEditing] = useState(false);
const [editId, setEditId] = useState(null);
useEffect(()=>{
keepTheme();
})
// retreive saved notes
useEffect(()=>{
const savedNotes = JSON.parse(localStorage.getItem('react-notes-data'));
if (savedNotes) {
setNotes(savedNotes)
}
}, []);
// save notes to local storage
useEffect(() => {
localStorage.setItem('react-notes-data', JSON.stringify(notes))
setNotesCopy([...notes]);
}, [notes]);
// save button will add new note
const addNote = (text) => {
const date = new Date();
const newNote = {
id: nanoid(),
text: text,
date: date.toLocaleDateString(),
category: category,
}
const newNotes = [...notes, newNote]
setNotes(newNotes);
}
const deleteNote = (id) => {
showAlert(true, 'Note deleted', 'warning');
const newNotes = notes.filter(note => note.id !== id);
setNotes(newNotes);
}
// hardcoded values for categories
const allCategories = ['Notes', 'Misc', 'Todo', 'Lecture Notes', 'Recipe'];
// copy notes for filtering through
const [notesCopy, setNotesCopy] = useState([...notes]);
const handleSidebar = (category) => {
setNotesCopy(category==='Notes'?[...notes]:
notes.filter(note=>note.category===category));
}
// function to call alert
const showAlert = (show=false, msg='', type='') => {
setAlert({show, msg, type});
}
return (
<div>
<div className="container">
<Sidebar
allCategories={allCategories}
handleSidebar={handleSidebar}
notesCopy={notesCopy}
key={notes.id}
/>
<Header notes={notes} alert={alert} removeAlert={showAlert} />
<Search handleSearchNote={setSearchText} />
<NotesList
notesCopy={notesCopy.filter(note=>
note.text.toLowerCase().includes(searchText) ||
note.category.toString().toLowerCase().includes(searchText)
)}
handleAddNote={addNote}
deleteNote={deleteNote}
category={category}
setCategory={setCategory}
allCategories={allCategories}
showAlert={showAlert}
notes={notes}
setNotes={setNotes}
editId={editId}
setEditId={setEditId}
isEditing={isEditing}
setIsEditing={setIsEditing}
/>
</div>
</div>
)
}
NotesList.js
const NotesList = (
{ notesCopy, handleAddNote, deleteNote, category, setCategory, showHideClassName, allCategories, showAlert, isEditing, setIsEditing, notes, setNotes, editId, setEditId }
) => {
const [ noteText, setNoteText ] = useState('');
const textareaRef = useRef();
// function to set edit notes
const editItem = (id) => {
const specificItem = notes.find(note=>note.id === id);
setNoteText(specificItem.text);
setIsEditing(true);
setEditId(id);
textareaRef.current.focus();
}
return (
<div key={allCategories} className="notes-list">
{notesCopy.map(note => {
return (
<Note
key={note.id}
{...note}
deleteNote={deleteNote}
category={note.category}
isEditing={isEditing}
editId={editId}
editItem={editItem}
/>)
})}
<AddNote
handleAddNote={handleAddNote}
category={category}
setCategory={setCategory}
showHideClassName={showHideClassName}
allCategories={allCategories}
showAlert={showAlert}
isEditing={isEditing}
setIsEditing={setIsEditing}
notes={notes}
setNotes={setNotes}
editId={editId}
setEditId={setEditId}
noteText={noteText}
setNoteText={setNoteText}
textareaRef={textareaRef}
/>
</div>
)
}
AddNote.js
const AddNote = ({ notes, setNotes, handleAddNote, category, setCategory, showHideClassName, allCategories, showAlert, isEditing, setIsEditing, editId, setEditId, noteText, setNoteText, textareaRef }) => {
const [ show, setShow ] = useState(false);
const [ modalText, setModalText ] = useState('');
const charCount = 200;
const handleChange = (event) => {
if (charCount - event.target.value.length >= 0) {
setNoteText(event.target.value);
}
}
const handleSaveClick = () => {
if (noteText.trim().length === 0) {
setModalText('Text cannot be blank!');
setShow(true);
}
if (category === '') {
setModalText('Please select a label');
setShow(true);
}
if (noteText.trim().length > 0 && category!=='') {
showAlert(true, 'Note added', 'success');
handleAddNote(noteText);
setNoteText('');
setShow(false);
}
if (noteText.trim().length > 0 && category!=='' && isEditing) {
setNotes(notes.map(note=>{
if (note.id === editId) {
return ({...note, text:noteText, category:category})
}
return note
}));
setEditId(null);
setIsEditing(false);
showAlert(true, 'Note Changed', 'success');
}
}
const handleCategory = ( event ) => {
let { value } = event.target;
setCategory(value);
}
showHideClassName = show ? "modal display-block" : "modal display-none";
return (
<div className="note new">
<textarea
cols="10"
rows="8"
className='placeholder-dark'
placeholder="Type to add a note.."
onChange={handleChange}
value={noteText}
autoFocus
ref={textareaRef}
>
</textarea>
<div className="note-footer">
<small
className='remaining'
style={{color:(charCount - noteText.length == 0) && '#c60000'}}>
{charCount - noteText.length} remaining</small>
<div className='select'>
<select
name={category}
className="select"
onChange={(e)=>handleCategory(e)}
required
title='Select a label for your note'
defaultValue="Notes"
>
<option value="Notes" disabled selected>Categories</option>
{allCategories.map(item => {
return <option key={item} value={item}>{item}</option>
})}
</select>
</div>
<button className='save' onClick={handleSaveClick} title='Save note'>
<h4>{isEditing ? 'Edit':'Save'}</h4>
</button>
</div>
{/* Modal */}
<main>
<div className={showHideClassName}>
<section className="modal-main">
<p className='modal-text'>{modalText}</p>
<button type="button" className='modal-close-btn'
onClick={()=>setShow(false)}><p>Close</p>
</button>
</section>
</div>
</main>
</div>
)
}
I want the functions passed from App.js to NotesList.js to be in AddNote.js without them being passed in NotesList.js basically minimizing the props destructuring in NotesList.js
Context API does work with function. What you need to do is pass your function to Provider inside value :
<MyContext.Provider value={{notes: notesData, handler: myFunction}} >
For example:
// notes-context.js
import React, { useContext, createContext } from 'react';
const Context = createContext({});
export const NotesProvider = ({children}) => {
const [notes, setNote] = useState([]);
const addNote = setNote(...); // your logic
const removeNote = setNote(...); // your logic
return (
<Context.Provider value={{notes, addNote, removeNote}}>
{children}
</Context.Provider>
)
}
export const useNotes = () => useContext(Context);
Add Provider to your App.js like so:
// App.js
import NoteProvider from './notes-context';
export default App = () => {
return (
<NoteProvider>
<div>... Your App</div>
</NoteProvider>
)
}
Then call UseNote in your NoteList.js to use the function:
// NoteList.js
import {useNotes} from './note-context.js';
export const NoteList = () => {
const {notes, addNotes, removeNotes} = useNotes();
// do your stuff. You can now use functions addNotes and removeNotes without passing them down the props
}
I am trying to rewrite a small app from vanilla js to react, and in one element I encountered a problem with passing on values in the inputs. What this element does, is after selecting a number it generates that many inputs to fill, and after filling send its id and value further (value can also be empty)
In Vanilla Js I did it with id and querySelector, but in React I have a trouble to change it correct
React code:
import React, { useState, useEffect } from "react";
import "./style.css";
import Values from "./Values";
export default function App() {
const [numberValue, setNumberValue] = useState("");
const [inputValues, setInputValues] = useState([]);
const [sendValues, setSendValues] = useState(false);
const [inputs, setInputs] = useState([]);
let numbers = [4, 6, 8];
//reset teamsName on change teamsValue
useEffect(() => {
for (let i = 1; i <= numberValue; i++) {
setInputValues(prev => [
...prev,
{
id: i,
value: ""
}
]);
}
}, [numberValue]);
const showButtons = numbers.map((number, i) => (
<button
className={`${numberValue === number ? "button active" : "button"}`}
onClick={() => {
setNumberValue(number);
setInputValues([]);
setInputs([]);
showInputs();
}}
>
{number}
</button>
));
//let inputs = [];
const showInputs = () => {
for (let i = 1; i <= numberValue; i++) {
setInputs(prev => [
...prev,
<input
type="text"
className="input"
placeholder={`Input ${i}`}
//value={inputValues.find(input => input.id === i && input.value)}
onChange={e =>
inputValues.filter(
input =>
input.id === i &&
setInputValues([
...inputValues,
{ id: i, value: e.target.value }
])
)
}
/>
]);
}
};
return (
<>
<div className="button-group">{showButtons}</div>
{numberValue && (
<>
<h3 className="title">Your inputs</h3>
<div className="input-group">{inputs}</div>
</>
)}
<button onClick={() => setSendValues(true)}>SEND</button>
{sendValues && <Values inputValues={inputValues} />}
</>
);
}
JS:
const buttonGroup = document.querySelector(".button-group");
const inputGroup = document.querySelector(".input-group");
const inputValues = document.querySelector(".input-values");
let n;
const showInputs = number => {
n = number;
inputGroup.innerHTML = ''
for (let i = 1; i <= number; i++) {
inputGroup.innerHTML += `
<input type="text" name="name" id="input-${i}" class="input" placeholder="team name"> <br>
`;
}
};
let values = []
const showValues = () => {
//clear
inputValues.innerHTML = '';
values = [];
//show new
for (let i = 1; i <= n; i++) {
const input_val = document.querySelector(`#input-${i}`).value;
values.push({
id: i,
value: input_val
});
}
for(let i = 0; i<=n; i++){
inputValues.innerHTML += `
<p>id: ${values[i].id} value:${values[i].value}
</p>
`
}
};
Links to code:
React -> https://stackblitz.com/edit/react-uw9dzc?file=src/App.js
JS -> https://codepen.io/Arex/pen/qBqLVBq?editors=1111
I took the liberty to simplify your code a bit. Basically I assigned value as it's own variable const value = e.target.value; as it is a synthetic event and tends to get lost if you pass it further down, so this preserves the value. Also, I changed inputValues to an object to make it easier to update:
// App.js
export default function App() {
const [numberValue, setNumberValue] = useState("");
const [inputValues, setInputValues] = useState({});
const [sendValues, setSendValues] = useState(false);
let numbers = [4, 6, 8];
const showButtons = numbers.map((number, i) => (
<button
className={`${numberValue === number ? "button active" : "button"}`}
onClick={async () => {
await setNumberValue(number);
await setInputValues({});
}}
>
{number}
</button>
));
return (
<>
<div className="button-group">{showButtons}</div>
{numberValue && (
<>
<h3 className="title">Your inputs</h3>
<div className="input-group">
{[...new Array(numberValue)].map((_value, id) => (
<input
type="text"
className="input"
placeholder={`Input ${id}`}
onChange={e => {
const value = e.target.value;
setInputValues(prev => {
prev[id] = value;
return prev;
});
}}
/>
))}
</div>
</>
)}
<button onClick={() => setSendValues(true)}>SEND</button>
{sendValues && <Values inputValues={inputValues} />}
</>
);
}
// Values.js
const Values = ({ inputValues }) => {
const showValues = Object.keys(inputValues).map(input => (
<div>
{input} : {inputValues[input]}
</div>
));
return <div>{showValues}</div>;
};
export default Values;
There are multiple issues in the shared code as follows:
It is not recommended to store components in state, in the shared code you are storing <input/> component in state. For more details check this
Unnecessary states are being used, always try to keep a minimum number of states as more number of states as more states are needed to be managed, making things unnecesarily complicated.
using previous state syntax to generate new state where it is not needed.
I am adding a working code with minimum changes for you reference.
App.js
import React, { useState, useEffect } from "react";
import "./style.css";
import Values from "./Values";
export default function App() {
const [numberValue, setNumberValue] = useState('');
const [inputValues, setInputValues] = useState([]);
const [sendValues, setSendValues] = useState(false);
let numbers = [4, 6, 8];
//reset teamsName on change teamsValue
useEffect(() => {
setInputValues(
Array(numberValue).fill("")
);
setSendValues(false)
}, [numberValue]);
const showButtons = numbers.map((number, i) => (
<button
className={`${numberValue === number ? "button active" : "button"}`}
onClick={() => {
setNumberValue(number);
}}
>
{number}
</button>
));
return (
<>
<div className="button-group">{showButtons}</div>
{numberValue && (
<>
<h3 className="title">Your inputs</h3>
<div className="input-group">
{inputValues.map((val, i) => (
<input
key={`input${i}`}
type="text"
className="input"
placeholder={`Input ${i+1}`}
value={val}
onChange={(e) => {let newValues = inputValues.slice(); newValues[i]=e.target.value; setInputValues(newValues)}
}
/>
))}
</div>
</>
)}
<button onClick={() => setSendValues(true)}>SEND</button>
{sendValues && <Values inputValues={inputValues} />}
</>
);
}
Values.js
import React from "react";
const Values = ({ inputValues }) => {
const showValues = inputValues.map((input, i) => (
<div key={'output'+i}>
{i+1} : {input}
</div>
));
return <div>{showValues}</div>;
};
export default Values;
I am also sharing a updated stackblitz code reference you shared for better understanding Updated snippet with fixes for reference.
I am trying to filter ResourceItems in my ResourceList by their tag. For exmaple, if a user searches for the tag "Sports", all items with the this tag should be returned.
I have been utilising this example to produce this, but it doesn't actually have any functionality when the user enters a tag to filter by.
This is my code so far, in which I don't get any items back:
const GetProductList = () => {
// State setup
const [taggedWith, setTaggedWith] = useState(null);
const [queryValue, setQueryValue] = useState(null);
// Handled TaggedWith filter
const handleTaggedChange = useCallback(
(value) => setTaggedWith(value),
[],
);
const handleTaggedRemove = useCallback(() => setTaggedWith(null), []);
const handleQueryRemove = useCallback(() => setQueryValue(null), []);
const handleFilterClear = useCallback(() => {
handleTaggedRemove();
handleQueryRemove();
}, [handleQueryRemove, handleTaggedRemove]);
const filters = [
{
key:'taggedWith',
label:'Tagged With',
filter: (
<TextField
label="Tagged With"
value={taggedWith}
onChange={handleTaggedChange}
labelHidden
/>
),
shortcut: true,
}
];
const appliedFilters = !isEmpty(taggedWith)
? [
{
key: 'taggedWith',
label: disambiguateLabel('taggedWith', taggedWith),
onRemove: handleTaggedRemove,
},
]
: [];
const filterControl = (
<Filters
queryValue={queryValue}
filters={filters}
appliedFilters={appliedFilters}
onQueryChange={setQueryValue}
onQueryClear={handleQueryRemove}
onClearAll={handleFilterClear}
children={() => {
<div>Hello World</div>
}}
>
<div>
<Button onClick={() => console.log('New Filter Saved')}>Save</Button>
</div>
</Filters>
)
// Execute GET_PRODUCTS GQL Query
const { loading, error, data } = useQuery(GET_PRODUCTS);
if (loading) return "Loading products...";
if (error) return `Error = ${error}`;
// Return dropdown menu of all products
return (
<Frame>
<Page>
<Layout>
<Layout.Section>
<DisplayText size="large">WeBuy Valuation Tool</DisplayText>
</Layout.Section>
<Layout.Section>
<Card>
<Card.Section>
<div>
<Card>
<ResourceList
resourceName={{singular: 'product', plural: 'products'}}
items={data ? data.products.edges : ""}
renderItem={renderItem}
filterControl={filterControl}
>
</ResourceList>
</Card>
</div>
</Card.Section>
</Card>
</Layout.Section>
</Layout>
</Page>
</Frame>
)
function renderItem(item) {
const { id, title, images, tags } = item.node;
const media = (
<Thumbnail
source={ images.edges[0] ? images.edges[0].node.originalSrc : '' }
alt={ images.edges[0] ? images.edges[0].node.altText : '' }
/>
);
const resourceItem = (
<ResourceItem
id={id}
accessibilityLabel={`View details for ${title}`}
media={media}
>
<Stack>
<Stack.Item fill>
<h3><TextStyle variation="strong">{title}</TextStyle></h3>
<h2>{tags}</h2>
</Stack.Item>
<Stack.Item>
<AddMetafield id={id} />
<DeleteMetafield id={id} />
</Stack.Item>
</Stack>
</ResourceItem>
);
tags ? tags.forEach(tag => {
if (tag == "Sports") {
console.log("has tag")
return resourceItem
}
}) : console.log("Return")
}
function disambiguateLabel (key, value) {
switch(key) {
case 'taggedWith' :
return `Tagged with ${value}`;
default:
return value;
}
}
function isEmpty(value) {
if (Array.isArray(value)) {
return value.length === 0;
} else {
return value === '' || value == null;
}
}
}
I Couldn't find any documentation on how to implement this, and I expected it to be built-in, but I ended up doing something like this:
//add "filter.apply" function to the filter object
const filters = [{...apply:(c:Item)=>...},isApplied=...]
const appliedFilters = filters.filter(f=>f.isApplied);
let filteredItems = pageData.Items;
for (const filter of appliedFilters){
filteredItems = filteredItems.filter(filter.apply)
}
if(queryValue && queryValue.length > 0){
filteredItems = filteredItems.filter((c:FBComment)=>c.text.includes(queryValue))
}
I'm making a filter function by checkbox. I made a reproduce like below. I changed values in array but checkbox checked status not change, what I missed? I think I must re-render list but it's also refill checked array to initial state. What should I do? Thanks!
import * as React from "react";
import "./styles.css";
import { Checkbox } from "antd";
const arr = [
{
name: "haha"
},
{
name: "haha2"
},
{
name: "hahaha"
}
];
const App = () => {
let [viewAll, setViewAll] = React.useState(true);
let checked = new Array(3).fill(true);
// render calendars checkbox
let list = arr.map((value, index) => {
return (
<Checkbox
style={{ color: "white" }}
checked={checked[index]}
onChange={() => handleFilter(value, index)}
className="check-box"
>
haha
</Checkbox>
);
});
const handleViewAll = () => {
setViewAll(!viewAll);
checked = checked.map(() => viewAll);
};
const handleFilter = (value, index) => {
setViewAll(false);
checked.map((_value, _index) => {
if (_index === index) {
return (checked[_index] = !checked[_index]);
} else {
return checked[_index];
}
});
console.log(checked);
};
return (
<div className="App">
<Checkbox checked={viewAll} onChange={() => handleViewAll()}>Check all</Checkbox>
{list}
</div>
);
};
export default App;
Here is codesanboxlink
You should create checked state. Check the code below.
let [viewAll, setViewAll] = React.useState(true);
let [checked, setChecked] = React.useState([true, true, true]);
// render calendars checkbox
let list = arr.map((value, index) => {
return (
<Checkbox
style={{ color: "black" }}
checked={checked[index]}
onChange={() => handleFilter(value, index)}
className="check-box"
>
{value.name}
</Checkbox>
);
});
const handleViewAll = () => {
setViewAll(!viewAll);
setChecked(() => checked.map(item => !viewAll));
};
const handleFilter = (value, index) => {
setViewAll(false);
setChecked(() =>
checked.map((_value, _index) => {
if (_index === index) return !checked[_index];
return checked[_index];
})
);
};
return (
<div className="App">
<Checkbox checked={viewAll} onChange={() => handleViewAll()}>
{checked}
</Checkbox>
{list}
</div>
);
codesandbox demo
You have to define checked array as a state value.
Right now your code is not firing render function because checked array is not props but also not state.