Deleting Note From Array - reactjs

Current I am working on a note app using React.Js and I had the functionality for saving notes but then I decided to add the functionality to remove the note when it is clicked. I wrote some code however, now it won't work. I can't add notes display them. Usually when you click on the button is was suppose to add the note which it was doing before I tried adding the deleting the note functionality. There is also no error in anywhere. I have been trying to solve this the past few days. Here is my code:

The problem is that the id is undefined in your NoteItem component. You are only passing the title to the component. You should pass the note object and then you can have access to the id and title. Suggested changes below:
NotesList
const NotesList = (props) => {
return props.data.map((note) => {
return (
<NoteItem
note={note}
key={note.id}
onDelete={props.onDeleteItem}
>
</NoteItem>
);
})
}
and change NoteItem component to access note as an object. (You could also pass title/id individually as props as well.
NoteItem:
const NoteItem = props => {
const deleteHandler = () => {
props.onDelete(props.note.id);
}
return (
<h3 onClick={deleteHandler}>{props.note.title}: {props.note.id}</h3>
)
}

Related

Make a momentary highlight inside a virtualized list when render is not triggered

I have an extensive list of items in an application, so it is rendered using a virtual list provided by react-virtuoso. The content of the list itself changes based on API calls made by a separate component. What I am trying to achieve is whenever a new item is added to the list, the list automatically scrolls to that item and then highlights it for a second.
What I managed to come up with is to have the other component place the id of the newly created item inside a context that the virtual list has access to. So the virtual list looks something like this:
function MyList(props) {
const { collection } = props;
const { getLastId } useApiResultsContext();
cosnt highlightIndex = useRef();
const listRef = useRef(null);
const turnHighlightOff = useCallback(() => {
highlighIndex.current = undefined;
}, []);
useEffect(() => {
const id = getLastId();
// calling this function also resets the lastId inside the context,
// so next time it is called it will return undefined
// unless another item was entered
if (!id) return;
const index = collection.findIndex((item) => item.id === if);
if (index < 0) return;
listRef.current?.scrollToIndex({ index, align: 'start' });
highlightIndex.current = index;
}, [collection, getLastId]);
return (
<Virtuoso
ref={listRef}
data={collection}
itemContent={(index, item) => (
<ItemRow
content={item}
toHighlight={highlighIndex.current}
checkHighlight={turnHighlightOff}
/>
)}
/>
);
}
I'm using useRef instead of useState here because using a state breaks the whole thing - I guess because Virtuouso doesn't actually re-renders when it scrolls. With useRef everything actually works well. Inside ItemRow the highlight is managed like this:
function ItemRow(props) {
const { content, toHighlight, checkHighligh } = props;
const highlightMe = toHighlight;
useEffect(() => {
toHighlight && checkHighlight && checkHighligh();
});
return (
<div className={highlightMe ? 'highligh' : undefined}>
// ... The rest of the render
</div>
);
}
In CSS I defined for the highligh class a 1sec animation with a change in background-color.
Everything so far works exactly as I want it to, except for one issue that I couldn't figure out how to solve: if the list scrolls to a row that was out of frame, the highlight works well because that row gets rendered. However, if the row is already in-frame, react-virtuoso does not need to render it, and so, because I'm using a ref instead of a state, the highlight never gets called into action. As I mentioned above, using useState broke the entire thing so I ended up using useRef, but I don't know how to force a re-render of the needed row when already in view.
I kinda solved this issue. My solution is not the best, and in some rare cases doesn't highlight the row as I want, but it's the best I could come up with unless someone here has a better idea.
The core of the solution is in changing the idea behind the getLastId that is exposed by the context. Before it used to reset the id back to undefined as soon as it is drawn by the component in useEffect. Now, instead, the context exposes two functions - one function to get the id and another to reset it. Basically, it throws the responsibility of resetting it to the component. Behind the scenes, getLastId and resetLastId manipulate a ref object, not a state in order to prevent unnecessary renders. So, now, MyList component looks like this:
function MyList(props) {
const { collection } = props;
const { getLastId, resetLastId } useApiResultsContext();
cosnt highlightIndex = useRef();
const listRef = useRef(null);
const turnHighlightOff = useCallback(() => {
highlighIndex.current = undefined;
}, []);
useEffect(() => {
const id = getLastId();
resetLastId();
if (!id) return;
const index = collection.findIndex((item) => item.id === if);
if (index < 0) return;
listRef.current?.scrollToIndex({ index, align: 'start' });
highlightIndex.current = index;
}, [collection, getLastId]);
return (
<Virtuoso
ref={listRef}
data={collection}
itemContent={(index, item) => (
<ItemRow
content={item}
toHighlight={highlighIndex.current === index || getLastId() === item.id}
checkHighlight={turnHighlightOff}
/>
)}
/>
);
}
Now, setting the highlightIndex inside useEffect takes care of items outside the viewport, and feeding the getLastId call into the properties of each ItemRow takes care of those already in view.

React UseRef is undefined using a wrapper

I'm following this example in order to achive dynamically-created elements that can be printed using react To Print.
I have the following code (showing sections to keep this question as clean as possible):
/*This section is loaded after a user has been selected from a select input*/
{rows?.map((row,index) => (
<PrintHojaVacaciones key={index} vacacion={row}/>
))}
const PrintHojaVacaciones = ({vacacion}) => {
const componentRef = useRef();
return (
<div>
{vacacion.id}
<ReactToPrint
trigger={() => {
<SqIconButton tip={`Imprimir (Aprobada por ${vacacion.aprobadapor})`}
color={"success"}
disableElevation={true}>
<><BsIcons.BsHandThumbsUpFill/><AiIcons.AiFillPrinter/></>
</SqIconButton>
}}
content={() => componentRef.current}
/>
<Diseno_PrintHojaVacaciones ref={componentRef} value={vacacion}/>
</div>
)
}
export default PrintHojaVacaciones
const Diseno_PrintHojaVacaciones = React.forwardRef((props,ref) => {
const { value } = props;
return (
<div ref={ref}>{value.aprobadapor}</div>
);
});
export default Diseno_PrintHojaVacaciones;
Thing is, useRef is undefined. I have been trying with CreateRef as well, with the same result. I also tried to "move" my code to the codesandbox above displayed and it works well, but in my own application, it returns undefined. The whole useRef is new to me and I don't understand it well yet, so any help would be appreciated.
The route is being called using Lazy loading from react router (I don't know if this could be the culprit)
I don't know exactly what I did to make it work, I really have no idea, but my Trigger now looks like this and it is now working (not sure if I'm missing something else). The difference is that the function is not inside brackets after the =>.
trigger={() =>
<SqIconButton tip={`Imprimir (Aprobada por ${vacacion.aprobadapor})`}
color={"success"}
disableElevation={true}>
<><BsIcons.BsHandThumbsUpFill/><AiIcons.AiFillPrinter/></>
</SqIconButton>
}

How should I update individual items' className onClick in a list in a React functional component?

I'm new to React and I'm stuck trying to get this onClick function to work properly.
I have a component "Row" that contains a dynamic list of divs that it gets from a function and returns them:
export function Row({parentState, setParentState}) {
let divList = getDivList(parentState, setParentState);
return (
<div>
{divList}
</div>
)
}
Say parentState could just be:
[["Name", "info"],
["Name2", "info2"]]
The function returns a list of divs, each with their own className determined based on data in the parentState. Each one needs to be able to update its own info in parentState with an onClick function, which must in turn update the className so that the appearance of the div can change. My code so far seems to update the parentState properly (React Devtools shows the changes, at least when I navigate away from the component and then navigate back, for some reason), but won't update the className until a later event. Right now it looks like this:
export function getDivList(parentState, setParentState) {
//parentState is an array of two-element arrays
const divList = parentState.map((ele, i) => {
let divClass = "class" + ele[1];
return (
<div
key={ele, i}
className={divClass}
onClick={() => {
let newParentState =
JSON.parse(JSON.stringify(parentState);
newParentState[i][1] = "newInfo";
setParentState(newParentState);}}>
{ele[0]}
</div>
)
}
return divList;
}
I have tried to use useEffect, probably wrong, but no luck. How should I do this?
Since your Row component has parentState as a prop, I assume it is a direct child of this parent component that contains parentState. You are trying to access getDivList in Row component without passing it as a prop, it won't work if you write your code this way.
You could use the children prop provided by React that allow you to write a component with an opening and closing tag: <Component>...</Component>. Everything inside will be in the children. For your code it would looks like this :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const [parentState, setParentState] = React.useState([
['I am a div', 'bg-red'],
['I am another div', 'bg-red'],
]);
React.useEffect(
() => console.log('render on ParentState changes'),
[parentState]
);
const getDivList = () => {
return parentState.map((ele, i) => {
return (
<div
key={(ele, i)}
className={ele[1]}
onClick={() => {
// Copy of your state with the spread operator (...)
let newParentState = [...parentState];
// We don't know the new value here, I just invented it for the example
newParentState[i][1] = [newParentState[i][1], 'bg-blue'];
setParentState(newParentState);
}}
>
{ele[0]}
</div>
);
});
};
return <Row>{getDivList()}</Row>;
};
const Row = ({ children }) => {
return <>{children}</>;
};
render(<App />, document.getElementById('root'));
And a bit of css for the example :
.bg-red {
background-color: darkred;
color: white;
}
.bg-blue {
background-color:aliceblue;
}
Here is a repro on StackBlitz so you can play with it.
I assumed the shape of the parentState, yu will have to adapt by your needs but it should be something like that.
Now, if your data needs to be shared across multiple components, I highly recommand using a context. Here is my answer to another post where you'll find a simple example on how to implement a context Api.

Odd behaviour from custom react hook

https://jsfiddle.net/dqx7kb91/1/
In this fiddle, I extracted a part of a project (I tried to simplify it as much as I could without breaking anything), used to manage search filters.
We have many different types of filters defined as an object like this:
filter = {
name: "",
initVal: 1, //default value for the filter,
controlChip: ... // filter's chip component
...
}
the chip component are used to list all filters activated and to edit already activated filters (in this case remove the filter from the list of activated filters).
The filters are contained in an object handled by an unique container containing a custom hook.
The problem is, let's say I set the 1st filter, then set the second and I decide to finally remove the first filter, both filters are removed. Everything is working fine apart from this.
What might be causing this ? It seems to me that in the custom hook useMap that I use, when I try to remove the filter, it doesn't take account of the actual current state but uses the state it was when I added the first filter (an empty object), so it tries to remove something from nothing and set the state to the result, an empty object.
How can I fix this ? Thank you
What's happening is when you set your first filter (let's say #1) you're setting map that contains just filter 1. When you set filter #2 map contains filters #1 & #2. BUT... and here's the thing... your remove callback for filter #1 has map only containing #1, not #2. That's because your callback was set before you set filter #2. This would normally be solved because you're using hooks like useCallback, but the way you're implementing them (createContainer(useFilters)), you're creating separate hooks for each filter object.
I would simplify this into only one component and once it is working start extracting pieces one by one if you really need to.
I know this is a complete rewrite from what you had, but this works:
import React from "react";
import ReactDOM from "react-dom";
const App = () => {
const [map, setMap] = React.useState({});
// const get = React.useCallback(key => map[key], [map])
const set = (key, entry) => {
setMap({ ...map, [key]: entry });
};
const remove = key => {
const {[key]: omit, ...rest} = map;
setMap(rest);
};
// const reset = () => setMap({});
const filters = [
{ name: 'filter1', value: 1 },
{ name: 'filter2', value: 2 },
];
return (
<>
{Object.keys(map).map(key => (
<button onClick={() => remove(key)}>
REMOVE {key}
</button>
))}
<hr />
{filters.map(({ name, value }) => (
<button onClick={() => set(name, { value })}>
SET {name}
</button>
))}
</>
)
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Calling onClick outside of a component acting globally

so i have this definition here:
const generateList = props => {
...
item = <ListItemEl
icon='directions'
text={'Directions'}
onClick={props.openGoogleMaps(coords)}
/>
...
}
const SideList = props => {
const listItems = generateList(props)
return (
<List>
{listItems}
</List>
)
}
however the onClick action is not bound to that specific ListItemEl.
The onClick is called way up the component tree that this ListItemEl belongs to.
openGoogleMaps is successfully called but upon clicking anything not just that ListItemEl.
here is the parent component when openGoogleMaps is passed.
class index extends Component {
openGoogleMaps(coords) {
const center = PolygonCenter(coords)
window.open(
"https://www.google.com/maps/dir/?api=1&destination=" +
center.coordinates[0] +
"," +
center.coordinates[1]
)
}
...
{context.state.drawerOpen ? <SideList data={this.props.data} openGoogleMaps={this.openGoogleMaps}/> : null}
}
i'm guessing this has something to do with binding openGoogleMaps to that specific ListItemEl but when i try to do that with 'this.props.openGoogleMaps(coords).bind(this)'
or something similar the onClick no longer works anywhere.
also im guessing this has to do with a gap in my knowledge concerning defining 'const' vs a component. previously when i've used bind i've used 'this' in front of the function im binding but since the ListItemEl is defined in a const i can't use
'this'. please and thank you
also here is the branch, repo and file where the problem is occuring https://github.com/CodeForCary/cary-connects-web/blob/googlemaps/src/components/Drawer/SideList.js

Resources