Having problems with searchbar logic - javascript

so I'm a beginner and I'm trying to build a simple phonebook, where you can add persons and filter them with their name.
My problem is, that when I'm trying to add a new contact, the new contact does't show until I write something in the searchbar.
When I deleted the search component, I was able to add contacts normally.
Here is my code so far:
(App.js)
import { useState } from 'react'
import Person from './components/Person'
const App = () => {
const [persons, setPersons] = useState([
{ name: 'Arto Hellas', number: '040-123456', id: 1 },
{ name: 'Ada Lovelace', number: '39-44-5323523', id: 2 },
{ name: 'Dan Abramov', number: '12-43-234345', id: 3 },
{ name: 'Mary Poppendieck', number: '39-23-6423122', id: 4 }
])
const [newName, setNewName] = useState('')
const [newNumber, setNewNumber] = useState('')
const[newSearch, setNewSearch] = useState('')
const[personsFilter, setPersonsFilter] = useState(persons)
const addContact = (event) => {
event.preventDefault()
const nameObject = {
name: newName,
number: newNumber,
id: persons.length+1,
}
const currentPerson = persons.filter((person) => person.name === newName);
if (currentPerson.length === 1) {
alert(`${newName} is already added to phonebook`)
} else {
setPersons(persons.concat(nameObject))
setNewName('')
setNewNumber('')
}
}
const handleNameChange = (event) => {
setNewName(event.target.value)
}
const handleNumberChange = (event) => {
setNewNumber(event.target.value)
}
const filterPersons = (event) => {
const searchName = event.target.value.toLowerCase()
setNewSearch(searchName)
const newPersons = persons.filter (
(person) =>
person.name.toLowerCase().search(searchName) !== -1
)
setPersonsFilter(newPersons)
}
return (
<div>
<h2>Phonebook</h2>
<div>
filter:
<input value={newSearch}
onChange={filterPersons}
/>
</div>
<form onSubmit={addContact}>
<div>
name: <input
value={newName}
onChange={handleNameChange}
/>
</div>
<div>
number: <input
value={newNumber}
onChange={handleNumberChange}/>
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
<ul>
<Person persons={personsFilter} />
</ul>
</div>
)
}
export default App
(Persons.js)
import React from "react";
const Person = ({ persons }) => {
return persons.map((person) =>
<li key={person.id}>{person.name}: <span>{person.number}</span></li>
)
}
export default Person
and index.js
import React from 'react';
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
I hope I explained my error clearly lol
Thanks in advance
Have great day <3

The problem with your code is when you set a new person it only updates the persons state not the personsFilter state. The personsFilter state is the one being used to show data to the DOM. The reason it works when you filter is because your function filterPersons() takes a copy of the persons state.
Instead of focusing on two states why not just use a single searchTerm state then just filter that when you map?
This is how I would do it:
App.js
import { useState } from 'react'
import Person from './components/Person'
const App = () => {
const [persons, setPersons] = useState([
{ name: 'Arto Hellas', number: '040-123456', id: 1 },
{ name: 'Ada Lovelace', number: '39-44-5323523', id: 2 },
{ name: 'Dan Abramov', number: '12-43-234345', id: 3 },
{ name: 'Mary Poppendieck', number: '39-23-6423122', id: 4 }
])
const [newName, setNewName] = useState('')
const [newNumber, setNewNumber] = useState('')
const[searchTerm, setSearchTerm] = useState('')
const addContact = (event) => {
event.preventDefault()
const nameObject = {
name: newName,
number: newNumber,
id: persons.length+1,
}
const currentPerson = persons.filter((person) => person.name === newName);
if (currentPerson.length === 1) {
alert(`${newName} is already added to phonebook`)
} else {
setPersons(persons.concat(nameObject))
setNewName('')
setNewNumber('')
}
}
const handleNameChange = (event) => {
setNewName(event.target.value)
}
const handleNumberChange = (event) => {
setNewNumber(event.target.value)
}
const filterPersons = (event) => {
setSearchTerm(event.target.value.toLowerCase())
}
return (
<div>
<h2>Phonebook</h2>
<div>
filter:
<input value={searchTerm}
onChange={filterPersons}
/>
</div>
<form onSubmit={addContact}>
<div>
name: <input
value={newName}
onChange={handleNameChange}
/>
</div>
<div>
number: <input
value={newNumber}
onChange={handleNumberChange}/>
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
<ul>
<Person persons={persons} searchTerm={searchTerm} />
</ul>
</div>
)
}
export default App
Person.js
const Person = ({ persons, searchTerm }) => {
return persons
.filter((person) => person.name.toLowerCase().includes(searchTerm))
.map((person) => (
<li key={person.id}>
{person.name}: <span>{person.number}</span>
</li>
));
};
export default Person;
https://codesandbox.io/s/silly-architecture-iu8qkz?file=/src/App.js

