How to toggle between boolean based on another value in react - javascript

If a user is selected from the list, the boolean is true, when the selected user is unselected from the list, the boolean is false. The boolean will remain true if another user is selected from the list.
Here's my code:
import React, { useEffect, useState } from "react";
import "./styles.css";
import Users from "./Users";
export default function UserApp() {
const [ selectUser, setSelectUser ] = useState(null);
const [ isUserSelected, setIsUserSelected ] = useState(false);
const handleUserClick = (user) => {
setSelectUser((prev) => (
user !== prev ? user : null
))
if(selectUser === null) {
setIsUserSelected(!isUserSelected)
} else {
setIsUserSelected(isUserSelected)
}
}
console.log(selectUser);
console.log(isUserSelected);
return (
<div className="App">
<Users selectedUser={handleUserClick} />
</div>
);
}

Update your if statement:
if(selectUser !== null) {
setIsUserSelected(true)
} else {
setIsUserSelected(false)
}
OR:
if(selectUser === null) {
setIsUserSelected(false)
} else {
setIsUserSelected(true)
}

You can check whether user is selected or not without using hooks. All you need is one state hook.
const User = props => {
const [selectedUser, setSelectedUser] = useState(null);
const handleUserSelect = user => setSelectedUser(user.id === selectedUser.id ? null : user);
return (
<>
<Users onSelectUser={handleUserSelect} />
<Checkbox checked={Boolean(selectedUser)} />
</>
)
}
If selectedUser is null Boolean(selectedUser) will return false otherwise it will return true. You don't need extra hook for this.

You do not need to store the 'isUserSelect' information since it will be redundant with the 'selectUser === null' condition.
Here is a code proposal
export default function UserApp() {
const [ selectedUser, setSelectedUser ] = useState(null);
const handleUserClick = (user) => {
if(selectedUser === user) {
setSelectedUser(null)
} else {
setSelectedUser(user)
}
}
return (
<div className="App">
<Users onUserClick={handleUserClick} />
</div>
)
}
export default function Users({ onUserClick }) {
return (
<ul>
{USERS_DATA.map((user, index) => (
<li
key={index}
className="users"
onClick={() => onUserClick(user)}
>
{user.user_name}
</li>
))}
</ul>
)
}
note: code not tested, might contain some typos

Related

My search bar is delaying updating the results

I was making a search bar component that modifies an array and then a mapping function that displays the resulted array it as the page results, the problem is that the page is delaying to update, in other words when I type a character in the search bar nothing changes but when I add another character the results are being updated with the first character input only and the.
I was using a hook state to hold the value of the search input and then using a filter function to update the array, finally I used a mapping function to display the modified array data as card components. As I said the problem is the delay that the website takes to update the array and it seams that the problem is with the state hook I uses but I couldn't solve that problem.
I also reuse the filtered array to display search suggetions
Here is app.js
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
import Card from "./components/Card";
import resourcesData from "./resourcesData";
import { type } from "#testing-library/user-event/dist/type";
function App() {
const [filteredList, setFilteredList] = useState(resourcesData);
const [searchTerm, setSearchTerm] = useState("");
const changeInput = (event) => {
setSearchTerm(event);
};
function handleSearchTerm(event) {
setSearchTerm(event.target.value);
var updatedList = [...resourcesData];
updatedList = updatedList.filter((val) => {
if (searchTerm === "") return val;
else if (
val.title.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())
) {
return val;
} else if (
val.thematicArea
.toLocaleLowerCase()
.includes(searchTerm.toLocaleLowerCase())
) {
return val;
}
});
setFilteredList(updatedList);
}
return (
<div className="App">
<input
type="text"
value={searchTerm}
onChange={handleSearchTerm}
className="input"
></input>
<div className="dropdown">
{filteredList.slice(0, 10).map((item) => (
<div
onClick={() => changeInput(item.title)}
className="dropdown-row"
key={item.title}
>
{item.title}
</div>
))}
</div>
<div className="cards">
{filteredList.map((value, index) => (
<Card
resourceURL={value.link}
thumbnailURL=""
title={value.title}
subtitle=""
date={value.date}
description=""
cost=""
cardkeywords={
value.cost === "free"
? [
value.level,
value.language,
value.type,
value.thematicArea,
value.cost,
]
: [
value.level,
value.language,
value.type,
...value.thematicArea.split(","),
]
}
key={index}
/>
))}
</div>
</div>
);
}
export default App;
In the function handleSearchTerm you use setSearchTerm(event.target.value); and after you are using searchTerm which updates asynchronously.
Use in this function event.target.value.
function handleSearchTerm(event) {
const newValue = event.target.value;
setSearchTerm(newValue);
var updatedList = [...resourcesData];
updatedList = updatedList.filter((val) => {
if (newValue === "") return val;
else if (
val.title.toLocaleLowerCase().includes(newValue.toLocaleLowerCase())
) {
return val;
} else if (
val.thematicArea
.toLocaleLowerCase()
.includes(newValue.toLocaleLowerCase())
) {
return val;
}
});
setFilteredList(updatedList);
}

