How to show form only for one element by id? - javascript

I want to show my edit form while clicking on some image, but it shows for all tasks, and I cant figure out how to do this only for one task
I tried to show edit form, changing boolean value "active" by useState. However, I dont understand how to create the function, that obtain id of task and show edit form only for this task.
Thanks in advance for reply
In App.js I have:
import {useState} from 'react'
import Tasks from "./components/Tasks";
function App() {
const [editActive, setEditActive] = useState(false)
const [tasks, setTasks] = useState( [
{
id: 1,
title: 'Task',
date: "12/03/2023 10:30",
status: false,
urgently: false,
}
....
])
....
return (
<div className="container">
<Tasks tasks={tasks} active={editActive} setActive={() => setEditActive(!editActive)} />
<div>
)
In Tasks.js:
import Task from "./Task"
const Tasks = ({tasks, active, setActive}) => {
return (
<div>
{tasks.map((task) => (
<Task key ={task.id} task={task} active={active} setActive={setActive}/>
))}
</div>
)
}
In Task.js:
import {useState} from 'react'
import {FaTimes, FaPencilAlt} from 'react-icons/fa'
import EditTask from './EditTask'
const Task = ({task, active, setActive}) => {
...
return (
<p>{task.date} <FaPencilAlt style={{cursor:'pointer'}} onClick={setActive}/></p>
{active && <EditTask onUpdate={onUpdate} task={task} setActive={setActive}/>}
)

Use conditional statement like
if(useState)
{
//put your function here to open task
}

You can pass the task id in setActive itself like setActive(task.id) and you can update task using useEffect hook in App.js to find the specific task on every modalActive change.

It was really simple. In Task.js you have to add this:
const Task = ({task, onUpdate}) => {
const [show, setShow] = useState(false);
const editTaskChanged = () =>{setShow(!show)}
....
return (
<p>{task.date} (<FaPencilAlt style={{cursor:'pointer'}} onClick={() =>
editTaskChanged()}/>)</p>
{show && <EditTask onUpdate={onUpdate} task={task} />}
)
After that, when you click on image in 'p' tag - you can see edit form

Related

how to keep the last component styles?

i'm building a to-do app using React Js . inside the task component i used a state to apply a certain styles for the completed task and it works fine . but , after i cliked any delete button the style of the completed task deleted . how can i prevent that ?
import Task from "../Task/Task";
import style from "./TasksList.module.css";
const TasksList = ({ tasks, deleteTaskHandler }) => {
return (
<div className={style.tasks}>
<div className="container">
{tasks.map((task, idx) => {
return (
<Task
task={task}
id={idx}
key={Math.random()}
deleteTaskHandler={deleteTaskHandler}
/>
);
})}
</div>
</div>
);
};
export default TasksList;
import { useState } from "react";
import style from "./Task.module.css";
const Task = ({ task, id, deleteTaskHandler }) => {
const [isComplete, setIsComplete] = useState(false);
const markComplete = () => {
setIsComplete(!isComplete);
};
return (
<div
className={
isComplete ? `${style.task} ${style.completed}` : `${style.task}`
}
onClick={markComplete}
>
<label>{task.desc}</label>
<button onClick={() => deleteTaskHandler(id)}> Delete </button>
</div>
);
};
export default Task;
How are you maintaining the complete status of task in higher components?
Currently you are not initializing the complete state of Task.
If the task object contains the isComplete property, then you can use as shown below
const [isComplete, setIsComplete] = useState(task.isComplete);
however, you also need to update value of completed in task. So, I would suggest to have all lower components as stateless. and maintain the state at Higher component i.e. TaskList
import style from "./Task.module.css";
const Task = ({ task, id, deleteTaskHandler, setTaskCompleteHandler }) => {
const markComplete = () => {
setTaskCompleteHandler(!task.isComplete);
};
return (
<div
className={
task.isComplete ? `${style.task} ${style.completed}` : `${style.task}`
}
onClick={markComplete}
>
<label>{task.desc}</label>
<button onClick={() => deleteTaskHandler(id)}> Delete </button>
</div>
);
};
export default Task;
Implement setTaskCompleteHandler in TaskList and pass is as prop as part of Task component reandering.
Your problem is in position of your delete button, that wrapped by div with makrComplete handler, so then you click on your delete button, markComplete fired too, so your isComplete changed and styles deleted.
To prevent this behavor, you can do a little trick with prevent default. So, try to wrap your deleteTaskHandler in another function like that:
const deleteButtonClickHandler = (e) => {
e.preventDefault();
e.stopPropagation();
deleteTaskHandler(id)
}
and your delete button makrdown should be look like this:
<button onClick={deleteButtonClickHandler}> Delete </button>

Modal component does not render on a custom button component

I am trying to render a custom and dynamic modal on button clicks. For example, when a "Game" button is clicked, I would like a modal to render with specfics about the game and when a "Bank" button is clicked, I would like the modal to populate with specfics about a bank.
First, when I add an onClick function to a custom button component, the modal does not render. However, when I put the onClick function on a regular button, the modal does render. How can I simply add an onClick function on any component to render a dynamic modal?
Second, I would like to populate each modal with differnet data. For example, a "Game" button would populate the modal with a title of "Game" and so on. I'm using props to do this, but is that the best solution?
Here is the code I have so far, but it is broken when I add the onClick function to components.
// Navbar.js
import { ModalContext } from '../contexts/ModalContext'
function Navbar() {
const [showModal, updateShowModal] = React.useState(false)
const toggleModal = () => updateShowModal((state) => !state)
return(
<ModalContext.Provider value={{ showModal, toggleModal }}>
<Modal
title="Title"
canShow={showModal}
updateModalState={toggleModal}
/>
</ModalContext.Provider>
)
// does not render a modal
<Button
onClick={toggleModal}
type="navItem"
label="Game"
icon="windows"
/>
// render a modal
<button onClick={toggleModal}>Show Modal</button>
)
}
import { ModalContext } from '../contexts/ModalContext'
// Modal.js
const Modal = ({ title }) => {
return (
<ModalContext.Consumer>
{(context) => {
if (context.showModal) {
return (
<div style={modalStyles}>
<h1>{title}</h1>
<button onClick={context.toggleModal}>X</button>
</div>
)
}
return null
}}
</ModalContext.Consumer>
)
}
// modalContext.js
export const ModalContext = React.createContext()
// Button.js
function Button({ label, type = 'default', icon }) {
return (
<ButtonStyle buttonType={type}>
{setIcon(icon)}
{label}
</ButtonStyle>
)
}
First problem:
I think the onClick prop of the <Button> component is not pointing to the onClick of the actual HTML button inside the component.
Could you please check that? And if you think It's been set up in the right way, then can you share the code of the component?
Second Problem
Yes, there's another way to do that. And I think it's React Composition. You can build the modal as the following:
<Modal
showModal={showModal}
updateModalState={toggleModal}
>
<div className="modal__header">{title}</div>
<div className="modal__body">{body}</div>
<div className="modal__footer">{footer}</div>
</Modal>
I think this pattern will give you more control over that component.
Issue
You are not passing the onClick prop through to the styled button component.
Solution
Given style-component button:
const ButtonStyle = styled.button``;
The custom Button component needs to pass all button props on to the ButtonStyle component.
// Button.js
function Button({ label, type='default', icon, onClick }) {
return (
<ButtonStyle buttonType={type} onClick={onClick}>
{setIcon(icon)}
{label}
</ButtonStyle>
)
}
If there are other button props then you can use the Spread syntax to collect them into a single object that can then be spread into the ButtonStyle component.
// Button.js
function Button({ label, type = 'default', icon, ...props }) {
return (
<ButtonStyle buttonType={type} {...props}>
{setIcon(icon)}
{label}
</ButtonStyle>
)
}
Second Question
For the second issue I suggest encapsulating the open/close/title state entirely in the modal context provider, along with the Modal component.
Here's an example implementation:
const ModalContext = React.createContext({
openModal: () => {},
});
const Modal = ({ title, onClose}) => (
<>
<h1>{title}</h1>
<button onClick={onClose}>X</button>
</>
)
const ModalProvider = ({ children }) => {
const [showModal, setShowModal] = React.useState(false);
const [title, setTitle] = React.useState('');
const openModal = (title) => {
setShowModal(true);
setTitle(title);
}
const closeModal = () => setShowModal(false);
return (
<ModalContext.Provider value={{ openModal }}>
{children}
{showModal && <Modal title={title} onClose={closeModal} />}
</ModalContext.Provider>
)
}
Example consumer to set/open a modal:
const OpenModalButton = ({ children }) => {
const { openModal } = useContext(ModalContext);
return <button onClick={() => openModal(children)}>{children}</button>
}
Example usage:
function App() {
return (
<ModalProvider>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<OpenModalButton>Modal A</OpenModalButton>
<OpenModalButton>Modal B</OpenModalButton>
</div>
</ModalProvider>
);
}
Demo

React render list only when data source changes

Basically I have a modal with a state in the parent component and I have a component that renders a list. When I open the modal, I dont want the list to re render every time because there can be hundreds of items in the list its too expensive. I only want the list to render when the dataSource prop changes.
I also want to try to avoid using useMemo if possible. Im thinking maybe move the modal to a different container, im not sure.
If someone can please help it would be much appreciated. Here is the link to sandbox: https://codesandbox.io/s/rerender-reactmemo-rz6ss?file=/src/App.js
Since you said you want to avoid React.memo, I think the best approach would be to move the <Modal /> component to another "module"
export default function App() {
return (
<>
<Another list={list} />
<List dataSource={list} />
</>
);
}
And inside <Another /> component you would have you <Modal />:
import React, { useState } from "react";
import { Modal } from "antd";
const Another = ({ list }) => {
const [showModal, setShowModal] = useState(false);
return (
<div>
<Modal
visible={showModal}
onCancel={() => setShowModal(false)}
onOk={() => {
list.push({ name: "drink" });
setShowModal(false);
}}
/>
<button onClick={() => setShowModal(true)}>Show Modal</button>
</div>
)
}
export default Another
Now the list don't rerender when you open the Modal
You can use React.memo, for more information about it please check reactmemo
const List = React.memo(({ dataSource, loading }) => {
console.log("render list");
return (
<div>
{dataSource.map((i) => {
return <div>{i.name}</div>;
})}
</div>
);
});
sandbox here

React Todo App I get an error while converting class components to functional components

First of all, thank you in advance for your help. While making Todo App, I made adding and removing operations into functional components, but I could not make other components. I would be glad if you could help.
TodoItem.js: (I tried a lot but could not make it functional due to errors.)
class TodoItem extends Component {
render() {
const { id, title } = this.props.todo // I did not understand here what it does.
return (
<div>
<p>
<input
type="checkbox"
onChange={this.props.markComplete.bind(this, id)} // and here too
/>
{""} {title}{" "}
<button onClick={this.props.deleteTodo.bind(this, id)}>X </button>{" "}
</p>{" "}
</div>
)
}
}
Addtodo.js: (I converted it to functional but it doesn't list the input I wrote.)
const Addtodo = () => {
const [title, setTitle] = useState("")
const onSubmit = (e) => { // I made a mistake here, I don't know why there is a problem.
e.preventDefault()
setTitle("")
}
const onChange = (e) => setTitle(e.target.value)
return (
<form onSubmit={onSubmit}>
<input
type="text"
onChange={onChange}
value={title}
placeholder="Add todo"
/>
<input type="Submit" value="Submit" className="btn" />
</form>
)
}
App.js component: (I was able to make them functional. I would be glad if you can check it.)
const App = () => {
const [todos, setTodos] = useState([])
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/todos")
.then((res) => setTodos(res.data))
}, [])
const markComplete = (id) => {
setTodos(
todos.map((todo) => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
)
}
const deleteTodo = (id) => {
axios
.delete(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then((res) => setTodos([...todos.filter((todo) => todo.id !== id)]))
}
// Add Todo
const Addtodo = (title) => {
axios
.post("https://jsonplaceholder.typicode.com/todos", {
title,
completed: false
})
.then((res) => setTodos([...todos, res.data]))
}
return (
<div className="App">
<div className="container">
<Header />
<AddTodo Addtodo={Addtodo} /> // I think I made a mistake here with the props.
<Todo
todos={todos}
markComplete={markComplete}
deleteTodo={deleteTodo}
/>{" "}
</div>{" "}
</div>
)
}
There is a mismatch between your definition of the AddTodo component, and then adding your AddTodo component to the DOM. In Addtodo.js, you've got the function signature as:
const Addtodo = () => {
Therefore the Addtodo component is not expecting any props to be passed into it.
In App.js, you try to add the component to the DOM with:
<AddTodo Addtodo={Addtodo} />
So you're asking the component to be rendered with a prop called Addtodo, but as stated earlier, in the component's definition, it's not expecting to receive any props.
You need to decide whether or not you want / need the AddTodo component to receive props.
If you want it to receive the AddTodo prop, you can change the function definition to:
const Addtodo = ({ AddTodo }) => {
Also, make sure that when you export the Addtodo component, you export it as a default export, as the casing is currently inconsistent in your code (defined as Addtodo, but tried to render as AddTodo in App.js). If this is a default export though, it doesn't matter too much. Make sure your import statement for the AddTodo is the same as when you render AddTodo.
To be explicit, make sure you have export default Addtodo in Addtodo.js
Make sure the import statement for the add todo component is the same. So, if the top of App.js says import AddTodo from './Addtodo', then when you render the component later in the file, it is done like <AddTodo />. And if the import was import Addtodo from './Addtodo', then the component is rendered as <Addtodo> (casing must be consistent)
I apologise if that wasn't the clearest explanation, I'm unsure of what the actual terms for some of the things I referred to are, but I hope you got what I was trying to say.
You can remove the onSubmit function from the form element and use it with onClick in the button element. Give it a try. I think it will work.

How do I add the ability to edit text within a react component?

So here's the user function I'm trying to create:
1.) User double clicks on text
2.) Text turns into input field where user can edit text
3.) User hits enter, and upon submission, text is updated to be edited text.
Basically, it's just an edit function where the user can change certain blocks of text.
So here's my problem - I can turn the text into an input field upon a double click, but how do I get the edited text submitted and rendered?
My parent component, App.js, stores the function to update the App state (updateHandler). The updated information needs to be passed from the Tasks.jsx component, which is where the text input is being handled. I should also point out that some props are being sent to Tasks via TaskList. Code as follows:
App.js
import React, {useState} from 'react';
import Header from './Header'
import Card from './Card'
import cardData from './cardData'
import Dates from './Dates'
import Tasks from './Tasks'
import Footer from './Footer'
import TaskList from './TaskList'
const jobItems= [
{
id:8,
chore: 'wash dishes'
},
{
id:9,
chore: 'do laundry'
},
{
id:10,
chore: 'clean bathroom'
}
]
function App() {
const [listOfTasks, setTasks] = useState(jobItems)
const updateHandler = (task) => {
setTasks(listOfTasks.map(item => {
if(item.id === task.id) {
return {
...item,
chore: task.chore
}
} else {
return task
}
}))
}
const cardComponents = cardData.map(card => {
return <Card key = {card.id} name = {card.name}/>
})
return (
<div>
<Header/>
<Dates/>
<div className = 'card-container'>
{cardComponents}
</div>
<TaskList jobItems = {listOfTasks} setTasks = {setTasks} updateHandler = {updateHandler}/>
<div>
<Footer/>
</div>
</div>
)
}
export default App;
Tasks.jsx
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
TaskList.jsx
import React from 'react'
import Tasks from './Tasks'
function TaskList (props) {
const settingTasks = props.setTasks //might need 'this'
return (
<div>
{
props.jobItems.map(item => {
return <Tasks key = {item.id} item = {item} setTasks = {settingTasks} jobItems ={props.jobItems} updateHandler = {props.updateHandler}/>
})
}
</div>
)
}
export default TaskList
You forgot onChange handler on input element to set item's chore value.
Tasks.jsx must be like below
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
const handleInputChange = (e)=>{
// console.log( e.target.value );
// your awesome stuffs goes here
}
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' onChange={handleInputChange} defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
So, first of all, I would encourage you not to switch between input fields and divs but rather to use a contenteditable div. Then you just use the onInput attribute to call a setState function, like this:
function Tasks ({item}) {
return(
<div className = 'tasks-container'>
<div contenteditable="true" onInput={e => editTask(item.id, e.currentTarget.textContent)} >
{item.chore}
</div>
</div>
)
}
Then, in the parent component, you can define editTask to be a function that find an item by its id and replaces it with the new content (in a copy of the original tasks array, not the original array itself.
Additionally, you should avoid renaming the variable between components. (listOfTasks -> jobItems). This adds needless overhead, and you'll inevitably get confused at some point which variable is connected to which. Instead say, <MyComponent jobItems={jobItems} > or if you want to allow for greater abstraction <MyComponent items={jobItems} > and then you can reuse the component for listable items other than jobs.
See sandbox for working example:
https://codesandbox.io/s/practical-lewin-sxoys?file=/src/App.js
Your Task component needs a keyPress handler to set isEditing to false when enter is pressed:
const handleKeyPress = (e) => {
if (e.key === "Enter") {
setIsEditing(false);
}
};
Your updateHandler should also be passed to the input's onChange attribute, and instead of defaultValue, use value. It also needs to be reconfigured to take in the onChange event, and you can map tasks with an index to find them in state:
const updateHandler = (e, index) => {
const value = e.target.value;
setTasks(state => [
...state.slice(0, index),
{ ...state[index], chore: value },
...state.slice(index + 1)
]);
};
Finally, TaskList seems like an unnecessary middleman since all the functionality is between App and Task; you can just render the tasks directly into a div with a className of your choosing.
react-edit-text is a package I created which does exactly what you described.
It provides a lightweight editable text component in React.
A live demo is also available.

Categories

Resources