Error - Rendering multiple instances in react search component - arrays

I am building a search component where the user types in the value and it filters through some dummy data and renders the result for the user to see. The problem is when I type in one character in the search field i get the entire array of data on every character I type. So for example there are 4 data strings in the array so if I type in two characters in the search bar then my result is 8.
Here is the code and the problem duplicated: Any help is appreciated.

The answer is below, basically, the code was loading in an extra map from CompanyInfoList component. I also had a useContext that was not needed in the same component, so i removed it and replaced results.map from useContext setState hook to filtered.map. filtered was the prop that needed to be passed down from CompSearch to CompanyInfoList. The last change I made was to delete the RenderList component and remove return RenderList in the serachList component to CompanyInfoList
CompSearch.js
import React, {useContext, useState} from "react";
import CompanyInfoList from "./CompanyInfoList";
import { CompListContext } from "./CompListContext";
const CompSerach = () => {
// const [input, setInput] = useState('');
const [results, setResults] = useContext(CompListContext);
const [searchField, setSearchField] = useState("");
const [searchShow, setSearchShow] = useState(false);
const filtered = results.filter((res) => {
return (
res.name.toLowerCase().includes(searchField.toLowerCase()) ||
res.employee.toLowerCase().includes(searchField.toLowerCase()) ||
res.date.toLowerCase().includes(searchField.toLowerCase()) ||
res.amount.toLowerCase().includes(searchField.toLowerCase())
);
});
const handleChange = (e) => {
setSearchField(e.target.value);
if (e.target.value === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
};
function searchList() {
if (searchShow) {
return <CompanyInfoList filtered={filtered} />;
}
}
return (
<>
<div>
<input
type="search"
placeholder="search Company Title"
// input="input"
// value={input}
onChange={handleChange}
// onChange={handleChange}
/>
</div>
{searchList()}
</>
);
};
export default CompSerach;
CompanyInfoList.js
import Results from "./Results";
const CompanyInfoList = ({ filtered }) => {
const fltr = filtered.map((result) => (
<Results
key={result.id}
name={result.name}
employee={result.employee}
date={result.date}
amount={result.amount}
/>
));
return <div>{fltr}</div>;
};
export default CompanyInfoList;

Related

useEffect not working saving data in my local storage when I refresh my page of the todo list

I am new to react and creating my first react app. not sure why the todo list is not saved even though I have used localStorage set and get methods. I am also getting error about the key in my map method. I can't seen to find any issues on my own with the code.Below is the code of the todo list App
import TodoList from "./TodoList";
import {v4 as uuid} from 'uuid'
function App() {
const [todos,setTodos] = useState([{}]);
const inputRef = useRef();
const LOCAL_STORAGE_KEY = "todoapp"
useEffect(() =>{
const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
if(storedTodos){
setTodos(storedTodos)}
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY,JSON.stringify(todos))
}, [todos])
function toggleTodo(id){
const newTodos= [...todos]
const todo = newTodos.find(todo => todo.id === id)
todo.complete = !todo.complete
setTodos(newTodos)
}
function handleAdd(e) {
const name = inputRef.current.value;
if(name === "")return
setTodos(prevTodos => {
return [...prevTodos,{id:uuid(),name:name,complete:false}]
})
inputRef.current.value = null;
}
function handleClearTodos(){
const newTodos = todos.filter(todo=>!todo.complete)
setTodos(newTodos)
}
return (
<>
<h1>Chores!!</h1>
<TodoList todo={todos} toggleTodo ={toggleTodo} />
<input ref={inputRef} type="text" />
<button onClick ={handleAdd}>Add todo</button>
<button onClick={handleClearTodos}>Clear todo </button>
<div> {todos.filter(todo => !todo.complete).length} left todo</div>
</>
)
}
export default App;
import Todo from './Todo'
export default function TodoList({todo,toggleTodo}) {
return (
todo.map((todo)=> {
return <Todo key={todo.id} todo={todo} toggleTodo={toggleTodo} />
})
)
}
This:
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY,JSON.stringify(todos))
}, [todos])
Is probably taking the initial state of todos on the first render (empty array) and overwriting what data was in their with that initial state.
You might think the previous effect counters this since todos is populated from local storage -- but it doesn't, because on that initial render pass, the second effect will only see the old value of todos. This seems counter-intuitive at first. But it's because whenever you call a set state operation, it doesn't actual change the value of todos immediately, it waits until the render passes, and then it changes for the next render. I.e. it is, in a way, "queued".
For the local storage setItem, you probably want to do it in the event handler of what manipulates the todos and not in an effect. See the React docs.
import TodoList from "./TodoList";
import {v4 as uuid} from 'uuid'
function App() {
const [todos,setTodos] = useState([{}]);
const inputRef = useRef();
const LOCAL_STORAGE_KEY = "todoapp"
const storeTodos = (todos) => {
localStorage.setItem(LOCAL_STORAGE_KEY,JSON.stringify(todos))
setTodos(todos)
}
useEffect(() =>{
const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
if(storedTodos){
setTodos(storedTodos)}
}, [])
function toggleTodo(id){
const newTodos= [...todos]
const todo = newTodos.find(todo => todo.id === id)
todo.complete = !todo.complete
storeTodos(newTodos)
}
function handleAdd(e) {
const name = inputRef.current.value;
if(name === "")return
storeTodos(prevTodos => {
return [...prevTodos,{id:uuid(),name:name,complete:false}]
})
inputRef.current.value = null;
}
function handleClearTodos(){
const newTodos = todos.filter(todo=>!todo.complete)
storeTodos(newTodos)
}
return (
<>
<h1>Chores!!</h1>
<TodoList todo={todos} toggleTodo ={toggleTodo} />
<input ref={inputRef} type="text" />
<button onClick ={handleAdd}>Add todo</button>
<button onClick={handleClearTodos}>Clear todo </button>
<div> {todos.filter(todo => !todo.complete).length} left todo</div>
</>
)
}
export default App;
As the for the key error, we'd need to see the code in TodoList, but you need to ensure when you map over them, that the id property of each todo is passed to a key prop on the top most element/component within the map callback.

