Implement Edit feature in a Note Application using react - javascript

I am trying to create a notes application wherein each note object contains a title and content. The user can add, deleted and update a note.
What I was able to achieve so far:
I am able to create a new note , push it into an array and also delete a note from the array . I am finding it a bit hard to edit an existing note.
This is how I want to implement the edit feature:
When the user clicks on the note, the data has to automatically fill into the input box, and the user can modify the data which is then saved into an object and pushed inside an array and then displayed onto the respective note.
When the user clicks on the Edit button, the note id is sent to the App component, the note is searched within the notes array and an object returned to the Create Area component. This object is then displayed on the input field. I'm using UseEffect() hook to display the object data on the input box, but I'm not able to edit the contents on the input box. Here's my code below:
App.jsx:
If the user clicked the edit button, it sets the IsDone state to true in the Edit function. The edit function gets an object from the Notes component
import Header from "./Header";
import CreateArea from "./CreateArea";
import Note from "./Note";
import Footer from "./Footer";
import { useState } from "react";
function App() {
const [noteArray, setArray] = useState([]);
const [isDone,setDone] = useState(false);
const [editNote,setEditNote] = useState({
title:"",
content:""});
function AddOnClick(note) {
setArray((prevNote) => {
return [...prevNote, note];
});
}
function DeleteOnClick(id) {
setArray((prevNote) => {
return prevNote.filter((note, index) => {
return index !== id;
});
});
}
function EditNote(obj)
{
setDone(true);
setEditNote(prevState=>{
return { ...prevState,...obj}});
}
return (
<div>
<Header />
<CreateArea AddOnClick={AddOnClick} noteEdit = {editNote} editFunction = {EditNote}btnClicked = {isDone}/>
{noteArray.map((note, index) => (
<Note
key={index}
id={index}
title={note.title}
content={note.content}
deleteNote={DeleteOnClick}
EditNote = {EditNote}
/>
))}
<Footer />
</div>
);
}
export default App;
Notes.jsx: The id of the note is also included in the object that's passed to App component through the EditNote() function
function Note(props) {
const obj = {title : props.title,
content: props.content,
id:props.id}
return (
<div className="note">
<h1>{props.title}</h1>
<p>{props.content}</p>
<button
onClick={() => {
props.deleteNote(props.id);
}}
>
DELETE
</button>
<button onClick={()=>{props.EditNote(obj)}}>EDIT</button>
</div>
);
}
export default Note;
CreateArea: If the buttonClicked value is true, I'm calling the handleEdit() that takes the object sent from the EditNote() in App.jsx to saves it to to note object using useState() which automatically updates the input and text area field using event.target.value with the help of useEffect().
import { useState } from "react";
function CreateArea(props) {
const [note, setNote] = useState({
title: "",
content: ""
});
function handleChange(event) {
console.log(event.target);
const { name, value } = event.target;
setNote((prevNote) => {
return { ...prevNote, [name]: value };
});
}
function addNote(event) {
setNote({ title: "", content: "" });
props.AddOnClick(note,note.id);
event.preventDefault();
}
function handleEdit()
{
setNote(prevValue=>{
return {...prevValue,...props.noteEdit}
})
}
useEffect (()=>{
if(props.btnClicked){handleEdit();
}
});
return (
<div>
<form>
<input name="title" id="title" value={note.title}onChange={handleChange}placeholder="Title"/>
<textarea name="content" id="content" value={note.content}onChange={handleChange} placeholder="Take a note..." rows="3"/>
<button onClick={addNote}>Add</button>
</form>
</div>
);
}
export default CreateArea;
The code runs well but now I can't add any more text on the input box,it just blocks me from doing it.I tried calling HandleChange() inside UseEffect(), that throws an error saying: Cannot read properties of target:undefined at HandleChange() I really need help how to implement edit.
I tried directly populating the input box and the text area field using document.getElementById.value = myValue even that does not seem to work

Related

Display a message in semantic ui only after a button is clicked