Related

How do I pass a component to an event handler ? (React JS)

I am trying to refactor my code into components. So right now I want to refactor the AddName component. This AddName component is to be pass to an event handler <form>(below the header Add Info). The problem right now is that now after refactoring the code, exporting and importing it and passing it to the event handler, I got an error in my VS code saying setPersons is assigned but never used. This is not something that I expect it to be since the other props in the AddName component have no problems but only this setPersons. I only know that to pass prop in between components, usually one will do it like this,
<AddName setPersons={setPersons}/>
In this situation it is not appropiate to use something like this right ? How can I fix this ? Below are my code,
App.js
import { useState } from 'react'
import AddName from './components/AddName'
const App = () => {
const [persons, setPersons] = useState([
{ name: 'Arto Hellas', number: '012-3456789', id: 1 },
{ name: 'Ada Lovelace', number: '013-4567890', id: 2 },
{ name: 'Dan Abramov', number: '011-1234567', id: 3 },
{ name: 'Mary Poppendieck', number: '016-5556789', id: 4 }
])
const [newName, setNewName] = useState('')
const [newNumber, setNewNumber] = useState('')
const [searchTerm, setSearchTerm] = useState("")
const handleNameChange = (event) => {
setNewName(event.target.value)
}
const handleNumberChange = (event) => {
setNewNumber(event.target.value)
}
return (
<div>
<h2> Phonebook </h2>
<input type="text" placeholder="Search..." onChange= {(event) => setSearchTerm(event.target.value)} />
<h2> Add Info </h2>
<form onSubmit={AddName}>
<div>
name: <input value={newName} onChange={handleNameChange} />
</div>
<div>
phonenumber: <input value={newNumber} onChange={handleNumberChange} />
</div>
<div>
<button type="submit"> add </button>
</div>
</form>
<h2> Numbers </h2>
<ul>
{persons.filter((person) => {
if (searchTerm === "") {
return person
} else if (person.name.toLowerCase().includes(searchTerm.toLowerCase())) {
return person
}
}).map((person) => {
return (
<li key={person.name}> {person.name} {person.number} </li>
);
})
}
</ul>
</div>
)
}
export default App
AddName.js
const AddName = (props, event) => {
event.preventDefault()
const nameObject = {
name: props.newName,
number: props.newNumber2
}
const isNameExist = (value) => props.persons.some(person => person.name.includes(value))
if (isNameExist(nameObject.name)) {
alert("name already exist")
} else {
props.setPersons(props.persons.concat(nameObject))
props.setNewName('')
props.setNewNumber('')
}
}
export default AddName
Why whould you pass a component to an onSubmit handler? Do you expect anything to render? I would have a simple function in the App component like this:
const addName = (event) => {
event.preventDefault()
const nameObject = {
name: newName,
number: newNumber2
}
const isNameExist = (value) => persons.some(person => person.name.includes(value))
if (isNameExist(nameObject.name)) {
alert("name already exist")
} else {
setPersons(persons.concat(nameObject))
setNewName('')
setNewNumber('')
}
}
...
...
<form onSubmit={addName}>
...
I would also extract the list in another component otherwise also the list would rerender at all the state changes (i.e. at all input change)

How to use React Select if the object properties's are not value and label?

import { Fragment, useState } from "react";
import Select from 'react-select';
let items = [
{
item: 1,
name: "tv"
},
{
item: 2,
name: "PC"
}
]
const Home = () => {
const [selectedValue, setSelectedValue] = useState(6)
const handleChange = obj => {
setSelectedValue(obj.item)
}
return (
<Fragment>
<div>Home page</div>
<p>Test React Select...</p>
<Select
value={items.find(x => x.item === selectedValue)}
options={items}
onChange={handleChange}
/>
<p>selected Value:...</p>
{selectedValue}
</Fragment>
)
}
export default Home;
you can pass the mapped array to "options" property:
options={items.map(({item, name}) => ({value: name, label: item}))}