React get value from key:value array

Beginner question. I know this is a simple question but I haven't been able to get this to work. I'm passing an object which holds an array of k:v pairs to a component. Eventually this props will contain multiple k:v pairs, but for now I'm just passing the one.
[{goal: 20000}]
In the component I'm trying to grab the value, 20000, so I can display it on screen. I can't seem to get just the number. If I look at props.goal I get the entire k:v.
[{goal: 20000}]
If I try props[0].goal I get 'TypeError: undefined is not an object (evaluating 'props[0].goal')'
What am I missing? Thanks for any help.
Update:
Here is the entire code for the component in question.
import { React, useState } from "react";
import Form from "react-bootstrap/Form";
import { Row, Col, Button } from "react-bootstrap";
import "./../css/Goal.css";
const Goal = (props) => {
// const [goal, setGoal] = useState("");
const [record, setRecord] = useState("");
const monthlyGoal = 2;
console.log("props[0]");
console.log(props[0]); //undefined
console.log("props");
console.log({ props }); //See below
props: Object
goal: Object
goals: [{goal: 20000}] (1)
const handleInput = (event) => {
console.log(event);
event.preventDefault();
setRecord(event.target.value);
console.log(record);
};
const defaultOptions = {
significantDigits: 2,
thousandsSeparator: ",",
decimalSeparator: ".",
symbol: "$",
};
const formattedMonthlyGoal = (value, options) => {
if (typeof value !== "number") value = 0.0;
options = { ...defaultOptions, ...options };
value = value.toFixed(options.significantDigits);
const [currency, decimal] = value.split(".");
return `${options.symbol} ${currency.replace(
/\B(?=(\d{3})+(?!\d))/g,
options.thousandsSeparator
)}${options.decimalSeparator}${decimal}`;
};
return (
<Form>
<Row className="align-items-center flex">
<Col sm={3} className="goal sm={3}">
<Form.Control
id="inlineFormInputGoal"
placeholder="Goal"
// onChange={(e) => setGoal(e.target.value)}
/>
<Button type="submit" className="submit btn-3" onSubmit={handleInput}>
Submit
</Button>
</Col>
<Col>
<h1 className="text-box">
Goal: {formattedMonthlyGoal(monthlyGoal)}
</h1>
</Col>
</Row>
</Form>
);
};
export default Goal;
Update 2:Here is the parent component:
import React, { useEffect, useState } from "react";
import Goal from "./Goal";
import axios from "axios";
const Dashboard = () => {
const [dashboardinfo, setdashboardinfo] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const data = (await axios.get("/api/goals/getgoals")).data;
setdashboardinfo(data);
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
return (
<React.Fragment>
<Goal dashboardinfo={dashboardinfo} />
</React.Fragment>
);
};
export default Dashboard;
If you get an object like the following from console logging destructured props:
{
dashboardinfo: {goals: [{goal: 20000}]}
}
You need to use props.dashboardinfo.goals[0].goal to get the value.
Your props contains the object "dashboardinfo" so you need to do
props.dashboardinfo.goals[0].goal
or a better way is to destructure your props object like this
const Goal = ({dashboardinfo: { goals }}) => {
...
goals[0].goal
...
}
I believe I've resolved my issue. It wasn't so much a problem with accessing the key:value as I thought, because when the page was initialized I was able to grab the value and display it fine. However, when I refreshed the page I lost all of the props data and that resulted in an error. I tracked it down to the useState didn't seem to be updating the value before I was trying to read it. So I added a useEffect in the child component.
const Goal = (props) => {
const [goal, setgoal] = useState([]);
useEffect(() => {
setgoal(props.goal);
console.log("the goal", goal);
}, [props.goal, goal]);
...
This seems to have worked as I'm getting the information I want and not getting any errors when I refresh. This may not be the ideal way to go about this but it is working.

Rendering Items Array in localStorage to Unordered List

When I click DisplayBtn() it should sets the display state to true and display myLeads Array from localStorage. localStorage contains MyLeads Array and I've used a map() in an attempt to fetch items and place them in an unordered list. I've done this before on arrays and it has worked but its not currently working.
Basically, I just want them items in localStorage to render in an unordered list. I've attempted several approaches to solve this issue my latest error message is 'Cannot read properties of null (reading 'map')'
import {useState} from 'react';
import List from './components/List'
import { SaveBtn } from './components/Buttons';
function App() {
const [myLeads, setMyLeads] = useState([]);
const [leadValue, setLeadValue] = useState({
inputVal: ""
})
const [display, setDisplay] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setLeadValue((prev) => {
return {
...prev,
[name]: value,
};
});
};
const [localItems, setLocalItems] = useState(
JSON.parse(localStorage.getItem("myLeads"))
);
const displayLocalItems = localItems.map((item) => {
return <List key={item} val={item}/>
})
const saveBtn = () => {
setMyLeads(prev => [...prev, leadValue.inputVal]);
localStorage.setItem("myLeads", JSON.stringify(myLeads))
setLocalItems((prevItems) => [...prevItems, leadValue.inputVal]);
setDisplay(false);
};
const displayBtn = () => {
setDisplay(true)
};
return (
<main>
<input
name="inputVal"
value={leadValue.inputVal}
type="text"
onChange={handleChange}
required
/>
<SaveBtn saveBtn={saveBtn} />
<button onClick={displayBtn}>Display Leads</button>
{display && (
{displayLocalItems}
)
}
</main>
);
}
export default App;
You can do this:
const [localItems, setLocalItems] = useState(JSON.parse(localStorage.getItem("myLeads")) || []);
So if the local storage is empty you initialize your state to an empty array, which can be safely mapped.