Rendering two React components in a sequence

In Summary: {
How can I merge those 2 arrays into one. As in, instead of having it like this:
[1st,new1st,...] [2nd,new2nd,...]
I want it to be like this:
[1st,2nd,new1st,new2nd,...]
}
I have this note app that I am creating. I am trying to render the two components so that each note rendered is kind of the last element of an array. So, in short, I want each component to be below the previous added note (think of it like a list where each added input is added after the previous list items).
So, this is how it looks before adding anything.
and this is how it looks after adding one note on each create area.
and this is what I am trying to avoid after adding the new notes from each note create area.
What I want is
-1st -2nd -new1st - new2nd
As in no matter which create area I use, it gets rendered after all the previous ones.
Here's my code
import React, { useState } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
function App() {
const [notes, setNotes] = useState([]);
const [notes2, setNotes2] = useState([]);
function addNote(newNote) {
setNotes(prevNotes => {
return [...prevNotes, newNote];
});
}
function addNote2(newNote) {
setNotes2(prevNotes => {
return [...prevNotes, newNote];
});
}
function deleteNote(id) {
setNotes(prevNotes => {
return prevNotes.filter((noteItem, index) => {
return index !== id;
});
});
}
function deleteNote2(id) {
setNotes(prevNotes => {
return prevNotes.filter((noteItem, index) => {
return index !== id;
});
});
}
return (
<div>
<Header />
<CreateArea onAdd={addNote} />
<CreateArea onAdd={addNote2} />
{notes.map((noteItem, index1) => {
return (
<Note
key={index1}
id={index1}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote}
/>
);
})}
{notes2.map((noteItem, index2) => {
return (
<Note
key={index2}
id={index2}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote2}
/>
);
})}
<Footer />
</div>
);
}
export default App;
You can test the app by copying the above code instead of App.jsx at CodeSandbox.
I need to do something like that:
-item1
=nested item 1
=nested item 2
=nested item 3
-item 2
so I need the second create area to eventually be used for nested items (children). and the 1st create area to be for 'item1' or 'item2' or ... (parent). But with the way it functions from my code, it gets rendered like that:
-item1
-item2
=nested item 1
=nested item 2
=nested item 3
I don't understand a reason why you would want to do that. You need to either have one list or two. If for rendering, you want it to be one list, you can have that in a single state. Also if it's just about having two input fields to add note, both fields can push to same state. Here is how it could be:
import React, { useState } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
function App() {
const [notes, setNotes] = useState([]);
function addNote(newNote) {
setNotes(prevNotes => {
return [...prevNotes, newNote];
});
}
function deleteNote(id) {
setNotes(prevNotes => {
return prevNotes.filter((noteItem, index) => {
return index !== id;
});
});
}
return (
<div>
<Header />
<CreateArea onAdd={addNote} />
<CreateArea onAdd={addNote} />
{notes.map((noteItem, index1) => {
return (
<Note
key={index1}
id={index1}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote}
/>
);
})}
<Footer />
</div>
);
}
export default App;
Well, if you still want it :D then here is a thing you can do:
import React, { useState } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
function App() {
const [notes, setNotes] = useState([]);
const [notes2, setNotes2] = useState([]);
const [combinedNotes, setCombinedNotes] = useState([]);
useEffect(() => {
const notesList = [...notes, ...notes2].sort((note1, note2) => note1.timestamp - note2.timestamp);
setCombinedNotes(notesList);
}, [notes, notes2]);
function addNote(newNote) {
setNotes(prevNotes => {
return [...prevNotes, { ...newNote, timestamp: new Date().getTime() }];
});
}
function addNote2(newNote) {
setNotes2(prevNotes => {
return [...prevNotes, { ...newNote, timestamp: new Date().getTime() }];
});
}
function deleteNote(id) {
const isFirstNote = notes.find((note) => note.timestamp === id);
if (isFirstNote) {
setNotes(prevNotes => {
return prevNotes.filter((noteItem) => {
return noteItem.timestamp !== id;
});
});
} else {
setNotes2(prevNotes => {
return prevNotes.filter((noteItem) => {
return noteItem.timestamp !== id;
});
});
}
}
return (
<div>
<Header />
<CreateArea onAdd={addNote} />
<CreateArea onAdd={addNote2} />
{combinedNotes((noteItem, index) => {
return (
<Note
key={index}
id={noteItem.timestamp}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote}
/>
);
})}
<Footer />
</div>
);
}
export default App;