In React Using Hook (useState) How i can add new row Like This Pattren? [duplicate]

This question already has answers here:
Correct modification of state arrays in React.js
(19 answers)
Closed 1 year ago.
how i can add more arrays in use state when i add a array by using a setAddtaskv array will be add but format is not true
import react,{useState,useEffect} from 'react'
const App = () => {
const [addtaskV,setAddtaskv] = useState([
{ id: 1, title: "Alabama",text:"Test" },
{ id: 2, title: "Georgia",text:"Test" },
{ id: 3, title: "Tennessee",text:"Test" }
]);
const addTask = () =>
{
const title = document.getElementById('title').value;
const text = document.getElementById('text').value;
}
return (
<div>
<input type="text" placeholder="Title" id="title"/>
<input type="text" placeholder="Write some text" id="text" />
<button onClick={addTask}>Add</button>
</div>
);`enter code here`
}
export default App;
On your addTask you could do this
const addTask = () => {
const title = document.getElementById("title").value;
const text = document.getElementById("text").value;
setAddtaskv((previousState) => [
...previousState,
{ id: addtaskV.length + 1, title: title, text: text }
]);
};
The spread on ...previousState means that you get whatever is already inside there and you add your new object to it.
Here's the full component
import react, { useState, useEffect } from "react";
const App = () => {
const [addtaskV, setAddtaskv] = useState([
{ id: 1, title: "Alabama", text: "Test" },
{ id: 2, title: "Georgia", text: "Test" },
{ id: 3, title: "Tennessee", text: "Test" }
]);
const addTask = () => {
const title = document.getElementById("title").value;
const text = document.getElementById("text").value;
setAddtaskv((previousState) => [
...previousState,
{ id: addtaskV.length + 1, title: title, text: text }
]);
};
return (
<>
<div>
<input type="text" placeholder="Title" id="title" />
<input type="text" placeholder="Write some text" id="text" />
<button onClick={addTask}>Add</button>
</div>
{addtaskV.map((task) => (
<div>
<span>
{task.id} - {task.title} - {task.text}
</span>
</div>
))}
</>
);
};
export default App;
EDIT: I also suggest that you do some research on how to properly develop React forms. Although your solution works, there are better ways of doing it. But you seem to be doing well 🤘
This is the general and correct way to do this in React JS
const [item, setItem] = useState({title: '', text: '', id: 0})
const [data, setData] = useState([])
// get inputs values
const handleInput = ({name, value}) => {
setItem({...item, [name]:value, id:data.length})
}
// new item object to `data`
const submit = () => {
if(item.title === '' || item.text === ''){
console.log('Please, fill empty spaces')
}else{
setData({...data, item})
}
}
<input value={item.title} name='title'
onChange={(e)=>handleInput(e.target)} />
<input value={item.text} name='text'
onChange={(e)=>handleInput(e.target)} />
<button onclick={()=>submit()}>Add new</button>
// render to screen
data &&
data.map(({title, text, id}) => <div key={id}>
<h1>{title}</h1>
<p>{text}</p>
</div>)
Please, do not use DOM Manipulation in React if not crucial

React: Add item in the beginning of list

