Can't update React state with text input value - javascript

What I'm trying to do is I want to get the list of employees from API, save them in state and do a "live" search by employee name.
Where I struggle is that I can't update my state with filtered array. When I start typing in search field, employees filters, but once I delete some of the letters, nothing changes.
If I .map() not state, but variable that contains filtered array, everything works just fine. This is somehow related to state and state update.
Here is my code:
import "./App.css";
import React, { useState, useEffect } from "react";
import styled from "styled-components";
const Container = styled.div`
width: 1280px;
max-width: 100%;
margin: 0 auto;
th {
text-align: left;
padding: 10px;
background: #f5f5f5;
cursor: pointer;
:hover {
background: #ddd;
}
}
td {
border-bottom: 1px solid #f5f5f5;
padding: 5px;
}
`;
const TopHeader = styled.div`
display: flex;
justify-content: space-between;
padding: 20px;
input {
width: 400px;
padding: 10px;
}
`;
function App() {
const [employees, updateEmployees] = useState([]);
if (employees == 0) {
document.title = "Loading...";
}
useEffect(() => {
fetch("http://dummy.restapiexample.com/api/v1/employees")
.then(res => res.json())
.then(result => {
updateEmployees(result.data);
document.title = `Total: ${result.data.length} `;
});
}, []);
const [searchValue, updateSearch] = useState("");
const filteredEmpl = employees.filter(empl => {
return empl.employee_name.toLowerCase().includes(searchValue.toLowerCase());
});
const handleSearch = e => {
updateSearch(e.target.value);
updateEmployees(filteredEmpl);
};
return (
<Container>
<TopHeader>
<div>
Total employees: <strong>{employees.length}</strong> Filtered
employees: <strong>{filteredEmpl.length}</strong>
</div>
<div>
<input
type="text"
onChange={handleSearch}
value={searchValue}
placeholder="search"
/>
</div>
</TopHeader>
<table style={{ width: "100%" }}>
<thead>
<tr>
<th>id</th>
<th>Employee name</th>
<th>Employee salary</th>
<th>Employee age</th>
</tr>
</thead>
<tbody>
{employees.map(employee => (
<tr key={employee.id}>
<td>{employee.id}</td>
<td>{employee.employee_name}</td>
<td>{employee.employee_salary}</td>
<td>{employee.employee_age}</td>
</tr>
))}
</tbody>
</table>
</Container>
);
}
export default App;
Any ideas what's missing?

The problem is that the search term is stale here
const handleSearch = e => {
updateSearch(e.target.value);
updateEmployees(filteredEmpl);
};
while calling updateEmployees. And you're also replacing the result you got from the api call each time you do a search. There's no need setting the search term to state, do this instead:
const [searchResult, updateSearch] = useState([]);
const filterEmpl = useCallback((searchTerm) => {
return employees.filter(({employee_name}) => {
return employee_name.toLowerCase().includes(searchTerm.toLowerCase());
})
}, [employees]);
const handleSearch = useCallback(({target}) => {
const filteredEmpl = filterEmpl(target.value)
updateSearch(filteredEmpl);
}, [filterEmpl]);

You don't need to store the filtered employees into a state variable. You just need to compute it from the original employees each time the searchValue or the employees are updated (by using useMemo).
By the way, it would be preferable to manage the title into its own effect like above.
const [employees, updateEmployees] = useState([]);
const [searchValue, updateSearch] = useState("");
useEffect(() => {
fetch("http://dummy.restapiexample.com/api/v1/employees")
.then(res => res.json())
.then(result => updateEmployees(result.data));
}, []);
useEffect(() {
document.title = !employees.length ? "Loading..." : `Total: ${employees.length} `
}, [employees]);
const filteredEmpl = useMemo(() => {
if (!searchValue) return employees;
return employees.filter(empl =>
empl.employee_name.toLowerCase().includes(searchValue.toLowerCase())
);
}, [employees, searchValue]);
const handleSearch = e => updateSearch(e.target.value);
If you want to sort the array of employees, you can do like this
const filteredEmpl = useMemo(() => {
const sortFn = (empl1, empl2) => {...};
const filterFn = empl =>
empl.employee_name.toLowerCase().includes(searchValue.toLowerCase());
if (!searchValue) {
return [...employees].sort(sortFn);
} else {
return employees.filter(filterFn).sort(sortFn);
}
}, [employees, searchValue]);
If the sort criteria can be updated by the user (with an input), then you need to store the sort criteria into a new state variable.

