How to use useRef in React - reactjs

I have a sidebar with filters, several filters displayd on home page for example 'price', 'actions'.. it is a tag component
<Tag
key={filter.fieldCode}
text={filter.displayName}
icon={<IconCross />}
type="soft"
size={"md"}
onClick={
() => goToFilters(filter.displayName)
}
/>
then in the method goToFilters I toggle another component multyFilter and set name of category filter fox example 'Price'
const [namef, setNamef] = useState("");
const goToFilters = (name) => {
setNamef(name);
handleToggleFilter(name);
};
then if multyFilter is true I display MultyFilter component
{multyFilter && (
<MultyFilter
setMultyFilter={handleToggleFilter}
filters={filters}
handleToggleFilter={handleToggleFilter}
name={namef}
/>
)}
in the MultyFilter I have a FilterBox component
<FilterBox entry={entry} type={entry.type}/>
FilterBox - component for each filter, for price or actions.
in the FilterBox I have a switch case for selectig what type of filter will be dislpayed
switch (type) {
case 1:
return (
<div className={style.filterBox}>
<div className={style.title}>{entry.displayName}</div>
{showAll
? visibleProps.map((prop) => (
<>
<label className={`${style.check} ${style.option}`}>
<input
type="checkbox"
onChange={(e) => handleClick(e)}
className={style.check__input}
/>
<span className={style.check__box}></span>
<span className={style.name}>{prop.displayName}</span>
</label>
</>
))
: visibleProps.slice(0, 5).map((prop) => (
<>
<label className={`${style.check} ${style.option}`}>
<input type="checkbox" className={style.check__input} />
<span className={style.check__box}></span>
<span className={style.name}>{prop.displayName}</span>
</label>
</>
))}
{!showAll && visibleProps.length > 5 ? (
<button
onClick={() => handleShowAll(entry.id)}
className={style.showAllBtn}
>
Показать все
</button>
) : visibleProps.length > 5 ? (
<button
onClick={() => handlHideAll(entry.id)}
className={style.hideAllBtn}
>
Скрыть
</button>
) : null}
</div>
);
break;
case 2:
return <></>;
break;
case 3:
return (
<>
<RangeFilterBox name={entry.displayName} id={entry.id} filters={entry.properties} />
</>
);
break;
When I tap on button with name price, my filter sidebar is opening and I want to scroll on field with name price, to scroll to selected filter, how to do it i don't know.
I thought about ref
const elementRef = useRef(null);
useEffect(() => {
elementRef.current.scrollIntoView();
}, []);
<FilterBox entry={entry} type={entry.type} ref={elementRef} />
But How to define what exactly filter I need I have a name of filter in component MultyFilter
<MultyFilter
name={namef}
/>
but how to use it, i don't know, help me if u know how to do it.

Related

Is there a way to populate an input field from a list of buttons in React?

I am building a React application and I need to populate an input field from a list of predefined buttons (the text of the btn should be displayed on the input if the user clicks on the btn).
For example: let's suppose the user sees 3 btns with the following text for each: 'Banana', 'Apple','Orange'.
If the user clicks on the Banana btn, the world 'Banana' must appear on the input field.
Here is the code of App.js :
function App() {
const [suggestiveListBorder, setSuggestiveListBorder] = useState("");
return (
<div className="App">
<Navbar />
<ListOfExercises
suggestiveListBorder={suggestiveListBorder}
></ListOfExercises>
<div className="pages">
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/chest"
element={
<ChestWorkouts
setSuggestiveListBorder={setSuggestiveListBorder}
/>
}
/>
<Route path="/hamstrings" element={<HamstringsWorkouts />} />
the code of ListOfExercises.js
export function ListOfExercises({ suggestiveListBorder }) {
const currentLocation = useLocation();
let currentLocat = currentLocation.pathname;
const chestExos = exercisesData.exercises.chest_Exercises;
return (
<>
{currentLocat === "/chest" ? (
<ChestExosList
suggestiveListBorder={suggestiveListBorder}
chestExos={chestExos}
/>
) : currentLocat === "/hamstrings" ? (
<span>hamstrings</span>
) : null}
</>
);
}
the code of ChestExosList.js
export function ChestExosList({ chestExos, suggestiveListBorder }) {
const chestValues = Object.values(chestExos);
return (
<div
className="chest-exos-list-container"
style={{
border: suggestiveListBorder,
}}
>
<div>
<p className="chest-exos-paragraph">
Have no idea about the exercise you want to do ? Here are some
suggestions :
</p>
</div>
<Stack direction={"row"} spacing={2} sx={{ marginBottom: "30px" }}>
{chestValues.map(
(elem, index) =>
index < 8 &&
index >= 0 && (
<Button type="secondary" key={index}>
{elem}
</Button>
)
)}
</Stack>
the code of Chest page
const Chest = ({ setSuggestiveListBorder }) => {
const { workouts, dispatch } = useWorkoutsContext();
useEffect(() => {
const fetchWorkouts = async () => {
const response = await fetch("/api/workouts");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_WORKOUTS", payload: json });
}
};
fetchWorkouts();
}, [dispatch]);
const antIcon = (
<LoadingOutlined
style={{
fontSize: 50,
}}
/>
);
return (
<div className="chest-page-container">
<div className="chest-page-workouts">
{!workouts && <Spin spinning={true} indicator={antIcon}></Spin>}
{workouts &&
workouts.map((workout) => (
<WorkoutDetails workout={workout} key={workout._id} />
))}
</div>
<WorkoutForm setSuggestiveListBorder={setSuggestiveListBorder} />
</div>
);
};
and a snippet of the code of WorkoutForm.js :
return (
<form className="chest-workouts-form" onSubmit={handleSubmit}>
<div className="chest-workouts-form-inner">
<h3>Add a New Workout</h3>
<label>Excercise Title : </label>
<input
onMouseOver={() => {
setSuggestiveListBorder("1.5px solid #1aac83");
}}
type="text"
onChange={(e) => setTitle(e.target.value)}
value={title}
// value=""
className={emptyFields.includes("title") ? "form-msg-error" : ""}
/>
...
thank you all.
Yes, you can. You can add a different onClick event on each button (see first button in example) or a more generic one if you're planning on having many buttons (second button in example)
const Component = () => {
const [inputValue, setInputValue] = useState("");
const firstButtonText = "First button";
return (
<>
<button onClick={() => setInputValue(firstButtonText)}>{firstButtonText}</button
<button onClick={(e) => setInputValue(e.target.innerHTML)}>Second button</button>
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
</>
)
}
Create state for you input value :
const [inputValue,setInputValue]=useState('');
Give this value to your input:
<input
onMouseOver={() => {
setSuggestiveListBorder("1.5px solid #1aac83");
}}
type="text"
onChange={(e) => setTitle(e.target.value)}
value={inputValue}
className={emptyFields.includes("title") ? "form-msg-error" : ""}
/>
On your button click, you set this value:
<button onClick={()=> setInputValue("button one"}>...
Do the same for your other buttons

How can I add item on localStroge through ReactJS

I don't understand how I can add an item on localStroge with the handle change method in react js.
The problem is I want to add a favorite list. When I click the favorite list all checkbox fill I need to control them.
I want to store the filled item in my local storage. handlechange function fill all favorite icon why?
Every click will be a single item fill. Is it possible to Material UI or Martial UI checkBox icon? How can I handle it?
Here is my UI view
function Main() {
// Here is my state define
const [checked, setChecked] = React.useState(
localStorage.getItem("love") === "true"
);
const handleChange = (e) => {
localStorage.setItem("love", `${e.target.checked}`);
setChecked(e.target.checked);
console.log(e.target.checked);
};
return (
<>
<div className="row mt-5">
{isLoading ? (
<>
{Array.from({ length }).map((_, i) => (
<MainLoading key={i} />
))}
</>
) : error ? (
<p>Error occured</p>
) : (
<>
{data?.map((product) => (
<div className="col-md-3 mb-5 text-center" key={product.id}>
<img
className="w-100"
style={{ height: "200px", objectFit: "contain" }}
src={product.image}
alt=""
/>
<div>{product.title.substring(0, 20)}</div>
<button
onClick={() => handelAddTocard(product)}
className="mt-3"
>
Add to card
</button>
<button>
<Link
to={`/details/${product.id}`}
className="mt-3 text-decoration-none text-black"
>
view details
</Link>
</button>
{/* How can i control evey single item */}
<Checkbox
checked={checked}
onChange={handleChange}
icon={<FavoriteBorder />}
checkedIcon={<Favorite />}
/>
</div>
))}
</>
)}
</div>
</>
);
}
export default Main;
The problem is that you are using a boolean and you have no way to identify a specific item.
If you want to favorite multiple items, I would use something like this:
const [checked, setChecked] = React.useState(
JSON.parse(localStorage.getItem("loveIds") || "[]")
);
const handleCheck = (id, productChecked) => {
const newItems = productChecked ? [...checked, id] : checked.filter(x => x !== id);
localStorage.setItem("loveIds", JSON.stringify(newItemS));
setChecked(newItems);
console.log(newItems);
};
// ...
<Checkbox
checked={checked}
onChange={(e) => handleCheck(product.id, e.target.checked)}
icon={<FavoriteBorder />}
checkedIcon={<Favorite />}
/>

onClick load react component in the same place

I have a panel with 3 buttons, i want to make onclick on every button, a different component will appear in the same place. How can this logic be done?
<AddNotification />
<EditNotification />
<DeleteNotification />
const AdminPanel = () => {
return (
<Card className={classes.input}>
<div><h1>Notification Panel</h1></div>
<form>
<div className="form-group">
<Button type="submit">Add Notification</Button>
</div>
<div className="form-group">
<Button type="submit">Edit Notification</Button>
</div>
<div className="form-group">
<Button type="submit">Delete Notification</Button>
</div>
</form>
</Card>
)
}
#MLDC No i don't have another divs, i want to replace the buttons with the crossponding component. For example: onclick on Add, then Add component will appears instead of the buttons.
In that case, create a boolean state for every Panel that you have (I created 3 so that you could open the panels simultaneously),
const [isAddPanelOpen, setIsAddPanelOpen] = useState(false);
const [isEditPanelOpen, setIsEditPanelOpen] = useState(false);
const [isDeletePanelOpen, setIsDeletePanelOpen] = useState(false);
Next, apply this to every button
<Button onClick={setIsAddPanelOpen(prevState=>!prevState)}>Add Notification</Button>
<Button onClick={setIsEditPanelOpen(prevState=>!prevState)}>Edit Notification</Button>
<Button onClick={setIsDeletePanelOpen(prevState=>!prevState)}>Delete Notification</Button>
Lastly, Refactor your html to
<div className="form-group">
{isAddPanelOpen ? <AddNotification/> : <Button type="submit">Add Notification</Button>}
</div>
<div className="form-group">
{isEditPanelOpen ? <EditNotification/> : <Button type="submit">Edit Notification</Button>}
</div>
<div className="form-group">
{isDeletePanelOpen ? <DeleteNotification/> :<Button type="submit">Delete Notification</Button>}
</div>
Try this if you want to display one component at a time and hide the others when you click a button
const AdminPanel = () => {
const [componentToDisplay, setComponentToDisplay] = useState("")
return (
<>
<Card className={classes.input}>
<div><h1>Notification Panel</h1></div>
<form>
<div className="form-group">
{componentToDisplay !== "add ? (
<Button type="submit" onCLick={() => setComponentTodisplay("add")}>Add Notification</Button>)
:(<AddNotification />)}
</div>
<div className="form-group">
{componentToDisplay !== "edit ? (
<Button type="submit" onCLick={() => setComponentTodisplay("edit")}>Edit Notification</Button>)
:(<EditNotification />)}
</div>
<div className="form-group">
{componentToDisplay !== "delete ? (
<Button type="submit" onCLick={() => setComponentTodisplay("delete")}>Delete Notification</Button>)
:(<DeleteNotification />)}
</div>
</form>
</Card>
</>
)
}
Or if you want to be able to replace every buttons, use this logic with one state per button
const AdminPanel = () => {
const [addNotif, setAddNotif] = useState(false)
const [editNotif, setEditNotif] = useState(false)
const [deleteNotif, setDeleteNotif] = useState(false)
return (
<>
<Card className={classes.input}>
<div><h1>Notification Panel</h1></div>
<form>
<div className={`form-group ${editNotif || deleteNotif ? "display: none" : "display: flex"}`}>
{!addNotif ? (
<Button type="submit" onCLick={() => setAddNotif(true)}>Add Notification</Button>)
:(<AddNotification setAddNotif={setAddNotif} />)}
</div>
<div className={`form-group ${addNotif || deleteNotif ? "display: none" : "display: flex"}`}>
{!editNotif ? (
<Button type="submit" onCLick={() => setEditNotif(true)}>Edit Notification</Button>)
:(<EditNotification setEditNotif={setEditNotif} />)}
</div>
<div className={`form-group ${addNotif || editNotif ? "display: none" : "display: flex"}`}>
{!deleteNotif ? (
<Button type="submit" onCLick={() => setDeleteNotif(true)}>Delete Notification</Button>)
:(<DeleteNotification setDeleteNotif={setDeleteNotif} />)}
</div>
</form>
</Card>
</>
)
}
Then in your component
const AddNotification = ({setAddNotif}) => {
...
return (
...
<button onCLick={() => setAddNotif(false)}>back</button>
...
)
}
Same logic for the other components
To achieve this logic you need to manage which component is displayed using a state.
This means:
Attribute an arbitrary id to each component.
Store the id of the active component in a useState hook.
Use conditional rendering to display the component that match the current state.
Update the state to the corresponding Id when clicking on each button.
A small example
const [activePanel, setActivePanel] = React.useState(0)
let currentPanel = <Panel0 />
switch(activePanel){
case 0:
currentPanel = <PanelO />
case 1:
currentPanel = <Panel1 />
// Continue as needed
}
return (
<div>
<CurrentPanel/>
<button onClick={() => setActivePanel (0)}> Panel 0 </button>
<button onClick={() => setActivePanel (1)}> Panel 1 </button>
// And so on
</div>
)
You can further refine this by extracting the switch statement into its own component that takes the activePanel as a prop.

React component second time render issue

I am building a tasks board app and I have an issue with rendering the TasksList component within a board of 3 lists: 1 Board -> 3 lists -> N tasks
It seems like the TasksList component is being rendered twice, which is fine, but on the 2nd time it seems to return different values for each task (which are wrong according to my conditional return, and right on the first render - why would there be a difference?)
I also get this warning. Maybe ts related:
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
Board.js component render function:
const tasksListsArr = Object.entries(this.state.tasks).map(list => {
return (
<TasksList
key={list[0]}
listrole={list[0]}
listTitle={this.state.Lists[list[0]]}
tasks={list[1]}
listTextChangedHandler={event => this.listTextChangedHandler(list[0], event)}
addTaskHandler={() => this.addTaskHandler(list[0])}
taskDeleteHandler={this.taskDeleteHandler}
moveTaskHandler={this.moveTaskHandler}
taskEditHandler={this.taskEditHandler}
taskEditApprove={this.taskEditApprove}
/>
)
})
TaskList.js component:
import React from "react"
import classes from "./TasksList.module.css"
const TasksList = props => {
const tasks = props.tasks.map(task => {
const buttonLeft =
!task.isEdit && (props.listrole === "inprogress" || props.listrole === "done") ? (
<button onClick={() => props.moveTaskHandler(task.id, "left")}>left</button>
) : null
const buttonRight =
!task.isEdit && (props.listrole === "inprogress" || props.listrole === "backlog") ? (
<button onClick={() => props.moveTaskHandler(task.id, "right")}>right</button>
) : null
const taskUtils = task.isEdit ? null : (
<div>
<span onClick={() => props.taskDeleteHandler(props.listrole, task.id)}>X</span>
<span onClick={() => props.taskEditHandler(props.listrole, task.id)}>edit</span>
</div>
)
const taskContent = task.isEdit ? (
<div>
<input
type='text'
onChange={event => props.listTextChangedHandler(props.listrole, event)}
/>
<button onClick={props.taskEditApprove(props.listrole, task.id)}>OK</button>
</div>
) : (
<div>
<div>{task.text}</div>
</div>
)
return (
<div key={task.id} className={classes.Task}>
{buttonLeft}
{taskContent}
{buttonRight}
{taskUtils}
</div>
)
})
console.log(tasks)
return (
<div className={classes.List}>
<h2 className={classes.ListTitle}> {props.listTitle} </h2>
<input type='text' onChange={props.listTextChangedHandler} placeholder='Add task...' />
<button onClick={props.addTaskHandler}>+</button>
<div className={classes.TasksList}>{tasks}</div>
</div>
)
}
export default TasksList
I suspect the issue is in TaskList component. Because the onChange of input and onClick of button getting called on every render but those event handler functions should be called only when user integrated with it. So to fix change them to arrow way so that the function gets called only when we interact.
Following changes required in TaskList.js
Change
<button onClick={props.taskEditApprove(props.listrole, task.id)}>OK</button>
To
<button onClick={() => props.taskEditApprove(props.listrole, task.id)}>OK</button>
And
Change
<input type='text' onChange={props.listTextChangedHandler} placeholder='Add task...' />
<button onClick={props.addTaskHandler}>+. </button>
To
<input type='text' onChange={event => props.listTextChangedHandler(event)} placeholder='Add task...' />
<button onClick={() => props.addTaskHandler()}>+</button>
OK
props.taskEditApprove is being called in render.
Try
props.taskEditApprove(props.listrole, task.id)}>OK
Then function will be called only on interaction.

React having onClick work only once on a component

Hello I am trying to build a forum site. I have simple categories and comments. I have a button that displays a text box to enter a comment on a category, but when I click the button it opens text boxes on every category. I just want one text box. Here is the code I have so far. Any help would be appreciated. Thanks
state = { showing: true };
renderLists(categories) {
const { showing } = this.state;
if (categories == null) return <div />;
return (
<ul className="ul">
{categories.map(category => {
return (
<li id={category._id} className="categories" key={category._id}>
{category.name}
<Posts categ={category._id} />
<button
className="label"
onClick={() => this.setState({ showing: !showing })}
>
Add Comment
</button>
{showing ? (
<div>
<form method="post" action="/post/create-posts">
<input type="text" name="body" />
<input type="hidden" name="cat" value={category._id} />
<input type="submit" value="Submit" />
</form>
</div>
) : null}
</li>
);
})}
</ul>
);
}
render() {
return (
<div>
<main className="categories">
{this.renderLists(this.state.category)}
</main>
</div>
);
}
Since you are controlling render of the form with a single state value, ever form for every item renders the form when the state value changes.
You should set a unique value on state to show single form every time.
Saving currently active items id to state and checking if that item is active or not can be a simple solution. This also ensures to only single form to be active. If you need to enable multiple forms to be active, you can change below code to hold an array of ids and checking if the id exist in array or not. This implementation also requires you to remove the id from array on a second click to remove form for that item.
Sample
state = { showing: ''};
renderLists(categories) {
const { showing } = this.state;
if (categories == null) return <div />;
return (
<ul className="ul">
{categories.map(category => {
return (
<li id={category._id} className="categories" key={category._id}>
{category.name}
<Posts categ={category._id} />
<button
className="label"
{/* Save item's id to state */}
onClick={() => this.setState({ showing: category._id })}
>
Add Comment
</button>
{/* check item's id */}
{(showing === category._id) ? (
<div>
<form method="post" action="/post/create-posts">
<input type="text" name="body" />
<input type="hidden" name="cat" value={category._id} />
<input type="submit" value="Submit" />
</form>
</div>
) : null}
</li>
);
})}
</ul>
);
}
You can set the dynamic state for each of your category. I have made it below. Initially it was not there in state. On clicking the button, it will update the state with new key(such as categoryName) as true. I have fixed it as toggle. If the required key is true then it will become false.
item.state = { showing: true };
renderLists(categories) {
const { showing } = this.state;
if (categories == null) return <div />;
return (
<ul className="ul">
{categories.map(category => {
let categoryName = category.name // get the categoryName in variable
return (
<li id={category._id} className="categories" key={category._id}>
{category.name}
<Posts categ={category._id} />
//You can also access the object values by using bracket ([]) notation. You can set the dynamic state with unique key, may be we use name as key here
<button
className="label"
onClick={() => {(this.state[categoryName] != undefined) ? (this.state[categoryName] ? this.setState({ [categoryName]: false }) : this.setState({ [categoryName]: true })) : this.setState({ [categoryName]: true })} }
>
Add Comment
</button>
{this.state[categoryName] ? (
<div>
<form method="post" action="/post/create-posts">
<input type="text" name="body" />
<input type="hidden" name="cat" value={category._id} />
<input type="submit" value="Submit" />
</form>
</div>
) : null}
</li>
);
})}
</ul>
);
}
render() {
return (
<div>
<main className="categories">
{this.renderLists(this.state.category)}
</main>
</div>
);
}

Resources