In Semantic UI, a message can be visible
<Message visible>You can always see me</Message>
or hidden.
<Message hidden>You can't see me</Message>
I have a button that when clicked gets an API response, that then fills a Message with text. I only want to see the Message after clicking the button, because until then it is just an empty box at the bottom of the screen. How can I implement this in my function?
import React, { useState } from 'react';
import { Button, Header, Message} from 'semantic-ui-react'
export default function Writer() {
const [titleText, setTitleText] = useState('');
const getResponse = () => {
//Does Some Stuff
}).then((response) => {
setTitleText(response.data.choices[0].text)
})
return (
<div>
<Button onClick={getResponse}></Button>
<Message>
<Header
icon textAlign='center'
content={titleText}
/>
</Message>
</div>
)
}
I tried to set a variable
var visibility = 'hidden'
and set visibility = 'visible' inside the function, then writing <Message {visibility}? but it did not work. Any recommendations please?
In Semantic-UI-React the visible & hidden are boolean flags. So you need to pass in a Boolean value. & because you need it to be done based on titleText you can simply use the length property.
import React, { useState } from 'react';
import { Button, Header, Message} from 'semantic-ui-react'
export default function Writer() {
const [titleText, setTitleText] = useState('');
const getResponse = () => {
//Does Some Stuff
}).then((response) => {
setTitleText(response.data.choices[0].text)
})
return (
<div>
<Button onClick={getResponse}></Button>
<Message visible={titleText.length > 0} hidden={titleText.length === 0}>
<Header
icon textAlign='center'
content={titleText}
/>
</Message>
</div>
)
}

Issues rendering the right dashboard UI user based on roles

I am trying to render UI in my project based on selected roles (brands, agency, influencer) on click. However, the logic that I am putting together is not loading the right UI and I don't quiet understand why not.
I have tried moving the role and setRole to the top component and passed the props down to the child components that read role and updated it via setRole so that I can have the state to be available in two places.
I also set a logic that should display components based on if the role equals the value of the buttons.
What happened was the components weren't loading upon clicking the function that handles click. However, logging out to the console if the role equals the value of the clicked button returns true, the right string that the logic was based on.
What I am expecting to happen is to load the component e.g: "Brands" when users click and select "brands" which is the value of the clicked button. Vise versa for the other components.
My code is as follows:
import { useState } from 'react';
import { useSession } from 'next-auth/react';
import Brands from './Brands';
import Agency from './Agency';
import CreatorsDash from './CreatorsDashboard';
export default function FirstPageModal({ role: userRole }) {
const [role, setRole] = useState(userRole);
const { data: session } = useSession();
const handleClick = (e) => {
e.preventDefault();
let buttonValue = e.target.value;
const clickedRole = role?.map((user) => {
let { role } = user;
if (buttonValue) {
userRole = { role: buttonValue };
}
return { userRole };
});
console.log(clickedRole); //Returns an array
clickedRole.map((item) => {
const { role } = item.userRole;
console.log(role); //Returns string ("agency" / "brands" / "Influencer")
if (session && role === 'brands') {
console.log(role); //This logs "brands" as expected but doesn't return the component
// return <Brands session={session} role={role} />;
} else if (session && role === 'agency') {
return <Agency session={session} role={role} />;
} else if (session && role === 'Influencer') {
return <CreatorsDash session={session} role={role} />;
} else {
console.log('Select a role');
}
});
};
return (
<>
<div className="">
<button type="button" className="" onClick={handleClick} value="agency">
As an Agency
</button>
<button type="button" className="" onClick={handleClick} value="brands">
As a Brand
</button>
<button
type="button"
className=""
onClick={handleClick}
value="Influencer"
>
As an Influencer
</button>
</div>
</>
);
}
Returning a component from an onClick handler doesn't automatically render the component. One thing you could do is to keep track of the role in the state and then put the <Brands /> <Agency/> and <CreatorsDash /> components in the render function and dynamically show/hide them like {role === "brands" && <Brands />. This can also be done with css, although the benefits of this are not so clear,.
Side note, it is very helpful to post a codepen with your code, especially as your code gets more complicated

How can i add another component by clicking a button on ReactJS?