I did a little adjustment to your code by changing a couple of variable names and added a filter function. I hope this helps. Let me know if you need any further assistance with this issue. Cheers!
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import "./App.css";
const Container = styled.div`
width: 1280px;
max-width: 100%;
margin: 0 auto;
th {
text-align: left;
padding: 10px;
background: #f5f5f5;
cursor: pointer;
:hover {
background: #ddd;
}
}
td {
border-bottom: 1px solid #f5f5f5;
padding: 5px;
}
`;
const TopHeader = styled.div`
display: flex;
justify-content: space-between;
padding: 20px;
input {
width: 400px;
padding: 10px;
}
`;
const Loading = styled.div`
display: flex;
text-align: 'center';
padding: 20px;
font-size: 2em;
font-weight: 300;
`;
const App = () => {
const [employees, setEmployees] = useState([]); // Change variable name from updateEmployees to setEmployees
const [searchValue, setSearchValue] = useState(""); // changed variable name from updateSearch to setSearchValue
const [employeesTotal, setEmployeesTotal] = useState(0); // Add a new state to handle intial employees total
// Renamed employees variable to employeesTotal
if (employeesTotal) {
document.title = "Loading...";
}
useEffect(() => {
fetch("http://dummy.restapiexample.com/api/v1/employees")
.then(res => res.json())
.then(result => {
setEmployees(result.data);
setEmployeesLength(result.data.length);
document.title = `Total: ${result.data.length} `; // Why though?
});
}, []);
const handleSearch = e => {
setSearchValue(e.target.value);
};
const filterDocument = doc => {
const employeeName = doc.employee_name.toLowerCase() || '';
return employeeName.includes(searchValue.toLowerCase());
};
// Check if employees array contains data, if it does, display content, otherwise show loading
return (
employeesTotal ? (
<Container>
<TopHeader>
<div>
Total employees: <strong>{employeesTotal}</strong> Filtered employees: <strong>{employees.length}</strong>
</div>
<div>
<input
type="text"
onChange={handleSearch}
value={searchValue}
placeholder="search"
/>
</div>
</TopHeader>
<table style={{ width: "100%" }}>
<thead>
<tr>
<th>id</th>
<th>Employee name</th>
<th>Employee salary</th>
<th>Employee age</th>
</tr>
</thead>
<tbody>
{/** Add filterDocument to filter function on employee array before calling its map funtion */}
{employees.filter(filterDocument).map(employee => (
<tr key={employee.id}>
<td>{employee.id}</td>
<td>{employee.employee_name}</td>
<td>{employee.employee_salary}</td>
<td>{employee.employee_age}</td>
</tr>
))}
</tbody>
</table>
</Container>
) : (
<Loading>Loading...</Loading>
)
);
}
export default App;

Related

how to pin a note and sort it in an array