React toggle view functionality in the parent via child component

I am trying to toggle view between list of meals and meal details. I have placed a button in the child component Meal.js to the Meals.js which is meant to be the list and the details view.
Can you please help me fix this issue. Seems like its not working even with the conditional rendering method I've used in the code below.
Meal.js
import { useState } from 'react'
import './Meal.css'
const Meal = (props) => {
const [isToggled, setIsToggled] = useState(false);
const sendIdHandler = () => {
if (isToggled === true) {
setIsToggled(false);
}
else {
setIsToggled(true);
}
props.onSaveIdHandler(props.id, isToggled)
}
return (
<div
className='meal'
onClick={sendIdHandler}
>
{props.label}
</div>
);
}
export default Meal;
Meals.js
import Meal from './Meal/Meal'
const Meals = (props) => {
let toggleCondition = false;
const saveIdHandler = (data, isToggled) => {
toggleCondition = isToggled;
const mealDetails = props.mealsMenuData.findIndex(i =>
i.id === data
)
console.log(mealDetails, toggleCondition)
}
return (
<div>
{toggleCondition === false &&
props.mealsMenuData.map(item =>
<Meal
key={item.id}
id={item.id}
label={item.label}
onSaveIdHandler={saveIdHandler}
/>
)
}
{toggleCondition === true &&
<div>Horray!</div>
}
</div>
);
}
export default Meals;
UPDATE
Finally figured how to do this properly. I put the condition true/false useState in the parent instead and have Meal.js only send the id I need to view the item
Code is below..
Meals.js
import { useState } from 'react'
import Meal from './Meal/Meal'
import MealDetails from './MealDetails/MealDetails'
const Meals = (props) => {
const [show, setShow] = useState(false);
const [mealId, setMealId] = useState(0);
const saveIdHandler = (data) => {
setShow(true);
setMealId(props.mealsMenuData.findIndex(i =>
i.id === data)
)
console.log(props.mealsMenuData[mealId].ingridients)
}
const backHandler = () => {
setShow(false)
}
return (
<div>
{show === false &&
props.mealsMenuData.map(item =>
<Meal
key={item.id}
id={item.id}
label={item.label}
onSaveIdHandler={saveIdHandler}
/>
)
}
{show === true &&
<div>
<MealDetails data={props.mealsMenuData[mealId]} />
<button onClick={backHandler}>Back</button>
</div>
}
</div>
);
}
export default Meals;
Meal.js
import './Meal.css'
const Meal = (props) => {
const sendIdHandler = () => {
props.onSaveIdHandler(props.id)
}
return (
<div
className='meal'
onClick={sendIdHandler}
>
{props.label}
</div>
);
}
export default Meal;
Your problem in sendIdHandler: You can update like this:
const sendIdHandler = () => {
const newIsToggled = !isToggled;
setIsToggled(newIsToggled)
props.onSaveIdHandler(props.id, newIsToggled)
}

Filtering arrays in JS/React