I have a simple list where I'm displaying names. When I'm trying to add new name, it's added in the end of the list.
How do I manage to add new names on the top of the list ?
I tried push() and unshift() and every time I have the following error
list.map is not a function
Code :
https://codesandbox.io/s/gallant-minsky-0xgmg?file=/src/App.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
export default () => {
const initialList = [
{
id: "1",
name: "John"
},
{
id: "2",
name: "Doe"
},
{
id: "3",
name: "Seb"
}
];
const [list, setList] = React.useState(initialList);
const [name, setName] = React.useState("");
function handleChange(event) {
setName(event.target.value);
}
function handleAdd() {
const newList = list.concat({ name });
setList(newList);
setName("");
}
return (
<div>
<div>
<input type="text" value={name} onChange={handleChange} />
<button type="button" onClick={handleAdd}>
Add
</button>{" "}
</div>
<ul>
<div>
{list.map((item, index) => (
<li key={item.id}>
<div>{item.name}</div>
</li>
))}
</div>
</ul>
</div>
);
};
When ever you add an element to array by array.push(element) or array.unshift(element) it will always return the new length of the array.
const initialList = [
{
id: "1",
name: "John"
},
{
id: "2",
name: "Doe"
},
{
id: "3",
name: "Seb"
}
];
const [list, setList] = React.useState(initialList);
const [name, setName] = React.useState("");
function handleAdd() {
const newList = list.push({ name }); // returns new length
setList(newList);
setName("");
}
here it will set the newList value to 4 and instead of new List.
<div>
{list.map((item, index) => (
<li key={item.id}>
<div>{item.name}</div>
</li>
))}
</div>
when you try to apply map on number 4 it will return .map is not function because it works only with list of items not on numbers. same with the unshift.
You can use spread operator and have:
function handleAdd() {
const newList = [{ name }, ...list];
setList(newList);
setName("");
}
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
export default () => {
const initialList = [
{
id: "1",
name: "John"
},
{
id: "2",
name: "Doe"
},
{
id: "3",
name: "Seb"
}
];
const [list, setList] = React.useState(initialList);
const [name, setName] = React.useState("");
function handleChange(event) {
setName(event.target.value);
}
function handleAdd() {
const newList = [{ name }, ...list]; // simply create a new array and spread the previous one
setList(newList);
setName("");
}
return (
<div>
<div>
<input type="text" value={name} onChange={handleChange} />
<button type="button" onClick={handleAdd}>
Add
</button>{" "}
</div>
<ul>
<div>
{list.map((item, index) => (
<li key={item.id}>
<div>{item.name}</div>
</li>
))}
</div>
</ul>
</div>
);
};

React hooks form, setting default values from a reduced array doesn't populate, but manually enterring same object does

