I'm trying to figure out on how to remove duplicates from an array of objects when entering multiple input skill tags. Maybe I am missing some key points here
const SkillsTags = ({ skillTags, setSkillTags, skill }) => {
const removeSkillTag = (i) => {
setSkillTags([...skillTags.filter((_, index) => index !== i)]);
};
const addSkillTag = (e) => {
e.preventDefault();
var updatedSkills = [...skillTags];
if (skill.current.value.trim().length !== 0) {
updatedSkills = [...skillTags, { SKILL_NAME: skill.current.value }];
}
setSkillTags(updatedSkills);
skill.current.value = "";
};
return (
<>
<label htmlFor="Skills">Skills:</label>
<div className="input-group">
<input
type="text"
placeholder="Enter Skills (Press Enter to add)"
onKeyPress={(e) => (e.key === "Enter" ? addSkillTag(e) : null)}
ref={skill}
/>
<button className="btn btn-outline-primary" onClick={addSkillTag}>
Add
</button>
</div>
<ul style={{ height: "12.5rem" }}>
{skillTags.map((val, index) => {
return (
<li key={index}>
{val.SKILL_NAME}
<button type="button" onClick={() => removeSkillTag(index)}>
Remove
</button>
</li>
);
})}
</ul>
</>
);
};
Demo here: https://codesandbox.io/s/add-skill-tags-nitthd?file=/src/SkillsTags.js
I think you are trying to filter duplicates, You can achieve this with simple Javascript.
const addSkillTag = (e) => {
e.preventDefault();
const isDuplicate = skillTags.some(function (item, idx) {
return item.SKILL_NAME === skill.current.value;
});
if (skill.current.value.trim().length !== 0) {
if (!isDuplicate) {
skillTags = [...skillTags, { SKILL_NAME: skill.current.value }];
}
}
setSkillTags(skillTags);
skill.current.value = "";
};
Related
I have to make a dynamic survey where each next question comes from the previous one.
I want based on clicks to display/hide each question. I am doing this with style.display = "none" or "".
This the components' code.
As of now when I click the button, the isVisible array gets populated with next question's Id.
function refreshResults(questions, currentqId, val, isVisible) {
var nextqId = val.target.value;
// var currentqIdIdx = questions.findIndex(q => q.qID === currentqId)
var nextqIdIdx = questions.findIndex(q => q.qID === nextqId);
var ret = isVisible;
if (!isVisible.includes(nextqId)) {
ret.push(nextqId);
}
return ret;
}
const Questionnaire = () => {
const navigate = useNavigate();
let params = useParams();
let questionnaireId = params.questionnaireId;
// const [questionnaire, setQuestionnaire] = useState([]);
var questionnaire = test_input;
let questions = questionnaire.questions;
const [isVisible, setIsVisible] = useState([]);
useEffect(() => {
const setup = () => {
setIsVisible([questions[0].qID]);
// setIsVisible("");
}
setup();
}, [questions]);
return (
<div className="Questionnaire">
{
questions.forEach(question => {
if (isVisible.includes(question.qID)) {
question.style = { display: "" };
} else {
question.style = { display: "none" };
}
})
}
<>
{
questions.map((q) => (
<div className="card" style={q.style} key={q.qID}>
<div className="card-body" >
<h5 className="card-title">- {q.qtext}</h5>
<div className="buttons col">
{q.options.map(opt => (
<div key={opt.optID} className="form-check">
<input className="form-check-input" type="radio" name={`flexRadio${q.qID}`} value={opt.nextqID} onClick={clickValue => setIsVisible(refreshResults(questions, q.qID, clickValue, isVisible))} />
<label className="form-check-label" >
{opt.opttxt}
</label>
</div>
))
}
</div>
</div >
</div>
))
}
</>
<div className="submitButton"><input className="btn btn-primary" type="submit" value="Submit" /></div>
</div>
)
}```
i trying to make a history.push on button click
i have this search bar that will show the names of doctors when serched {suggestion.firstname}
i am trying to pass {suggestion.id } as url when cliked on the li corresponding
but here when i type and if the {suggestion.firstname} first letter comes then it automaticaly is redirecting when typing in the input field.
finddoctor is working like onchange funtion but i have written onclick funtion
function finddoctor(e) {
console.log(e);
history.push(`/detiled/${e} `);
}
const onChange = (event) => {
const value = event.target.value;
setInputValue(value);
setShowResults(false);
const filteredSuggestions = suggestions.filter(
(suggestion) =>
suggestion.firstname
.toString()
.toLowerCase()
.includes(value.toLowerCase()) ||
suggestion.id.toString().toLowerCase().includes(value.toLowerCase())
);
setFilteredSuggestions(filteredSuggestions);
setDisplaySuggestions(true);
};
const onSelectSuggestion = (index) => {
setSelectedSuggestion(index);
setInputValue(filteredSuggestions[index]);
setFilteredSuggestions([]);
setDisplaySuggestions(false);
};
const SuggestionsList = (props) => {
const {
suggestions,
inputValue,
onSelectSuggestion,
displaySuggestions,
selectedSuggestion,
} = props;
if (inputValue && displaySuggestions) {
if (suggestions.length > 0) {
return (
<ul className="suggestions-list" style={styles.ulstyle}>
{suggestions.map((suggestion, index) => {
const isSelected = selectedSuggestion === index;
const classname = `suggestion ${isSelected ? "selected" : ""}`;
return (
<>
<li
style={styles.listyle}
onClick={finddoctor(suggestion.id)}
key={index}
className={classname}
>
{suggestion.firstname}
</li>
</>
);
})}
</ul>
);
} else {
return <div>No suggestions available...</div>;
}
}
return <></>;
};
useEffect(() => {
axios
.get("admin-panel/all-doctors-list/")
.then((res) => {
const data = res.data;
setShowSerch(data);
});
}, []);
return (
<>
<div className="note-container" style={styles.card}>
<div style={styles.inner}>
<p style={{ textAlign: "left" }}>Search Doctors</p>
<form className="search-form" style={{}}>
{showResults ? (
<FontAwesomeIcon
style={{ marginRight: "-23px" }}
icon={faSearch}
/>
) : null}
<input
onChange={onChange}
value={inputValue}
style={styles.input}
type="Search"
/>
<SuggestionsList
inputValue={inputValue}
selectedSuggestion={selectedSuggestion}
onSelectSuggestion={onSelectSuggestion}
displaySuggestions={displaySuggestions}
suggestions={filteredSuggestions}
/>
</form>
</div>
</div>
</>
);
};
change it do this, and it should work.
<li
style={styles.listyle}
onClick={() => finddoctor(suggestion.id)}
key={index}
>
{suggestion.firstname}
</li>
i trying to make a history.push on button click
i have this search bar that will show the names of doctors when serched {suggestion.firstname}
i am trying to pass {suggestion.id } as url when cliked on the li corresponding
when clicked on li no call going to finddoctor
function finddoctor(e) {
console.log(e);
history.push(`/detiled/${e} `);
}
const onChange = (event) => {
const value = event.target.value;
setInputValue(value);
setShowResults(false);
const filteredSuggestions = suggestions.filter(
(suggestion) =>
suggestion.firstname
.toString()
.toLowerCase()
.includes(value.toLowerCase()) ||
suggestion.id.toString().toLowerCase().includes(value.toLowerCase())
);
setFilteredSuggestions(filteredSuggestions);
setDisplaySuggestions(true);
};
const onSelectSuggestion = (index) => {
setSelectedSuggestion(index);
setInputValue(filteredSuggestions[index]);
setFilteredSuggestions([]);
setDisplaySuggestions(false);
};
const SuggestionsList = (props) => {
const {
suggestions,
inputValue,
onSelectSuggestion,
displaySuggestions,
selectedSuggestion,
} = props;
if (inputValue && displaySuggestions) {
if (suggestions.length > 0) {
return (
<ul className="suggestions-list" style={styles.ulstyle}>
{suggestions.map((suggestion, index) => {
const isSelected = selectedSuggestion === index;
const classname = `suggestion ${isSelected ? "selected" : ""}`;
return (
<li
style={styles.listyle}
onClick={()=> finddoctor(suggestion.id)}
key={index}
className={classname}
>
{suggestion.firstname}
</li>
);
})}
</ul>
);
} else {
return <div>No suggestions available...</div>;
}
}
return <></>;
};
useEffect(() => {
axios
.get("admin-panel/all-doctors-list/")
.then((res) => {
const data = res.data;
setShowSerch(data);
});
}, []);
return (
<>
<div className="note-container" style={styles.card}>
<div style={styles.inner}>
<p style={{ textAlign: "left" }}>Search Doctors</p>
<form className="search-form" style={{}}>
{showResults ? (
<FontAwesomeIcon
style={{ marginRight: "-23px" }}
icon={faSearch}
/>
) : null}
<input
onChange={onChange}
value={inputValue}
style={styles.input}
type="Search"
/>
<SuggestionsList
inputValue={inputValue}
selectedSuggestion={selectedSuggestion}
onSelectSuggestion={onSelectSuggestion}
displaySuggestions={displaySuggestions}
suggestions={filteredSuggestions}
/>
</form>
</div>
</div>
</>
);
};
Are you using "react-router-dom" in your project?
In this case, you should use the history object in a specific way. For example, you can get it with the useHistory hook.
import { useHistory } from "react-router-dom";
const SuggestionsList = ({
suggestions,
inputValue,
displaySuggestions,
selectedSuggestion,
}) => {
let history = useHistory();
const finddoctor = (e) => {
console.log(e);
history.push(`/detiled/${e} `)
};
if (inputValue && displaySuggestions) {
if (suggestions.length > 0) {
return (
<ul className="suggestions-list">
{suggestions.map((suggestion, index) => {
return (
<li
onClick={() => finddoctor(suggestion.id)}
key={index}
>
{suggestion.firstname}
</li>
)
})}
</ul>
)
} else {
return <div>No suggestions available...</div>
}
}
return <></>
};
i want to optimize my react App by testing with a large list of li
Its a simple todo List.
By exemple, when click on a li, task will be line-through, and check icon will be green. This simple action is very slow with a large list because, the whole list is re render.
How to do this with React Hooks?
function App() {
const [list, setList] = useState([]);
const [input, setInput] = useState("");
const inputRef = useRef(null);
useEffect(() => inputRef.current.focus(), []);
//Pseudo Big List
useEffect(() => {
const test = [];
let done = false;
for (let i = 0; i < 5000; i++) {
test.push({ task: i, done });
done = !done;
}
setList(test);
}, []);
const handlerSubmit = (e) => {
e.preventDefault();
const newTask = { task: input, done: false };
const copy = [...list, newTask];
setList(copy);
setInput("");
};
const checkHandler = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy[index].done = !copy[index].done;
setList(copy);
};
const suppression = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy.splice(index, 1);
setList(copy);
};
const DisplayList = () => {
return (
<ul>
{list.map((task, index) => (
<Li
key={index}
task={task}
index={index}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
);
};
//JSX
return (
<div className='App'>
<h1>TODO JS-REACT</h1>
<form id='form' onSubmit={handlerSubmit}>
<input
type='text'
placeholder='Add task'
required
onChange={(e) => setInput(e.target.value)}
value={input}
ref={inputRef}
/>
<button type='submit'>
<i className='fas fa-plus'></i>
</button>
</form>
{list.length === 0 && <div id='noTask'>No tasks...</div>}
<DisplayList />
</div>
);
}
export default App;
Li component
import React from "react";
export default function Li(props) {
return (
<li
onClick={(e) => props.checkHandler(e, props.index)}
className={props.task.done ? "line-through" : undefined}
>
{props.task.task}
<span className='actions'>
<i className={`fas fa-check-circle ${props.task.done && "green"}`}></i>
<i
className='fas fa-times'
onClick={(e) => props.suppression(e, props.index)}
></i>
</span>
</li>
);
}
CodeSandbox here: https://codesandbox.io/s/sad-babbage-kp3md?file=/src/App.js
I had the same question, as #Dvir Hazout answered, I followed this article and made your code the changes you need:
function App() {
const [list, setList] = useState([]);
const { register, handleSubmit, reset } = useForm();
//Pseudo Big List
useEffect(() => {
const arr = [];
let done = false;
for (let i = 0; i < 20; i++) {
arr.push({ id: uuidv4(), task: randomWords(), done });
done = !done;
}
setList(arr);
}, []);
const submit = ({ inputTask }) => {
const newTask = { task: inputTask, done: false, id: uuidv4() };
setList([newTask, ...list]);
reset(); //clear input
};
const checkHandler = useCallback((id) => {
setList((list) =>
list.map((li) => (li.id !== id ? li : { ...li, done: !li.done }))
);
}, []);
const suppression = useCallback((id) => {
setList((list) => list.filter((li) => li.id !== id));
}, []);
//JSX
return (
<div className="App">
<h1>TODO JS-REACT</h1>
<form onSubmit={handleSubmit(submit)}>
<input type="text" {...register("inputTask", { required: true })} />
<button type="submit">
<i className="fas fa-plus"></i>
</button>
</form>
{list.length === 0 && <div id="noTask">No tasks...</div>}
<ul>
{list.map((task, index) => (
<Li
key={task.id}
task={task}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
</div>
);
}
Li component
import React, { memo } from "react";
const Li = memo(({ task, suppression, checkHandler }) => {
// console.log each time a Li component re-rendered
console.log(`li ${task.id} rendered.`);
return (
<li
onClick={(e) => checkHandler(task.id)}
className={task.done ? "line-through" : undefined}
>
{task.task}
<span className="actions">
<i className={`fas fa-check-circle ${task.done && "green"}`}></i>
<i className="fas fa-times" onClick={(e) => suppression(task.id)}></i>
</span>
</li>
);
});
export default Li;
You can check it live here
I know it's probably late for your question, but may help others ;)
You can use React.memo and wrap the Li component. This will cache the instances of the Li component based on shallow comparison. Read more in the docs
Otherwise, if you don't need the state in the container, you can keep it locally in the Li component and then it won't cause a whole list rerender.
I was just testing around building a dummy todo list and was trying to figure out something. While setting the new state with the new task object that includes an id and a text. Well everything works well just my issue when I console.log(allTasks) it starts only to show the array of data after I have added the second task ?
const SearchInput = () => {
const [taskValue, setTaskValue] = useState("");
const [allTasks, setAllTasks] = useState([]);
const handleChange = (e) => {
setTaskValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (taskValue !== "") {
setAllTasks([
...allTasks,
{ id: allTasks.length + 1, text: taskValue.trim() },
]);
}
setTaskValue("");
console.log(allTasks);
};
return (
<>
<Form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Add a task..."
value={taskValue}
onChange={handleChange}
/>
<button>Submit the Task</button>
</Form>
<div>
{allTasks.length <= 0 ? (
<p>No tasks</p>
) : (
<ul>
{allTasks.map((task) => (
<li key={task.id}> {task.text} </li>
))}
</ul>
)}
</div>
</>
);
};
Here you get updated value and there is a conditional change I hope you will like.
Thanks
const SearchInput = () => {
const [taskValue, setTaskValue] = React.useState("");
const [allTasks, setAllTasks] = React.useState([]);
const handleChange = (e) => {
setTaskValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (taskValue !== "") {
setAllTasks([
...allTasks,
{ id: allTasks.length + 1, text: taskValue.trim() },
]);
}
setTaskValue("");
};
console.log(allTasks);
return (
<>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Add a task..."
value={taskValue}
onChange={handleChange}
/>
<button>Submit the Task</button>
</form>
<div>
{!allTasks.length && <p>No tasks</p>}
{!!allTasks.length &&
<ul>
{allTasks.map((task) => (
<li key={task.id}> {task.text} </li>
))}
</ul>
}
</div>
</>
);
};
According to the docs, setState is async in nature, which means it will execute only after execution of all synchronous code. And setState takes a callback as the second parameter which you can use to log it as expected.
setAllTasks([
...allTasks,
{ id: allTasks.length + 1, text: taskValue.trim() },
], ()=>{
console.log(alltasks)
});
Reference