So here I have search functionality. Everything works fine except when an Item has not been found in the array. I have tried something with objects.Keys but it is displaying it on the render not when the Book has not been found like it should. Should I use if statement or.
import React,{useState, useEffect, useRef, useContext}from 'react'
import {FaSearch} from 'react-icons/fa'
import {
Link, useHistory
} from "react-router-dom";
import { BookContext } from '../../context/books';
import SearchBooks from './SearchBooks';
const Search = () => {
const {data}= useContext(BookContext)
const [searchValue, setSearchValue] = React.useState('');
const history= useHistory()
const ref=useRef()
function filterBooks(book) {
console.log(book);
if (!searchValue.length ) return false;
return book.bookName?.toLowerCase().includes(searchValue.toLowerCase());
}
const handleSearch = (e) => {
if (ref.current && !ref.current.contains(e.target)) {
setSearchValue('')
}
};
useEffect(() => {
document.addEventListener('click', handleSearch);
return () => {
document.removeEventListener('click', handleSearch);
};
}, []);
return (
<div className='search__cont' ref={ref}>
{Object.keys(data).filter(filterBooks).length === 0 &&(
<div>
<h3>Book not found</h3>
</div>
)}
<SearchBooks searchValue={searchValue} setSearchValue={setSearchValue }/>
{Object.keys(data)
.map((key) => data[key])
.reduce((acc, curr) => acc.concat(curr), [])
.filter(filterBooks)
.map((book) => {
return (
<>
<div className='search__books'
onClick={() => {
history.push("/book/id", { book }); setSearchValue('')
}}
>
{" "}
{book.bookName}{" "}
</div>
</>
);
})}
</div>
)
}
export default Search
You're filtering the category names instead of the books (data is an object with category names as keys and books as values). You can use Object.values and Array.prototype.flat to get an array of all the books and then apply the filter.
const filteredBooks = Object.values(data).flat().filter(filterBooks)
const searchQueryPresent = searchValue.trim().length > 0
{
searchQueryPresent &&
(filteredBooks.length === 0 ? (
<div>No books found</div>
) : (
filteredBooks.map((book) => {
return <>{/* render books */}</>
})
))
}

How to pass callback function parameters to parent react component?

Just wondering the best way to pass the letterSelected into the useLazyQuery fetchMovies query, so that I don't have to use the static variable of "A". I was hoping there was a way to pass it directly into fetchMovies. useLazyQuery is an apollo query.
const BrowseMovies = () => {
const [fetchMovies, { data, loading}] = useLazyQuery(BROWSE_MOVIES_BY_LETTER, {
variables: {
firstLetter: "A"
}
})
return (
<div className="browserWrapper">
<h2>Browse Movies</h2>
<AlphabetSelect
pushLetterToParent={fetchMovies}
/>
{
data && !loading &&
data.browseMovies.map((movie: any) => {
return (
<div className="browseRow">
<a className="movieTitle">
{movie.name}
</a>
</div>
)
})
}
</div>
)
}
export default BrowseMovies
const AlphabetSelect = ({pushLetterToParent}: any) => {
const letters = ['A','B','C','D','E','F','G','H','I','J','K','L', 'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','#']
const [selectedLetter, setSelectedLetter] = useState("A")
const onLetterSelect = (letter: string) => {
setSelectedLetter(letter.toUpperCase())
pushLetterToParent(letter.toUpperCase())
}
return (
<div className="alphabetSelect">
{
letters.map((letter: string) => {
return(
<div
className={selectedLetter === letter ? 'letterSelectActive' : 'letterSelect'}
onClick={() => onLetterSelect(letter)}
>
{letter}
</div>
)
})
}
</div>
)
}
export default AlphabetSelect
This appears to be a problem solved by Lifting State Up. useLazyQuery takes a gql query and options and returns a function to execute the query at a later time. Sounds like you want the child component to update the variables config parameter.
BrowseMovies
Move firstLetter state BrowseMovies component
Update query parameters/options/config from state
Add useEffect to trigger fetch when state updates
Pass firstLetter state and setFirstLetter state updater to child component
const BrowseMovies = () => {
const [firstLetter, setFirstLetter] = useState('');
const [fetchMovies, { data, loading}] = useLazyQuery(
BROWSE_MOVIES_BY_LETTER,
{ variables: { firstLetter } } // <-- pass firstLetter state
);
useEffect(() => {
if (firstLetter) {
fetchMovies(); // <-- invoke fetch on state update
}
}, [firstLetter]);
return (
<div className="browserWrapper">
<h2>Browse Movies</h2>
<AlphabetSelect
pushLetterToParent={setFirstLetter} // <-- pass state updater
selectedLetter={firstLetter} // <-- pass state
/>
{
data && !loading &&
data.browseMovies.map((movie: any, index: number) => {
return (
<div key={index} className="browseRow">
<a className="movieTitle">
{movie.name}
</a>
</div>
)
})
}
</div>
)
}
AlphabetSelect
Attach pushLetterToParent callback to div's onClick handler
const AlphabetSelect = ({ pushLetterToParent, selectedLetter }: any) => {
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#';
return (
<div className="alphabetSelect">
{
letters.split('').map((letter: string) => {
return(
<div
key={letter}
className={selectedLetter === letter ? 'letterSelectActive' : 'letterSelect'}
onClick={() => pushLetterToParent(letter.toUpperCase())}
>
{letter}
</div>
)
})
}
</div>
)
}

Categories

Resources