Search field kicks me out on input field after 1 letter - javascript

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

Related

How to set a value to a specific component in ReactJs

I am trying to make a tic tac toe game using reactjs. I have a function named setSquareValue to set the value of squares to X but when I am clicking on a square all the other squares also filling with X.
The hierarchy is Game.js -> Board.js -> Square.js. I am using useState() which is in Game.js and also the function setSquareValue is in Game.js. I am passign the function to Square.js through Board.js.
Here is my codes:
Game.js
import React, { useState } from 'react';
import './Game.css'
import Board from './Board';
const Game = () => {
const [value, setValue] = useState(null);
const setSquareValue = () => {
setValue("X")
}
return (
<div className='game'>
<h1 style={{color: "#fff"}}>Tic Tac Toe</h1>
<h4 style={{color: "#2D033B"}}>Winner: </h4>
<Board value={value} setSquareValue={setSquareValue} />
</div>
);
};
export default Game;
Board.js
import React from "react";
import Square from "./Square";
import "./Board.css";
const Board = ({ value, setSquareValue }) => {
const squareKeys = [1, 2, 3, 4, 5, 6, 7, 8, 9];
return (
<div className="board">
{squareKeys.map(squareKey => (
<Square
key={squareKey}
value={value}
setSquareValue={setSquareValue} />
))}
</div>
);
};
export default Board;
Square.js
import React from "react";
import "./Square.css";
const Square = ({ value, setSquareValue }) => {
return (
<div className="square" onClick={() => setSquareValue()}>
{value}
</div>
);
};
export default Square;
I want, when I click a certain square, only that square fill with X. But every square is filling with X. How can I solve this?
I tried but X is appearing in every box of the game.
Edited: Added CSS codes
Game.css
.game {
background-color: #AD7BE9;
width: 100%;
height: 100vh;
padding: 2rem;
display: flex;
flex-direction: column;
align-items: center;
}
Board.css
.board {
background-color: #645CBB;
color: #fff;
padding: 1rem;
margin-top: 1rem;
border-radius: 5px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
Square.css
.square {
border: 1px solid #fff;
height: 5rem;
width: 5rem;
font-size: 20px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
You are passing the value to every single square in your map function, for the solution you can move your value state and setSquareValue function to square component so that every square itself has its own value.
updated Square.js:
import React from "react";
import "./Square.css";
const Square = () => {
const [value, setValue] = useState(null);
const setSquareValue = () => {
setValue("X")
}
return (
<div className="square" onClick={setSquareValue}>
{value}
</div>
);
};
export default Square;
also you don't need to pass value and setSquareValue from Game.js to Board.js and Square.js anymore;
You are creating a unique state for all of the square in Game component. so if you change any time all of the square state will change so this problem occurs. So what you can do is instead of common state, you can create an individual state in Square component as:
CODESANDBOX LINK
const Square = () => {
const [value, setValue] = useState(null);
const setSquareValue = () => {
setValue("X");
};
return (
<div className="square" onClick={setSquareValue}>
{value}
</div>
);
};
The problem is where you are tracking value. Move the logic to store the value state from Game.js to Square.js
When your app begins, there is one instance of Game which has an initial value set to value. Then you pass this single value to Board, where it renders that same value 9 times. Likewise, when you change that single value, it gets changed in Game, then passed to Board, then finally rendered 9 times.
Let Square store its own state.
It is because you are using only one state variable for all square:
const [value, setValue] = useState(null);
You can declare as array variable that length is 9 for 9 square.
Game.js
import React, { useState } from 'react';
import './Game.css'
import Board from './Board';
const Game = () => {
const [value, setValue] = useState(Array(9).fill(''));
const setSquareValue = (index) => {
let values = [...value];
values[index] = 'X';
setValue(values);
}
return (
<div className='game'>
<h1 style={{color: "#fff"}}>Tic Tac Toe</h1>
<h4 style={{color: "#2D033B"}}>Winner: </h4>
<Board value={value} setSquareValue={setSquareValue} />
</div>
);
};
export default Game;
Board.js
import React from "react";
import Square from "./Square";
import "./Board.css";
const Board = ({ value, setSquareValue }) => {
const squareKeys = [1, 2, 3, 4, 5, 6, 7, 8, 9];
return (
<div className="board">
{squareKeys.map((squareKey, index) => (
<Square
key={squareKey}
value={value[index]}
ind = {index}
setSquareValue={setSquareValue} />
))}
</div>
);
};
export default Board;
Square.js
import React from "react";
import "./Square.css";
const Square = ({ value, ind, setSquareValue }) => {
return (
<div className="square" onClick={() => setSquareValue(ind)}>
{value}
</div>
);
};
export default Square;

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;
});

React play different audio files at once - working with different refs

