how to keep the last component styles? - javascript

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>

Related

How to show form only for one element by id?

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

How to use state value in another js file

I have a file named Card.js. It contains two states called status and view .
Every time a button is clicked, my status state changes & according to this state my card is hidden.
At the same time, each of my cards has a view state. This increases by 1 according to the click event.
src/components/Card.js
const Card = (props) => {
const [status, setStatus] = useState(false);
const [view, setView] = useState(0);
const handleClick = () => {
setStatus(!status);
setView(view + 1);
};
return (
<div>
<Button onClick={handleClick}>{status ? "Show Component" : "Hide Component"}</Button>
{status ? null : <div className="card">{props.children} </div>}
</div>
);
};
export default Card;
In my app.js file, I return the data in my JSON file with the map method, I print this data to my card component.
src/App.js
const App= () => {
return (
<div>
{post.map((value, index) => {
return (
<Card key={index}>
{// here I want to print the "view" state in my Card.js file.}
</Card>
);
})}
</div>
);
};
export default App;
In App.js, I tried to create the view state using the useEffect hook, but every time I click the button, the view state of both my cards is updated at the same time. I don't want it to happen this way.
You can pass the data in your card as props so that the data is available in your Card module.
{
post.map((value, index) => {
return (
<Card key={index} status={false}>
{// here I want to print the "view" state in my Card.js file.}
</Card>
);
})
}
And in card module, pass the prop value to useState. I hope this will solve your problem.
const [status, setStatus] = useState(props.status);

How to re-render React components without actually changing state

In my React application I have a component called Value, which has several instances on multiple levels of the DOM tree. Its value can be shown or hidden, and by clicking on it, it shows up or gets hidden (like flipping a card).
I would like to make 2 buttons, "Show all" and "Hide all", which would make all these instances of the Value component to show up or get hidden. I created these buttons in a component (called Cases) which is a parent of each of the instances of the Value component. It has a state called mode, and clicking the buttons sets it to "showAll" or "hideAll". I use React Context to provide this chosen mode to the Value component.
My problem: after I click the "Hide All" button and then make some Value instances visible by clicking on them, I'm not able to hide all of them again. I guess it is because the Value components won't re-render, because even though the setMode("hideAll") function is called, it doesn't actually change the value of the state.
Is there a way I can make the Value instances re-render after calling the setMode function, even though no actual change was made?
I'm relatively new to React and web-development, I'm not sure if it is the right approach, so I'd also be happy to get some advices about what a better solution would be.
Here are the code for my components:
const ModeContext = React.createContext()
export default function Cases() {
const [mode, setMode] = useState("hideAll")
return (
<>
<div>
<button onClick={() => setMode("showAll")}>Show all answers</button>
<button onClick={() => setMode("hideAll")}>Hide all answers</button>
</div>
<ModeContext.Provider value={mode}>
<div>
{cases.map( item => <Case key={item.name} {...item}/> ) }
</div>
</ModeContext.Provider>
</>
)
}
export default function Value(props) {
const mode = useContext(ModeContext)
const [hidden, setHidden] = useState(mode === "showAll" ? false : true)
useEffect(() => {
if (mode === "showAll") setHidden(false)
else if (mode === "hideAll") setHidden(true)
}, [mode])
return (
hidden
? <span className="hiddenValue" onClick={() => setHidden(!hidden)}></span>
: <span className="value" onClick={() => setHidden(!hidden)}>{props.children}</span>
)
}
You first need to create your context before you can use it as a provider or user.
So make sure to add this to the top of the file.
const ModeContext = React.createContext('hideAll')
As it stands, since ModeContext isn't created, mode in your Value component should be undefined and never change.
If your components are on separate files, make sure to also export ModeContext and import it in the other component.
Example
Here's one way to organize everything and keep it simple.
// cases.js
const ModeContext = React.createContext('hideAll')
export default function Cases() {
const [mode, setMode] = useState("hideAll")
return (
<>
<div>
<button onClick={() => setMode("showAll")}>Show all answers</button>
<button onClick={() => setMode("hideAll")}>Hide all answers</button>
</div>
<ModeContext.Provider value={mode}>
<div>
{cases.map( item => <Case key={item.name} {...item}/> ) }
</div>
</ModeContext.Provider>
</>
)
}
export function useModeContext() {
return useContext(ModeContext)
}
// value.js
import { useModeContext } from './cases.js'
export default function Value(props) {
const mode = useContext(ModeContext)
const [hidden, setHidden] = useState(mode === "showAll" ? false : true)
useEffect(() => {
if (mode === "showAll") setHidden(false)
else if (mode === "hideAll") setHidden(true)
}, [mode])
return (
hidden
? <span className="hiddenValue" onClick={() => setHidden(!hidden)}></span>
: <span className="value" onClick={() => setHidden(!hidden)}>{props.children}</span>
)
}
P.S. I've made this mistake many times, too.
You shouldn't use a new state in the Value component. Your components should have an [only single of truth][1], in your case is mode. In your context, you should provide also a function to hide the components, you can call setHidden
Change the Value component like the following:
export default function Value(props) {
const { mode, setHidden } = useContext(ModeContext)
if(mode === "showAll") {
return <span className="hiddenValue" onClick={() => setHidden("hideAll")}></span>
} else if(mode === "hideAll") {
return <span className="value" onClick={() => setHidden("showAll")}>{props.children}</span>
} else {
return null;
}
)
}
P.S. Because mode seems a boolean value, you can switch between true and false.
[1]: https://reactjs.org/docs/lifting-state-up.html
There are a few ways to handle this scenario.
Move the state in the parent component. Track all visible states in the parent component like this:
const [visible, setVisibilty] = useState(cases.map(() => true))
...
<button onClick={() => setVisibilty(casses.map(() => false)}>Hide all answers</button>
...
{cases.map((item, index) => <Case key={item.name} visible={visible[index]} {...item}/> ) }
Reset the mode after it reset all states:
const [mode, setMode] = useState("hideAll")
useEffect(() => {
setMode("")
}, [mode])

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.

React returns older state value onClick

I am adding a component onclick and keeping track of the components using useState Array. However when I go to remove one of the added components, it doesn't recognize the full component Array size, only the state that was there when that component was initially added.
Is there a way to have the current state recognized within that delete function?
https://codesandbox.io/s/twilight-water-jxnup
import React, { useState } from "react";
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => deleteSpan(props.index)}>DELETE</button>
Length: {spans.length}
</div>
);
};
//set initial span w/ useState
const [spans, setSpans] = useState([<Span key={0} index={Math.random()} />]);
//add new span
const addSpan = () => {
let key = Math.random();
setSpans([...spans, <Span key={key} index={key} />]);
};
//delete span
const deleteSpan = index => {
console.log(spans);
console.log(spans.length);
};
//clear all spans
const clearInputs = () => {
setSpans([]);
};
return (
<>
{spans}
<button onClick={() => addSpan()}>add</button>
<button onClick={() => clearInputs()}>clear</button>
</>
);
}
UPDATE - Explaining why you are facing the issue descibed on your question
When you are adding your new span on your state, it's like it captures an image of the current values around it, including the value of spans. That is why logging spans on click returns you a different value. It's the value spans had when you added your <Span /> into your state.
This is one of the benefits of Closures. Every <Span /> you added, created a different closure, referencing a different version of the spans variable.
Is there a reason why you are pushing a Component into your state? I would suggest you to keep your state plain and clean. In that way, it's also reusable.
You can, for instance, use useState to create an empty array, where you will push data related to your spans. For the sake of the example, I will just push a timestamp, but for you might be something else.
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => setSpans(spans.filter(span => span !== props.span))}>DELETE</button>
Length: {spans.length}
</div>
);
};
const [spans, setSpans] = React.useState([]);
return (
<>
{spans.length
? spans.map((span, index) => (
<Span key={span} index={index} span={span} />
))
: null}
<button onClick={() => setSpans([
...spans,
new Date().getTime(),
])}>add</button>
<button onClick={() => setSpans([])}>clear</button>
</>
);
}
I hope this helps you find your way.

Categories

Resources