here's my problem.
I have a premade component, which is a "line" of content, which contains some buttons and some input boxes.
Now, i want to add more of this exact component, whenever i press a button "add row", but i have no clue on how i can do this.
I know basic concepts like States, map ... but i don't know ho to put it on code.
Can someone explain me how can i do it ?
PS I'm really new to React, and in general to JS world
Here's the code
App.js
import React, { useState } from "react";
import "./App.css";
import OpLine from "./Components/OpLine";
function App() {
const addRowHandler = () => {
};
return (
<div>
<div className="bg">
<button onClick={addRowHandler}> Add Row </button>
<OpLine></OpLine>
<div className="result">Result is : (resVar) </div>
</div>
</div>
);
}
export default App;
OpLine.js
import React from "react";
import Card from "./Card/Card";
import './OpLine.css';
import OpForm from "./OpForm/OpForm";
const OpLine = () => {
return(
<Card className="line">
<OpForm></OpForm>
</Card>
);
};
export default OpLine;
OpForm.js
import React from "react";
import "./OpForm.css";
const OpForm = () => {
return (
<div className="op-form">
{/*div handling op button*/}
<div className="operando">
<label>Operando</label>
<select>
<option>+</option>
<option>-</option>
</select>
</div>
{/*div handling number input button */}
<div className="number-input">
<label>Insert number</label>
<input type="number"></input>
</div>
<button>Delete</button>
<button>Disable</button>
</div>
);
};
export default OpForm;
Not sure if this fits your needs, but you can create a counter inside the state and iterate this in order to render the components.
Button will increment the counter
const [counter, setCounter] = useState(1);
const addRowHandler = () => {
setCounter(counter + 1);
};
and in render method
{Array.from({length: counter}).map(() => <OpForm></OpForm>)}
sandbox
Instead of thinking about it as "adding a component" think of it as "updating state". The state then informs the main component how to render it.
Here's a small example where I'm using name, age, and role as property names. After you've added a couple of rows, and filled in some information, click the "Show state" to see the result.
const { useState } = React;
function Example() {
// Initialise the state as an empty array
const [ state, setState ] = useState([]);
// When the `addRow` button is clicked, add a new
// empty object to the state
function addRow(e) {
const object = { name: '', age: '', role: '' };
setState([...state, object]);
}
// We pass this handler down to the Row component
function handleChange(e) {
// Check to see if the element that's changed
// is an input
if (e.target.matches('input')) {
// Get the id from the div dataset
const { id } = e.currentTarget.dataset;
// Get the name and value from the changed element
const { name, value } = e.target;
// Copy the state
const copy = [...state];
// Create a new object using the object in state
// at index === id, and update the correct property
const obj = { ...state[id], [name]: value };
// Add that object to the state copy
copy[id] = obj;
// Finally update the state
setState(copy);
}
}
// We now create some rows by mapping
// over the data and returning an array
// of components which have an id based
// on their position in the state array, some
// data contained in the object, and the handler
function createRows(state) {
return state.map((obj, i) => {
return (
<Row
id={i}
data={obj}
handleChange={handleChange}
/>
);
});
}
// Small function to show state when a
// button is clicked
function showState() {
console.log(JSON.stringify(state));
}
// Check to see if state has length, and then
// create the rows
return (
<div>
<button onClick={addRow}>Add a row</button>
<button onClick={showState}>Show state</button>
{state.length ? createRows(state) : <div />}
</div>
);
}
// Row accepts and id, some data, and the handler
function Row({ id, data, handleChange }) {
// Destructure the information from `data`
const { name, age, role } = data;
// Build the Row JSX. Note that we add the
// id as a data attribute on the div element
return (
<div
data-id={id}
className="row"
onChange={handleChange}
>
<label>Name
<input name="name" value={name} />
</label>
<label>Age
<input name="age" value={age} />
</label>
<label>Role
<input name="role" value={role} />
</label>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
input { margin-left: 0.5em; display: inline-block;}
.row { background-color: #efefef; padding: 0.3em; margin-top: 1em; }
button { margin-right: 0.5em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Is it possible to send the state to the page destination at the time of browser back with react-router-dom?

I'm using react.js and react-router-dom to create two pages. Form.js is the page where you enter your name in the form, and Confirmation.js is the page where you confirm the name.
I want to share the state of two classes. So, when you jump to another page from the link button, you will send the state at the same time. The sent state is received in the class constructor as this.state = props.history.location.state.
Many have omitted this code.
//Form.js
import React, { Component } from 'react'
import {Link} from 'react-router-dom'
class Form extends Component {
constructor(props) {
super(props);
const histState = props.history.location.state
this.state = histState == undefined ? {name: this.state} : histState
}
render() {
return (
<div>
<input type="text" onChange={this.handleFormInputChanged} value={this.state.name}/>
<Link to={pathname: "/confirmation" , state: this.state}>Send</Link>
</div>
)
}
}
//Confirmation.js
class Confirmation extends Component {
constructor(props) {
super(props);
this.state = props.history.location.state
}
render() {
return (
<div>
<div>Your Name : <span className="name">{this.state.name}</span></div>
<Link to={pathname: "/form" , state: this.state}>Edit</Link>
</div>
)
}
}
Now I can do what I want to do. However, I noticed that when the user pressed the browser back button on the Confirmation.js page, the state was not sent because it jumped to the Form.js page without pressing the Link component.
As a solution, I added the following code to Confirmation.js.
//Confirmation.js
componentWillUnmount() {
this.props.history.push("/form", this.state)
}
However, when I do a browser back this way and receive a state in the class constructor, props.history.location.state is undefined. And strangely, after a while or reloading, props.history.location.state is set to state normally.
//Form.js
constructor(props) {
...
console.log("Form constructor", props.history.location.state)
}
I want to resolve the time it takes for state to be set as the value of props.history.location.state, is there a solution?
You can pass basic parameters as route segments, like /form/:username, or you could use a query parameter like /form?username=Hiroaki, but passing around data more structured or complex via the url or location history seems inadvisable.
I think you'd save yourself a lot of pain by using context or setting up a simple orthogonal store to keep track of it as the user navigates.
Here's a sketch of how you might do it with context. Assuming the provider is above the router in the component hierarchy, the form state will persist through navigation (though not through page reloads). (I haven't tested any of this code. This is just to give you a sense of it.)
const [formState, setFormState] = useState({});
<FormStateContext.Provider value={formState}>
<Form onChange={setFormState} />
</FormStateContext.Provider>
const Form = ({ onChange }) => {
const formState = useContext(FormStateContext);
return (
<input name="username"
value={formState.username}
onChange={(e) => setFormState({ ...formState, username: e.target.value })}
/>
);
}
const Confirmation = () => {
const formState = useContext(FormStateContext);
return (
<div>Your Name: {formState.username}</div>
);
}
If your components aren't that big, you could do something like this instead of using a different route :
import React from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const [state, setState] = React.useState({isConfirmationMode: false});
const handleChange = e => setState({...state, [e.target.name]: e.target.value});
const confirm = () => {
console.log('confirmed');
// Here send you data or whatever you want
// then redirect wherever you want, I just display the form again
setState({...state, isConfirmationMode: false});
}
const cancel = () => {
// juste display the form again
setState({...state, isConfirmationMode: false});
}
const displayForm = () => (
<div>
name : <input type="text" name="name" value={state.name} onChange={handleChange} />
<button onClick={() => setState({...state, isConfirmationMode: true})}>Send</button>
</div>
);
return state.isConfirmationMode ?
<Confirmation name={state.name} confirm={confirm} cancel={cancel} /> :
displayForm()
};
// Here I created 'confirm' and 'cancel', but you might only need 'cancel'
const Confirmation = ({name, confirm, cancel}) => {
return (
<div>
Are you {name} ?<br />
<button onClick={confirm}>Confirm</button>
<button onClick={cancel}>Cancel</button>
</div>
);
}
render(<App />, document.getElementById("root"));
Here is the repro on Stackblitz. The idea is just to either display the form or a confirmation depending on the state of a simple boolean (I separated the confirmation in another component but here it could be part of the first one).

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