I'm creating a small app that plays an audio file and have some functionalities (loop, stop, mute). My goal is to add some more audio files that all should be played and stopped at once (one button to control all), but each will have a mute button, and I'm not sure what is the best practice to do so. I used useRef and thought maybe I need to set a refs array but how will I be able to start/stop them all at once, but still have the ability to control the mute separately?
This is my code so far. I guess I should split and have a different component for the audio sounds. Thanks for helping!
import React, {useState, useRef, useEffect} from 'react'
import {ImPlay2} from "react-icons/im"
import {ImStop} from "react-icons/im"
import styled from "styled-components"
import drums from '../loopfiles/DRUMS.mp3'
//import other audio files//
const AudioPlayer = () => {
const [isPlaying, setIsPlaying] = useState(false);
const [isLooping, setIsLooping] = useState(false);
const [isOnMute, setIsOnMute] = useState(false);
const audioRef = useRef(new Audio(drums));
useEffect(() => {
if (isOnMute) {
audioRef.current.volume=0;
}
else {
audioRef.current.volume=1;
}
}, [isOnMute]);
useEffect(() => {
if (isPlaying) {
audioRef.current.play();
} else {
audioRef.current.pause();
audioRef.current.load();
}
}, [isPlaying]);
useEffect(() => {
if (isLooping) {
audioRef.current.loop = true;
} else {
audioRef.current.loop = false;
}
}, [isLooping]);
return (
<div>
{!isPlaying ? (
<button type="button"
className="play"
onClick={() => setIsPlaying(true)}>
<ImPlay2></ImPlay2> Play
</button>
) : (
<button type="button"
className="pause"
onClick={() => setIsPlaying(false)}>
<ImStop></ImStop> Stop
</button>
)}
<Flex>
<Switcher selected={isLooping} />
<Text
onClick={() => setIsLooping(true)}>
Loop
</Text>
<Text
onClick={() => setIsLooping(false)}>
Unloop
</Text>
</Flex>
<Flex>
<Switcher selected={isOnMute} />
<Text
onClick={() => setIsOnMute(true)}>
Mute
</Text>
<Text
onClick={() => setIsOnMute(false)}>
UnMute
</Text>
</Flex>
</div>
)
}
const Flex = styled.div`
margin-top: 5px;
display: flex;
align-items: center;
border-radius: 2px;
background: grey;
height: 20px;
width: 120px;
position: relative;
margin-bottom: 5px;
`;
const Switcher = styled.div`
background: black;
border-radius: 2px;
height: 20px;
line-height: 41px;
width: 50%;
cursor: pointer;
position: absolute;
transition: 0.5s;
-webkit-transition: 0.5s;
-moz-transition: 0.5s;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
z-index: 1;
left: ${({ selected }) =>
selected === true ? "0px" : "60px"};
`;
const Text = styled.div`
color: ${({ selected }) => (selected ? "black" : "white")};
font-size: 13px;
font-weight: 20;
line-height: 4px;
padding: 30;
width: 50%;
text-align: center;
cursor: pointer;
`;
export default AudioPlayer
If you would like to mute/unmute individual sounds, but play/pause all sounds together, then you will need to create a mute/unmute slider for each sound. I can think of a number of ways to do this. The "best choice" might depend upon the standards in the rest of the application, how many sounds you're importing, and whether they're likely to change.
Method 1: One way to do this would be creating one array containing isOnMute values for each sound and another array containing all refs, and then map(...) over each of the elements of the isOnMute array to create your sliders.
Method 2: Another way would be to have one array of objects containing all sounds, and then the ref and the isOnMute values could be stored within each object. You could map(...) over that to create your sliders as well.
Method 3: You could also create separate child components for each sound like you said, and then pass the mute property between the parent AudioPlayer and the child AudioChannel components.
Then anytime the play/pause button is clicked, you would need to update each of the refs in the array (via a forEach or each of the child components via toggling a single isPlaying property).
Regardless of which you choose, I also might like to recommend the use-sound npm package. It makes managing multiple sounds and their properties a little bit less cumbersome in my opinion, including the ability to play and pause with a single method call.
Here is a snippet for you/
Also do not forget to use according ids instead of idx and idx2
const AudioList = () => {
/* here populate the array in format: array of objects
{
drums: mp3file,
isPlaying: boolean,
setIsPlaying: boolean,
isLooping: boolean,
setIsLooping: boolean,
isOnMute: boolean,
setIsOnMute: boolean,
}[]
*/
const [audios, setAudios] = useState([
{ isPlaying: true, isOnMute: false, isLooping: true, drums: "Your mpr" },
]); // use initial audios
return (
<div>
<button
onClick={() => {
// similar to start all, mute all, you have full controll logic over all elements
// also you could implement add new audiofile, or delete, similar logic :)
setAudios((audios) =>
audios.map((audio) => ({ ...audio, isPlaying: false }))
);
}}
>
Stop all
</button>
<div>
{audios.map((audio, idx) => (
<AudioPlayer
key={idx}
{...audio}
setIsPlaying={(val) =>
setAudios((audios) =>
audios.map((audio, idx2) =>
idx === idx2 ? { ...audio, isPlaying: val } : audio
)
)
}
// similar for setMute and setLopping function,
// i think you can figure it out, it is just a snippet:)
/>
))}
</div>
</div>
);
};
const AudioPlayer = ({
drums,
isPlaying,
setIsPlaying,
isLooping,
setIsLooping,
isOnMute,
setIsOnMute,
}) => {
const audioRef = useRef(new Audio(drums));
// also you have full controll of element inside component
useEffect(() => {
if (isOnMute) {
audioRef.current.volume = 0;
} else {
audioRef.current.volume = 1;
}
}, [isOnMute]);
useEffect(() => {
if (isPlaying) {
audioRef.current.play();
} else {
audioRef.current.pause();
audioRef.current.load();
}
}, [isPlaying]);
useEffect(() => {
if (isLooping) {
audioRef.current.loop = true;
} else {
audioRef.current.loop = false;
}
}, [isLooping]);
return (
<div>
{!isPlaying ? (
<button
type="button"
className="play"
onClick={() => setIsPlaying(true)}
>
<ImPlay2></ImPlay2> Play
</button>
) : (
<button
type="button"
className="pause"
onClick={() => setIsPlaying(false)}
>
<ImStop></ImStop> Stop
</button>
)}
<Flex>
<Switcher selected={isLooping} />
<Text onClick={() => setIsLooping(true)}>Loop</Text>
<Text onClick={() => setIsLooping(false)}>Unloop</Text>
</Flex>
<Flex>
<Switcher selected={isOnMute} />
<Text onClick={() => setIsOnMute(true)}>Mute</Text>
<Text onClick={() => setIsOnMute(false)}>UnMute</Text>
</Flex>
</div>
);
};
I changed the following:
I added Audios.js containing:
const audios = () => {
return [
{
color: 'lightgreen',
isOnMute: false,
audio: drums,
title: 'Drums'
}, ...
AudioList.js:
const AudioList = ({isPlaying, isLooping}) => {
const [audioToPlay, setAudioToPlay] = useState();
useEffect(()=> {
setAudioToPlay(audios())
},[]) ....//and mapped through <AudioItem>:
AudioItem.js:
const AudioItem = ({audio, isPlaying, isLooping}) => {
const [isOnMute, setIsOnMute] = useState(false);
const audioRef = useRef(null);
useEffect(() => {
if (isLooping) {
audioRef.current.loop = true;
} else {.... //other functionality
added a progressBar.js:
const ProgressBar = ({isPlaying}) => {
const [completed, setCompleted] = useState({
count: 0
});const intervalId = useRef(null)
useEffect(() => {...
ControlPanel.js:
const ControlPanel = ({
setIsLooping, isLooping, isPlaying, setIsPlaying}) => {
return (
<div>
<PlayButton> //....
and Home.js containing controlpanel, AudioList, ProgressBar:
const Home = () => {
const [isPlaying, setIsPlaying] = useState(false);
const [isLooping, setIsLooping] = useState(false);
return (
<div>
<ControlPanel
setIsLooping={setIsLooping} //....

Is there a way to import a JS function inside a React component?

I've been trying to create a React HOC that will apply the corresponding styles to any component pass to it.
My idea was to do something similar to this
// Button.css.js
const styles = {backgroundColor:"blue"}
// Button.js
const Button = (props) => <button {...props}/>
// applyStyles.js
const applyStyles = (Component) => (props) => {
const styles = import style from `${Component.name}.css`
return <Component {...props} style={styles} />
}
I know applyStyles contains invalid syntax but is just to illustrate what is what I'm trying to do.
If any of you have a way around this I will really appreciate it.
You can try this
import (`/pathTofile/${Component.name}.css`)
.then(data => {
// rest of the code goes her
})
My recommendation is that you use styled-component for this:
const Wrapper = styled.div`
> * {
backgroundColor:"blue"
}`
function AppyStylesHOC(Component) {
return (props) => {
return (<Wrapper>
<Components {...props} />
</Wrapper>
})
}
You can try this:
Button.js
import React from 'react';
import styles from './Button.module.css';
const Button = props => {
return (
<button
className={styles.button}
type={props.type || 'button'}
onClick={props.onClick}
>
{props.children}
</button>
);
};
export default Button;
Button.module.css
.button {
font: inherit;
border: 1px solid #4f005f;
background: #4f005f;
color: white;
padding: 0.25rem 1rem;
cursor: pointer;
}
App.js
import Button from '../Button';
...
<Button type="submit">+ Add</Button>
...

Can't update React state with text input value

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;

Categories

Resources