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.
Related
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.
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.
now, I using radio button to render 2 components differently I use onClick with radio button to [isRepeat,setisRepeat] setState value
<input
required
id="singleClass"
name="classType"
type="radio"
className="inputContainer"
value={1}
OnClick={() => setisRepeat("oneTime")}
></input>
<label>ครั้งเดียว</label>
<input
required
id="repeatClass"
name="classType"
type="radio"
className="inputContainer"
value={2}
onClick={() => setisRepeat("repeat")}
></input>
and render component like this
{isRepeat === "oneTime" && (
<>...</>
)}
{isRepeat === "repeat" && (
<div>...</div>
)}
but when I using onClick() with radio button, It's make user to click "twice" to make radio button checked
(actually, setState work fine when click radio button but radio button didn't be checked. It should be checked and setState at the same time to make user not confuse)
ps. I already using document.getElementByName('classType').value === 1 or 2 to render different items, but didn't work
You should use onChange with input as:
onChange={() => setisRepeat("oneTime")}
instead of onClick
Demo
export default function App() {
const [isRepeat, setisRepeat] = useState(""); // You can also give the initial value
return (
<div>
<input
required
id="singleClass"
name="classType"
type="radio"
className="inputContainer"
value={1}
onChange={() => setisRepeat("oneTime")}
></input>
<label>ครั้งเดียว</label>
<input
required
id="repeatClass"
name="classType"
type="radio"
className="inputContainer"
value={2}
onChange={() => setisRepeat("repeat")}
></input>
<br />
{isRepeat === "oneTime" && <> One Time </>}
{isRepeat === "repeat" && <div> Repeat </div>}
</div>
);
}
You can use onChange instead of onClick handler and checked property along with it.
import { useState } from "react";
export default function App() {
const [isRepeat, setisRepeat] = useState("");
const onClickHandler = (type) => {
if (type === "singleClass") {
setisRepeat("singleClass");
} else {
setisRepeat("repeatClass");
}
};
return (
<div className="App">
<input
required
name="singleClass"
type="radio"
className="inputContainer"
value={1}
checked={isRepeat === "singleClass"}
onChange={() => {
onClickHandler("singleClass");
}}
></input>
<label>ครั้งเดียว</label>
<input
required
name="repeatClass"
type="radio"
className="inputContainer"
value={2}
checked={isRepeat === "repeatClass"}
onChange={() => {
onClickHandler("repeatClass");
}}
></input>
{isRepeat === "singleClass" && (
<>
<p>One time component Triggered </p>
</>
)}
{isRepeat === "repeatClass" && <div>Repeat component Triggered </div>}
</div>
);
}
Demo
In the first input you have a typo. You have OnClick but you want onClick
It works for me with one click like this
(I removed a few props to make it shorter)
import React from 'react';
import { useState } from 'react';
const Demo = () => {
const [isRepeat, setIsRepeat] = useState('');
return (
<>
<input type="radio" value={1} onClick={() => setIsRepeat('oneTime')}></input>
<label>ครั้งเดียว</label>
<input type="radio" value={2} onClick={() => setIsRepeat('repeat')}></input>
{isRepeat === 'oneTime' && <span>One Time</span>}
{isRepeat === 'repeat' && <span>repeat</span>}
</>
);
};
export default Demo;
I have a component inside formik -based step wizard.
I make use of useField, useEffect and useState hooks as below:
const [counts, countsMeta, countsHelpers] = useField('counts');
const [visible, setVisible] = useState(false);
useEffect(() => {
countsHelpers.setValue(
Array(event.book && event.book.length ? event.book.length : 0).fill(0)
);
// eslint-disable-next-line
}, [event.book]);
I understand that my useEffect should only be called once, no matter what as long as event.book never changes.
I also have a way to apply coupons as below:
<div className={styles.coupon}>
<FontAwesomeIcon
icon={faPlusSquare}
onClick={() => setVisible(!visible)}
/>{' '}
coupon code
{visible && (
<div>
<input
id='coupon'
type='text'
value={values.coupon || ''}
onChange={onChange}
placeholder='Coupon code'
className={errors.coupon ? styles.error : ''}
/>
<div
className={styles.apply}
onClick={() => loadPromo(values.coupon)}
>
Apply
</div>
</div>
)}
</div>
For some reason I cannot pinpoint, when I call loadPromo() my component re-initializes. Once it happens, state and fields are reset e.g. visible is again set to false and counts array reinitialized with zeros. After that, everything is working fine.
What am I missing?
I have to show a list of defaultValues in the search list and when I click on any of those item then it should take me to that item's component but it's not going anywhere. It's only happening with the defaultValues because as soon as I start typing, then if I click on any search result then it takes me to the desired component. what is wrong with my code?
here's the code
const [search, setSearch] = useState("");
const [showDefaultValues, setShowDefaultValues] = useState(false);
const [defaultValues] = useState({
Mumbai: true
});
{!search.length && showDefaultValues ? (
<div className="result-box">
{data
.filter((item, idx) => defaultValues[item.district])
.map((dist, idx) => (
<div
key={idx}
className="search-result"
onClick={() => {
onResultClick(dist.district);
}}
>
{highlightedText(dist.district, search)}
</div>
))}
</div>
) : null}
Just change the codes at components/search/search.js line 39 to 49
<input
placeholder="Search for a District..."
type="text"
className="search-input"
value={search}
onChange={onSearchInputChange}
onFocus={() => {
toggleDefaultValues(true);
}}
onBlur={onBlurInput}
/>
To
<input
placeholder="Search for a District..."
type="text"
className="search-input"
value={search}
onChange={onSearchInputChange}
onFocus={() => {
toggleDefaultValues(true);
}}
/>
Or simply remove line 48
To compensate this, you can add below inside your useEffect (similar to componentDidMount)
document.addEventListener("mousedown", handleInputClickOutside);
add function handleInputClickOutside to set the state to false/hide
You forgot to implement the onClick logic on the default search result items and that's why the search results work fine, while the default search items do not.
Check this link to the working codesandbox.
All i did was invoke the same onResultClick function onClick of 'District' component.
<div
className="dist"
onClick={() => {
this.props.onResultClick(item.district);
}}
>
...
</div>
Hope this solves your problem.