Array in a react state is not updating correctly

I basically try to update filter the items from the all locations array to dropDownLocation array, but it is not updating correctly. on the first change in input field it wrongly update the array and the second change it does not update it.
import logo from "./logo.svg";
import "./App.css";
import { useState, useEffect } from "react";
function App() {
// location entered by the user
const [location, setlocation] = useState("");
const [allLocations, setallLocations] = useState(["F-1", "F-2", "G-1", "G-2"]);
const [dropDownLocations, setdropDownLocations] = useState([]);
const filterLocations = (userInput) => {
console.log("user input ", location);
allLocations.map((i) => {
if (i.includes(location)) {
console.log("true at ", i);
setdropDownLocations([...dropDownLocations, i]);
} else {
setdropDownLocations([]);
}
});
console.log("after map ", dropDownLocations);
};
return (
<div className="App">
<div>
<input
value={location}
onChange={(e) => {
setlocation(e.target.value);
filterLocations(e.target.value);
}}
/>
<ul>
{dropDownLocations.map((i) => (
<li key={i}>{i}</li>
))}
</ul>
</div>
</div>
);
}
export default App;
You don't need to make that complicated, Just filter the array based on the user's input
const filterLocations = (userInput) => {
setdropDownLocations(
allLocations.filter((location) => location.includes(userInput))
);
};
I made it simpler for you in this working example:
The setState is an asynchronous function and so your current implementation isn't working properly as you are trying to read the state before it is updated.
Update your filterLocations function like following:
const filterLocations = (e) => {
const location = e.target.value;
const filteredLocation = allLocations.filter(i => i.includes(location));
setlocation(location);
setdropDownLocations(filteredLocation)
};
And update your input tag like following:
<input value={location} onChange={filterLocations} />
It is not working because for each location, you are setting dropdown location, and if it doesn't contain the location, you set it to empty array [] again.
allLocations.map((i) => {
if (i.includes(location)) {
console.log("true at ", i);
setdropDownLocations([...dropDownLocations, i]);
} else {
setdropDownLocations([]);
}
});
A better approach would be:
setDropDownLocation([...allLocations].filter((i) => i.includes(userInput))
There is some mistakes what you have done, I have made some changes try to run the code which I have written.
import logo from "./logo.svg";
import "./App.css";
import { useState, useEffect } from "react";
const ALL_LOCATIONS = ['F-1', 'F-2', 'G-1', 'G-2'];
function App() {
// location entered by the user
const [location, setLocation] = useState("");
const [dropDownLocations, setDropDownLocations] = useState([]);
function onLocationInputChange(event){
setLocation(event.target.value);
setDropDownLocations(ALL_LOCATIONS.filter((item)=>item.includes(event.target.value)))
}
return (
<div className="App">
<div>
<input value={location} onChange={onLocationInputChange} />
<ul>
{dropDownLocations.map((loc) => (
<li key={`${location}-${loc}`}>{loc}</li>
))}
</ul>
</div>
</div>
);
}
export default App;
This is caused by the fact that your onChange handler is defined right in the JSX, causing React to recreate a new function at every render (same goes for filterLocations one).
You should always try to extract every single piece of JS logic outside of the component, or at least memoize them, here's how:
import React, { useState, useCallback } from "react";
import logo from "./logo.svg";
import "./App.css";
const ALL_LOCATIONS = ['F-1', 'F-2', 'G-1', 'G-2'];
function App() {
// location entered by the user
const [location, setLocation] = useState("");
// locations shown to the user in dropdown (filterable)
const [dropDownLocations, setDropDownLocations] = useState([]);
const onLocationInputChange = useCallback(
(ev) => {
// In case no target passed to callback, do nothing
if (!ev || !ev.target || !ev.target.value) {
return;
}
const userInput = ev.target.value;
// Filter so that if user input matches part of the location
// it gets not filtered out
setDropDownLocations([
...ALL_LOCATIONS.filter(
(loc) =>
loc.startsWith(userInput) ||
loc.endsWith(userInput) ||
loc.indexOf(userInput) !== -1
),
]);
// Finally update the location var
setLocation(userInput);
},
[setDropDownLocations]
);
return (
<div className="App">
<div>
<input value={location} onChange={onLocationInputChange} />
<ul>
{dropDownLocations.map((loc) => (
<li key={`${location}-${loc}`}>{loc}</li>
))}
</ul>
</div>
</div>
);
}
export default App;

how to execute on click function to show filtered results from search bar in react

I am working on a component where the user searches a term and it is returned to them through a filter. I am using useContext hook to pass data from db via axios. I would like to use the button in the CompSearch component to render the results rather than having them show up on a keystroke. My question is how do I render the results via button click?
Here is the code
Follow these steps to achieve that.
Change the input element into an uncontrolled component.
Get the value using reference in React.
import React, { useContext, useRef, useState } from "react";
import CompanyInfoList from "./CompanyInfoList";
import { CompListContext } from "./CompListContext";
const CompSerach = () => {
const [company, setCompany] = useContext(CompListContext);
const [searchField, setSearchField] = useState("");
const [searchShow, setSearchShow] = useState(false);
const filtered = company.filter((res) => {
return res.company.toLowerCase().includes(searchField.toLowerCase());
});
const inputRef = useRef(null); // 1. Create the ref
const handleClick = () => {
const val = inputRef.current.value; // 3. Get the value
setSearchField(val);
if (val === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
};
const searchList = () => {
if (searchShow) {
return <CompanyInfoList filtered={filtered} />;
}
};
return (
<>
<div>
<em>
NOTE: if you search "ABC" or "EFGH" you will see results - my goal is
to see those results after clicking button
</em>
<br />
<input
type="search"
placeholder="search Company Title"
ref={inputRef} {/* 2. Assign the ref to the Input */}
/>
<button onClick={handleClick}>Enter</button>
</div>
{searchList()}
</>
);
};
export default CompSerach;
https://codesandbox.io/s/show-on-button-click-68157003-rot6o?file=/src/TestTwo/CompSearch.js
Let me know if you need further support.
const handleChange = (e) => {
setSearchField(e.target.value);
if (e.target.value === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
**setCompany(e.target.value);**
};
i think your question is similar with autocomplete.

Resources