I am using react hooks forms, and I am trying to set the default values of a form that is outputted by mapping over an array and outputting the inputs in the form. I have reduced the array to an object like this {name0:"fijs",name1:"3838"...} and if I manually pass that in the default values it maps to my inputs and populates them. However if I enter them from the variable that is doing the reduce function it doesn't populate it. I think it is because on first render it is undefined. I have tried using a useEffect, but that didn't work so I am stuck.
This is the part of the code I am working on
const test = formState?.reduce((obj, item, idx) => {
return { ...obj, [`${item.name}${idx}`]: "fdsjfs" };
}, {});
const { register, handleSubmit, errors } = useForm({
defaultValues: test,
});
console.log(test);
and this is the whole thing
import { useQuery, gql, useMutation } from "#apollo/client";
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { useForm } from "react-hook-form";
const INPUT_VALUES = gql`
query GetInputValues {
allFormInputVals {
data {
name
_id
type
}
}
}
`;
const ADD_INPUT_VALUES = gql`
mutation AddInputValues(
$name: String!
$type: String!
$index: Int!
$ID: ID!
) {
createFormInputVal(
data: {
name: $name
type: $type
index: $index
formRoot: { connect: $ID }
}
) {
name
}
}
`;
const Home = () => {
const blankFormInput = {
__typename: "FormInputVal",
name: "test",
_id: uuidv4(),
type: "text",
};
const [formState, setFormState] = useState([blankFormInput]);
const [formStateVals, setFormStateVals] = useState(undefined);
const { loading, error, data } = useQuery(INPUT_VALUES);
const [createFormInputVal, { data: createInputData }] = useMutation(
ADD_INPUT_VALUES
);
useEffect(() => {
setFormState(data?.allFormInputVals?.data);
}, [data]);
const test = formState?.reduce((obj, item, idx) => {
return { ...obj, [`${item.name}${idx}`]: "fdsjfs" };
}, {});
const { register, handleSubmit, errors } = useForm({
defaultValues: test,
});
console.log(test);
const onSubmit = (data) => console.log(data);
console.log(errors);
const addInput = async () => {
const blanktext = {
__typename: "FormInputVal",
name: "Product Image",
_id: uuidv4(),
type: "text",
};
setFormState([...formState, { ...blanktext }]);
console.log(formState);
const res = await createFormInputVal({
variables: {
name: "test",
type: "text",
index: 0,
ID: "291541554941657608",
},
}).catch(console.error);
console.log(res);
};
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<input type="button" value="Add Form Input" onClick={addInput} />
{formState?.map((val, idx) => {
const nameId = `name${idx}`;
const typeId = `type-${idx}`;
return (
<div key={val._id}>
{val.type === "text" && (
<>
<label htmlFor={nameId}>{`${val.name} #${idx + 1}`}</label>
<input
type="text"
name={nameId}
id={nameId}
className={val.type}
ref={register()}
/>
{/* <label htmlFor={typeId}>{`Type #${idx + 1}`}</label>
<select name={typeId} id={typeId} className={val.type}>
{data.allFormInputVals.data.map((item) => {
return (
<option key={item._id} value={item.type}>
{item.type}
</option>
);
})}
</select> */}
</>
)}
</div>
);
})}
<button type="submit">Save Form</button>
</form>
</>
);
};
export default Home;
UPDATE: I have tried useEffect with a reset from the api, I thought this was the solution, but still no dice.
const { register, handleSubmit, errors, reset } = useForm();
useEffect(() => {
const result = test; // result: { firstName: 'test', lastName: 'test2' }
reset(result); // asynchronously reset your form values
}, [reset]);
UPDATE: I abstracted the Form to it;s own component, but it still does not work.
Form.js
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useQuery, gql, useMutation } from "#apollo/client";
import { v4 as uuidv4 } from "uuid";
const ADD_INPUT_VALUES = gql`
mutation AddInputValues(
$name: String!
$type: String!
$index: Int!
$ID: ID!
) {
createFormInputVal(
data: {
name: $name
type: $type
index: $index
formRoot: { connect: $ID }
}
) {
name
}
}
`;
export default function Form({ formState, setFormState }) {
const test = formState?.reduce((obj, item, idx) => {
return { ...obj, [`${item.name}${idx}`]: "fdsjfs" };
}, {});
console.log(test);
const { register, handleSubmit, errors } = useForm({ defaultValues: test });
const [formStateVals, setFormStateVals] = useState(undefined);
// console.log(test);
const onSubmit = (data) => console.log(data);
console.log(errors);
const addInput = async () => {
const blanktext = {
__typename: "FormInputVal",
name: "Product Image",
_id: uuidv4(),
type: "text",
};
setFormState([...formState, { ...blanktext }]);
console.log(formState);
const res = await createFormInputVal({
variables: {
name: "test",
type: "text",
index: 0,
ID: "291541554941657608",
},
}).catch(console.error);
console.log(res);
};
const [createFormInputVal, { data: createInputData }] = useMutation(
ADD_INPUT_VALUES
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="button" value="Add Form Input" onClick={addInput} />
{formState?.map((val, idx) => {
const nameId = `name${idx}`;
const typeId = `type-${idx}`;
return (
<div key={val._id}>
{val.type === "text" && (
<>
<label htmlFor={nameId}>{`${val.name} #${idx + 1}`}</label>
<input
type="text"
name={nameId}
id={nameId}
className={val.type}
ref={register()}
/>
{/* <label htmlFor={typeId}>{`Type #${idx + 1}`}</label>
<select name={typeId} id={typeId} className={val.type}>
{data.allFormInputVals.data.map((item) => {
return (
<option key={item._id} value={item.type}>
{item.type}
</option>
);
})}
</select> */}
</>
)}
</div>
);
})}
<button type="submit">Save Form</button>
</form>
);
}
index.js
import { useQuery, gql, useMutation } from "#apollo/client";
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import Form from "../components/Form";
const INPUT_VALUES = gql`
query GetInputValues {
allFormInputVals {
data {
name
_id
type
}
}
}
`;
const Home = () => {
const blankFormInput = {
__typename: "FormInputVal",
name: "test",
_id: uuidv4(),
type: "text",
};
const [formState, setFormState] = useState([blankFormInput]);
const { loading, error, data } = useQuery(INPUT_VALUES);
useEffect(() => {
const formData = data?.allFormInputVals?.data;
setFormState(formData);
}, [data]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<>
<Form formState={formState} setFormState={setFormState} />
</>
);
};
export default Home;
You could extract the form to its own component and only render it when the data is fetched. This way, when you use useForm in the child component, the default values will be set properly.
const Home = () => {
const { loading, error, data } = useQuery(INPUT_VALUES)
const blankFormInput = {
__typename: "FormInputVal",
name: "test",
_id: uuidv4(),
type: "text",
}
const [formState, setFormState] = useState([blankFormInput])
// other code
if (loading) {
return <p>Loading...</p>
}
return <MyForm defaultValues={formState} />
}
If you don't want to change the structure, you could set the input values using setValue when the data is ready.
useEffect(() => {
const formData = data?.allFormInputVals?.data
setFormState(formData)
formData?.forEach((item, idx) => {
setValue(`${item.name}${idx}`, 'whatever')
})
}, [data])

Categories