I was creating a notes app in react which has a pin functionality such that when I click on the pin icon on a particular note that particular note is displayed first. the user should only be able to pin 2 notes (and I've added that condition) the problem is The pin functionality is working perfectly fine but when I again click on a pinned note I want to un-pin it and again arrange it back in its place, how can i achieve this?
React code =
import React, { useState } from "react";
import "../styles/Notes.css";
import { useToast, Wrap, WrapItem, Button } from '#chakra-ui/react'
import { BsFillPinFill } from "react-icons/bs"
import { BsTrashFill } from "react-icons/bs"
import { BsPinAngle } from "react-icons/bs";
function NotesComponent() {
const [notes, setNotes] = useState([]);
const [title, setTitle] = useState("");
const [tagline, setTagline] = useState("");
const [body, setBody] = useState("");
const [page, setPage] = useState(1);
const toast = useToast()
// submit handler
const handleSubmit = (event) => {
event.preventDefault();
if (!title || !tagline || !body) {
toast({
title: 'Please complete the input',
status: 'error',
duration: 9000,
isClosable: true,
});
return;
}
// generating randome number to use as id
function generateUniqueNumber() {
let uniqueNumber = "";
while (uniqueNumber.length < 4) {
let digit = Math.floor(Math.random() * 10);
if (!uniqueNumber.includes(digit)) {
uniqueNumber += digit;
}
}
return uniqueNumber;
}
let number = generateUniqueNumber();
setNotes([...notes, { title, tagline, body, pinned: false, id: number }]);
setTitle("");
setTagline("");
setBody("");
};
// executing on click on the pen icon
const togglePin = (index) => {
setNotes(
notes.map((note, i) => {
if (i === index) {
let newNote = { ...note };
newNote.pinned = !note.pinned;
return newNote;
}
return note;
})
);
};
// sorting it
const sortedNotes = notes.sort((a, b) => {
if (a.pinned === b.pinned) {
return 0;
}
return a.pinned ? -1 : 1;
})
.map((note, i) => {
let newNote = { ...note };
if (note.pinned) {
const pinnedCount = notes.filter((n) => n.pinned).length;
if (pinnedCount > 2) {
newNote.pinned = false;
}
}
return newNote;
});
const pages = [1, 2, 3, 4, 5, 6];
const pageChnageHandler = (e) => {
setPage(e.target.innerText);
};
const deleteHandler = (id) => {
let index = id
const newArrayAfterDeleting = notes.filter((item) => item.id !== index)
setNotes(newArrayAfterDeleting)
}
return (
<div className="notes-app-container">
<form onSubmit={handleSubmit} className="notes-form">
<input
type="text"
placeholder="Title"
value={title}
onChange={(event) => setTitle(event.target.value)}
className="notes-input"
/>
<input
type="text"
placeholder="Tagline"
value={tagline}
onChange={(event) => setTagline(event.target.value)}
className="notes-input"
/>
<textarea
placeholder="Body"
value={body}
onChange={(event) => setBody(event.target.value)}
className="notes-textarea"
/>
<button type="submit" className="notes-button">
Add Note
</button>
</form>
<div className="enteredNotesMainParent">
{sortedNotes.slice(page * 6 - 6, page * 6).map((note, i) => (
<div key={i} className="enteredNoteIndivitual">
<div>{note.title}</div>
<div>{note.tagline}</div>
<div>{note.body}</div>
<br />
<div className="noteCtaHold">
<div>
<BsFillPinFill className="noteIcon" onClick={() => togglePin(i)} />
</div>
<div>
<BsTrashFill className="noteIcon" onClick={() => deleteHandler(note.id)} />
</div>
</div>
</div>
))}
</div>
{notes.length === 0 ? <p> Add some notes✅ </p> : ""}
{notes.length >= 4 && <div className="pagesHold">
{pages.map((item) => {
return <p onClick={pageChnageHandler} className="indivitualPage"> {item} </p>
})}
</div>}
</div>
);
}
export default NotesComponent;
can somebody please help me achieve this that if a note is pinned and if I click on that pinned note it should get un-pinned and re arrange back
You don't necessarily have to use sort() to get the pinned notes on top.
Just render the list twice: once for the pinned notes, filtering out the unpinned ones, and again for the rest of the list, filtering the pinned ones.
This way you don't have to concern yourself with where a given note is within the original list, because the original list doesn't change.
// creates a list of sample notes; not relevant to the funcionality.
const notes = Array.from({length: 6}, (_, i) => ({
title: `Note ${i + 1}`,
id: i
}))
function Notes ({notes}) {
// keep a list of the pinned note ids
const [pinned, setPinned] = React.useState([]);
// filter to get separate lists of pinned and unpinned notes
const pinnedNotes = notes.filter(({ id }) => pinned.includes(id));
const unpinnedNotes = notes.filter(({ id }) => !pinned.includes(id));
// to pin a note: add its id to the pinned list
const pin = id => setPinned([...pinned, id]);
// to unpin a note: remove its id from the pinned list
const unpin = id => {
pinned.splice(pinned.indexOf(id), 1);
setPinned([...pinned]);
}
// render both lists
return (
<div className="container">
<ul className="pinned">
{ pinnedNotes.map(note => (
<li key={note.id} onClick={() => unpin(note.id)}>{note.title}</li>
))}
</ul>
<ul className="unpinned">
{ unpinnedNotes.map(note => (
<li key={note.id} onClick={() => pin(note.id)}>{note.title}</li>
))}
</ul>
</div>
)
}
const root = ReactDOM.render(<Notes notes={notes} />, document.getElementById('root'));
/* all cosmetic. not necessary for it to work. */
.container {
font-family: sans-serif;
font-size: 12px;
}
.pinned {
background: skyblue;
margin-bottom: 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.5em;
padding: 0 0.5em;
}
.pinned:empty::after {
content: "No pinned items. Click a note below to pin it.";
padding: 1rem;
display: block;
text-align: center;
}
.unpinned:empty::after {
content: "No unpinned notes.";
padding: 1rem;
display: block;
text-align: center;
}
.pinned li {
background: aliceblue;
}
.unpinned {
background: aliceblue;
}
li {
margin: 0.5em 0;
padding: 1em;
border: 1px solid steelblue;
border-radius: 2px;
}
ul {
list-style: none;
margin: 0;
padding: 0.25em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Create Button from each item in array React

I am working with Unsplash API and I am trying to get the images to download. I can get them to download, however it downloads every single photo instead of just the one I want when I use a for loop. If I remove the saveAs part outside of the loop it only downloads the final image in the array instead of the others no matter what button I click. Here is my code:
import React, { useState, useEffect } from 'react';
import { Heading } from './components/Heading';
import { Loader } from './components/Loader';
import { UnsplashImage } from './components/UnsplashImage';
import InfiniteScroll from 'react-infinite-scroll-component';
import { saveAs } from 'file-saver';
import axios from 'axios';
import styled from 'styled-components';
import { createGlobalStyle } from 'styled-components';
import SearchPhotos from './components/searchPhotos';
import Heart from './components/Heart';
import { FileUpload } from './components/Upload';
const GlobalStyle = createGlobalStyle`
*{
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
body{
font-family: sans-serif;
}
`;
const WrapperImg = styled.section`
max-width: 70rem;
margin: 4rem auto;
display: grid;
grid-gap: 1em;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
`;
const H1 = styled.h1`
max-width: 70rem;
margin: 4rem auto;
`;
const Div = styled.div`
display: flex;
flex-direction: column;
margin-bottom: 2rem;
height: auto;
width: 100%;
position: relative;
`;
function App() {
const [images, setImages] = useState([]);
useEffect(() => {
fetchImages();
}, [])
const fetchImages = () => {
const apiURL = "https://api.unsplash.com";
const apiKey = "MY_KEY_";
axios
.get(`${apiURL}/photos/random?client_id=${apiKey}&count=1`)
.then(res => setImages([...images, ...res.data]))
}
const imgURL = images.map((download) => {
//console.log(download.urls.full)
return download.urls.full;
});
const Download = () => {
const downloadImage = () => {
for (var i = 0; i < imgURL.length; i++) {
var red = imgURL[i];
//saveAs(red, 'image.jpg');
console.log(red);
}
//saveAs(red, 'image.jpg');
}
return <button onClick={downloadImage}>Download</button>
}
return (
<div className="App">
<Heading />
<GlobalStyle />
<SearchPhotos />
<InfiniteScroll
dataLength={images.length}
next={fetchImages}
hasMore={true}
loader={<Loader />}
>
<H1>Main Feed:</H1>
<WrapperImg>
<FileUpload />
{images.map(image =>
(<>
<Div>
<Heart />
<UnsplashImage url={image.urls.thumb} key={image.id} />
<p className="like"> Amount of Likes ❤️ {image.likes}</p>
<Download />
</Div>
</>))}
</WrapperImg>
</InfiniteScroll>
</div>
);
}
export default App;
Try this to download each image, I have removed loop and modified the Download function
const imgURL = images.map((download) => {
//console.log(download.urls.full)
return download.urls.full;
});
const downloadImage = (index) => {
var red = imgURL[index];
saveAs(red, 'image.jpg');
}
return (
<div className="App">
<WrapperImg>
{images.map((image,index) =>
(<>
<Div>
<UnsplashImage url={image.urls.thumb} key={image.id} />
<button onClick={()=> { downloadImage(index) }>Download</button>
</Div>
</>))}
</WrapperImg>
</div>
);
}
This should help you:
// the download buttons with specific links will all be stored in the array returned here
const allYourDownloadButtons = images.map((download) => {
let imgURL = download.urls.full;
// saveAs was not mentioned in your code, if it's in scope here, you can directly pass it
return <DownloadV2 imgURL={imgURL} saveAs={saveAs} />;
});
const DownloadV2 = ({ imgURL, saveAs }) => {
return <button onClick={() => saveAs(imgURL, 'image.jpg')}>Download</button>;
};
Once you display the buttons on the UI, clicking on them will pass the specific URL through saveAs.
In case you need to know how to use this, please share in the question where you were calling this button.
A very generic way to use it would be like this:
<div className="allMyDownloadButtons">
{allYourDownloadButtons}
</div>
Edit: based on your updates I can see that your job is even easier as you were already looping through the images:
<WrapperImg>
<FileUpload />
{images.map((image) => (
<>
<Div>
<Heart />
<UnsplashImage url={image.urls.thumb} key={image.id} />
<p className="like"> Amount of Likes ❤️ {image.likes}</p>
<DownloadV2 imgURL={image.urls.full} />
</Div>
</>
))}
</WrapperImg>
You have this question - firstly understand this What is the happening
You can open it in codepen vanilla Javascript or you can skip this. - enter link description here
const root = document.querySelector("#root");
const arr = [1,2,3,4,5];
arr.map(each => {
const newButton = document.createElement("button");
newButton.innerHTML = each;
newButton.addEventListener("click", () => {
console.log(each);
})
root.appendChild(newButton);
})
Now Come to your code :
<WrapperImg>
<FileUpload />
{images.map(image =>
(<>
<Div>
<Heart />
<UnsplashImage url={image.urls.thumb} key={image.id} />
<p className="like"> Amount of Likes ❤️ {image.likes}</p>
<Download downloadUrl={image.urls.full} />
</Div>
</>))}
</WrapperImg>
Now you can go to the Download Component and edit it.
const Download = ({downloadUrl}) => {
const downloadImage = () => {
saveAs(downloadUrl, 'image.jpg');
}
}
return <button onClick={downloadImage}>Download</button>
}
Here you don't need these code below
const imgURL = images.map((download) => {
//console.log(download.urls.full)
return download.urls.full;
});

Correct way to map JSON data to <Table/> by Fetch API method (react js)

I can't believe that I'm asking an obvious question, but I still get the error in console log.
I'm trying to map some json elements to but can't see to figure out the correct way.
Console says that it can't recognize renderBody and BodyData.
How to render the data into Table correctly?
Customers.js
import React from 'react'
import Table from '../components/table/Table'
import React, { Component } from 'react';
import Cookies from 'universal-cookie';
export class Customers extends Component {
constructor(props) {
super(props);
this.state = {
deps: [],
};
}
componentDidMount() {
this.refershList();
}
async refershList() {
const cookies = new Cookies();
await fetch('https://xxxxxxxxxxxxxxxxx/Customers', {
headers: { Authorization: `Bearer ${cookies.get('userToken')}` }
})
.then(response => response.json())
.then(data => {
this.setState({ deps: data });
});
}
render() {
const { deps } = this.state;
const customerTableHead = [
'',
'name',
'email',
'phone',
'total orders',
'total spend',
'location'
]
const renderHead = (item, index) => <th key={index}>{item}</th>
const renderBody = (item, index) => (
<tr key={index}>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.email}</td>
<td>{item.phone}</td>
<td>{item.total_orders}</td>
<td>{item.total_spend}</td>
<td>{item.location}</td>
</tr>
)
const Customers = () => {
return (
<div>
<h2 className="page-header">
customers
</h2>
<div className="row">
<div className="col-12">
<div className="card">
<div className="card__body">
<Table
limit='10'
headData={customerTableHead}
renderHead={(item, index) => renderHead(item, index)}
bodyData={deps}
renderBody={(item, index) => renderBody(item, index)}
/>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default Customers
This is the Table.jsx component:
import React, {useState} from 'react'
import './table.css'
const Table = props => {
const initDataShow = props.limit && props.bodyData ? props.bodyData.slice(0, Number(props.limit)) : props.bodyData
const [dataShow, setDataShow] = useState(initDataShow)
let pages = 1
let range = []
if (props.limit !== undefined) {
let page = Math.floor(props.bodyData.length / Number(props.limit))
pages = props.bodyData.length % Number(props.limit) === 0 ? page : page + 1
range = [...Array(pages).keys()]
}
const [currPage, setCurrPage] = useState(0)
const selectPage = page => {
const start = Number(props.limit) * page
const end = start + Number(props.limit)
setDataShow(props.bodyData.slice(start, end))
setCurrPage(page)
}
return (
<div>
<div className="table-wrapper">
<table>
{
props.headData && props.renderHead ? (
<thead>
<tr>
{
props.headData.map((item, index) => props.renderHead(item, index))
}
</tr>
</thead>
) : null
}
{
props.bodyData && props.renderBody ? (
<tbody>
{
dataShow.map((item, index) => props.renderBody(item, index))
}
</tbody>
) : null
}
</table>
</div>
{
pages > 1 ? (
<div className="table__pagination">
{
range.map((item, index) => (
<div key={index} className={`table__pagination-item ${currPage === index ? 'active' : ''}`} onClick={() => selectPage(index)}>
{item + 1}
</div>
))
}
</div>
) : null
}
</div>
)
}
export default Table
Table.css
.table-wrapper {
overflow-y: auto;
}
table {
width: 100%;
min-width: 400px;
border-spacing: 0;
}
thead {
background-color: var(--second-bg);
}
tr {
text-align: left;
}
th,
td {
text-transform: capitalize;
padding: 15px 10px;
}
tbody > tr:hover {
background-color: var(--main-color);
color: var(--txt-white);
}
.table__pagination {
display: flex;
width: 100%;
justify-content: flex-end;
align-items: center;
margin-top: 20px;
}
.table__pagination-item ~ .table__pagination-item {
margin-left: 10px;
}
.table__pagination-item {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.table__pagination-item.active,
.table__pagination-item.active:hover {
background-color: var(--main-color);
color: var(--txt-white);
font-weight: 600;
}
.table__pagination-item:hover {
color: var(--txt-white);
background-color: var(--second-color);
}
API JSON data
[
{
"id":1,
"name":"Brittan Rois",
"email":"brois0#unicef.org",
"location":"Bator",
"phone":"+62 745 807 7685",
"total_spend":"$557248.44",
"total_orders":24011
},
{
"id":2,
"name":"Matthew Junifer",
"email":"mjunifer1#buzzfeed.com",
"location":"Bromma",
"phone":"+46 993 722 3008",
"total_spend":"$599864.94",
"total_orders":60195
},
{
"id":3,
"name":"Finlay Baylay",
"email":"fbaylay2#purevolume.com",
"location":"Atalaia",
"phone":"+55 232 355 3569",
"total_spend":"$171337.47",
"total_orders":96328
},
{
"id":4,
"name":"Beryle Monelli",
"email":"bmonelli3#amazonaws.com",
"location":"Martingança",
"phone":"+351 734 876 8127",
"total_spend":"$335862.78",
"total_orders":78768
}
]
I did this type of thing before functionally might wanna see mine and follow it in ur way (Note: i put searchbar and a pagination system inside the site for the tables so take into account only the required table mapping part):
const [users, setUsers] = useState([]);
const [SearchTerm, setSearchTerm] = useState("");
const [PageNumber, setPageNumber] = useState(0);
const [Usersperpage] = useState(4);
const PagesVisited = PageNumber * Usersperpage;
const pageCount = Math.ceil(users.length / Usersperpage);
const changePage = ({ selected }) => {
setPageNumber(selected);
}
const RenderUsers = (val) => {
return (
<tr key={val.id}>
<td><Link to={`/Users/User/${val.id}`}>{val.id}</Link></td>
<td><Link to={`/Users/User/${val.id}`}>{val.name}</Link></td>
<td><Link to={`/Users/User/${val.id}`}>{val.email}</Link></td>
</tr>
)
}
const displayUsers = users.filter((val) => {
if (SearchTerm === "") {
return val;
} else if (val.name.toLowerCase().includes(SearchTerm.toLowerCase())) {
return val;
} else if (val.email.toLowerCase().includes(SearchTerm.toLowerCase())) {
return val;
}
}).slice(PagesVisited, PagesVisited + Usersperpage).map(RenderUsers);
useEffect(() => {
getUsers();
}, []);
const getUsers = async () => {
const res4 = await fetch("https://jsonplaceholder.typicode.com/users");
const data4 = await res4.json();
setUsers(data4);
}
return (
<div className="AppWhole">
<div className="App">
<div className="users">
<h1 style={{ color: 'white' }}>Users</h1>
<input type="text" className='searchUser' onChange={(e) => { setSearchTerm(e.target.value); }} />
<div className="user">
<div className="app-container">
<ReactBootStrap.Table striped bordered hover>
<thead>
<tr>
<th>
ID
</th>
<th>
Name
</th>
<th>
Email
</th>
</tr>
</thead>
<tbody>
{displayUsers}
</tbody>
</ReactBootStrap.Table>
<ReactPaginate
pageCount={pageCount}
onPageChange={changePage}
previousLabel={"<<"}
nextLabel={">>"}
containerClassName={'paginationLinks'}
disabledClassName={'paginationDisabled'}
activeClassName={'paginationActive'}
/>
</div>
</div>
</div>
</div>
</div>
);

Search field kicks me out on input field after 1 letter

this is in React. I have a search input field, however after typing one letter it keeps me out of the input field and renders the page again. The search field does work, it just kicks me out. I've tried adding a
onChange={(e) => setSearchField(e.target.value), function(e) {
e.preventDefault();
}}
to the input field but it doesn't work. Here's my whole file:
import React, { useState, useEffect } from "react";
import { Container, Row, Col, Input } from "reactstrap";
import MeetingTable from "./MeetingTable";
import MeetingCreate from "./MeetingCreate";
import MeetingEdit from "./MeetingEdit";
import APIURL from "../helpers/environment";
import styled from "styled-components";
import "./MeetingMain.css";
const MeetingMain = (props) => {
const Div = styled.div`
background-color: #363136;
opacity: 0.8;
border-radius: 5px;
padding-top: 10px;
padding-left: 10px;
`;
const [meetings, setMeetings] = useState([]);
const [updateActive, setUpdateActive] = useState(false);
const [meetingToUpdate, setMeetingToUpdate] = useState({});
const [searchField, setSearchField] = useState("");
const tableStyle = {
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
maxWidth: "1175px",
};
const fetchMeetings = () => {
fetch(`${APIURL}/meeting`, {
method: "GET",
headers: new Headers({
"Content-Type": "application/json",
Authorization: props.token,
}),
})
.then((res) => res.json())
.then((logData) => {
setMeetings(logData.meetings);
console.log(logData.meetings);
});
};
const editUpdateMeeting = (meeting) => {
setMeetingToUpdate(meeting);
console.log(meeting);
};
const updateOn = () => {
setUpdateActive(true);
};
const updateOff = () => {
setUpdateActive(false);
};
useEffect(() => {
fetchMeetings();
}, []);
const filteredMeetings = meetings.filter((meeting) =>
meeting.day.toLowerCase().includes(searchField.toLowerCase())
);
return (
<Div>
<Container style={tableStyle}>
<Row>
<Col md="12">
<MeetingCreate fetchMeetings={fetchMeetings} token={props.token} />
</Col>
<Col md="12">
<Input
className="search-field"
type="search"
placeholder="Search Meetings"
onChange={(e) => setSearchField(e.target.value)}
value={searchField}
/>
<MeetingTable
meetings={filteredMeetings}
editUpdateMeeting={editUpdateMeeting}
updateOn={updateOn}
fetchMeetings={fetchMeetings}
token={props.token}
/>
</Col>
{updateActive ? (
<MeetingEdit
meetingToUpdate={meetingToUpdate}
updateOff={updateOff}
token={props.token}
fetchMeetings={fetchMeetings}
/>
) : (
<></>
)}
</Row>
</Container>
</Div>
);
};
export default MeetingMain;
So I'm a bit at a loss on what's causing this. Any help would be appreciated.
Issue
You're defining a styled component inside your functional component, this means it's a new component each render cycle. In other words, it is a new component and mounted & rendered versus just being rerendered when state updates from the onChange handler.
Define Styled Components outside of the render method
It is important to define your styled components outside of the render
method, otherwise it will be recreated on every single render pass.
Defining a styled component within the render method will thwart
caching and drastically slow down rendering speed, and should be
avoided.
Recall: The entire body of a functional component IS the render "method".
Solution
Declare the Div component outside MeetingMain so it is a stable component reference.
const Div = styled.div`
background-color: #363136;
opacity: 0.8;
border-radius: 5px;
padding-top: 10px;
padding-left: 10px;
`;
const MeetingMain = (props) => {
const [meetings, setMeetings] = useState([]);
const [updateActive, setUpdateActive] = useState(false);
const [meetingToUpdate, setMeetingToUpdate] = useState({});
const [searchField, setSearchField] = useState("");
You should move Div outside of your MeetingMain component as below.
const Div = styled.div`
background-color: #363136;
opacity: 0.8;
border-radius: 5px;
padding-top: 10px;
padding-left: 10px;
`;
const MeetingMain = (props) => {
...
}
Check it out here

React Context values not referenced correctly in dynamic element functions

I created a Context and hook to be able to see if areas of the application has been changed, and validate actions based on the current state in context.
Its called DirtyContext and the Implementation is used as follows:
const {isDirty, setDirtyContextFor} = useDirtyContext();
setDirtyContextFor(key) - Ads a key to a list, to mark something as dirty.
isDirty - Reports the current state of the application based on a memoized value that updates everytime something is removed or added to the list of keys.
I have a list of objects, that helps me create a set of dynamic elements on the page.
const thisFunctionWillLooseContextReference = (e) => {
e.preventDefault();
console.log('Context Value - IsDirty: ', isDirty)
};
const [buttons, setButtons] = useState(() => {
return [{onClick: thisFunctionWillLooseContextReference}]
});
This is tied together in the UI using the following:
const renderButtons = () => {
return buttons.map((btn, index) => (
<button onClick={btn.onClick}>Button-{index}</button>
));
}
Even if the context value isDirty is set to true, the function passed to the button, always just reports the initial value of isDirty.
Would appreciate any help on why this is happening, and how i can get the expected results (which is the correct/current value of isDirty)
Codepen - Have a look at the console when clicking the buttons:
(Code and implementation details are reduced to the smallest reproducable state)
const { useState, useMemo } = React;
const DirtyContext = React.createContext();
const DirtyContextProvider = ({ children }) => {
const [dirtyList, setDirtyList] = useState(new Set());
const isDirty = useMemo(() => {
return dirtyList.size > 0;
}, [dirtyList]);
function setDirtyStateFor(componentName) {
const newDirtyList = new Set(dirtyList);
newDirtyList.add(componentName);
setDirtyList(newDirtyList);
}
return (<DirtyContext.Provider value={{
setDirtyStateFor,
isDirty,
}}>
{children}
</DirtyContext.Provider>);
};
const useDirtyContext = () => React.useContext(DirtyContext);
const MyDirtyLittleApp = () => {
const {isDirty, setDirtyStateFor} = useDirtyContext();
const [input, setValue] = useState("");
const thisFunctionWillLooseContextReference = (e) => {
e.preventDefault();
console.log('Context Value - IsDirty: ', isDirty)
};
const [buttons, setButtons] = useState(() => {
return [{onClick: thisFunctionWillLooseContextReference}]
});
function handleInput(event) {
setValue(event.target.value);
setDirtyStateFor('MyDirtyLittleApp');
}
function updateInput(event) {
event.preventDefault();
console.log('Am i dirty ?', isDirty)
}
const renderButtons = () => {
return buttons.map((btn, index) => (
<button class="button is-dark" data-reactstuff={isDirty} onClick={btn.onClick}>btn {index}</button>
));
}
return (
<React.Fragment>
<h1>{isDirty ? 'I`m Dirty': 'I`m Clean'}</h1>
<form className="form">
<div class="field">
<label for="name-1">Update DirtyContext</label>
<div class="control">
<input type="text" value={input} name="name-1" onChange={handleInput} class="input"/>
</div>
</div>
<div class="field">
<div class="control">
<button onClick={updateInput} class="button is-dark">Save</button>
{renderButtons()}
</div>
<control>
<h5>Check console for results when clicking on the buttons</h5>
</control>
</div>
</form>
</React.Fragment>
)
}
const App = () => {
return (
<DirtyContextProvider>
<div className="box">
<MyDirtyLittleApp />
</div>
</DirtyContextProvider>
)
}
ReactDOM.render(<App />,
document.getElementById("root"))
body {
height: 100vh;
margin: 0;
display: grid;
place-items: center;
}
.box {
width: 300px;
h1 {
font-size: 20px;
margin: 0 0 1rem 0;
}
h5 {
font-size: 12px;
}
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css" type="text/css" />
<script src="https://unpkg.com/react#16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
https://codepen.io/Cnordbo/pen/zYqwVRL

Categories

Resources