For ag-grid, is it possible to access old rowData? - reactjs

I am creating some custom inline editing functionality and am trying to reset the data when the user hit cancel after making edits to specific columns. I'm using cellRenderers for this so I don't have access to the editing params that a cellEditor would.
Currently, what I am doing is saving the specific row's data in React state originalRowInfo when that row is clicked:
const moreInfoClick = (event, toggleMoreInfo, rowData) => {
event.persist();
setMoreInfoLocation({ x: event.clientX, y: event.clientY });
setRowInfo(rowData);
setOriginalRowInfo({ ...rowData });
toggleMoreInfo(true);
};
I am getting rowData via params.data when I click on a row
The reason I can't just use the rowInfo state is because that gets changed by ag-grid whenever I make an edit to the column. But using {...rowData} seems to fix that.
My only issue now is one of my column values is an array with objects inside and when I edit that column, it gets changed inside originalRowInfo as well. I would imagine this has something to do with object referencing.
The column looks something like this:
[
{position: 1, heat_num: 100},
{position: 2, heat_num: 200}
]
And what actually gets edited is the heat_num
The only thing I can think of is to refetch the data from DB on Cancel but I would rather not have to do that.

You don't need originalRowInfo to reset to the original state if the user cancels the editing, but you may rather do the opposite: save the new value inputValue as temporary state when the user editing and:
If the user submit the result: call ICellRendererParams.setValue(inputValue) to commit the result.
Otherwise, if the user abort: simply discard the result by resetting inputValue to ICellRendererParams.value which is the original value.
function EditableCellRenderer(params: ICellRendererParams) {
const [editable, setEditable] = React.useState(false);
const [originalData] = React.useState(params.value);
const [inputValue, setInputValue] = React.useState(params.value);
const inputRef = React.useRef(null);
const onEdit = () => {
setEditable(true);
setTimeout(() => {
inputRef.current?.focus();
});
};
const onCancel = () => {
setInputValue(originalData);
setEditable(false);
};
const onSubmit = () => {
params.setValue(inputValue);
setEditable(false);
};
return (
<>
<input
ref={inputRef}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
disabled={!editable}
/>
<button onClick={onEdit}>E</button>
<button onClick={onCancel}>X</button>
<button onClick={onSubmit}>S</button>
</>
);
}
Live Demo

Related

Difficulty updating state onChange in a Select tag React

I have a simple app to Generate a Random color. But it only updates on the next change and I can't figure out how to get an updater function to work.
const handleHueChange = (e) => {
const newHue = e.target.value;
setHueColor(newHue);
setColor(randomColor({ hue: hueColor, luminosity: lum }));
console.log(color, hueColor, lum);
};
There is the function and here it the select
<form>
<select
name="hue"
value={hueColor.value}
onChange={(e) => handleHueChange(e)}
>
{hue.map((hue, idx) => (
<option key={idx}>{hue}</option>
))}
</select>
</form>
And here is a link to the code sandbox
https://codesandbox.io/s/brave-austin-ujv5xw?file=/src/App.js:1414-1673
The useeffect is giving the right value but it's only updating on the next render.
You are using react 18 and in this version react will batch the changes, which means that react will apply your multiple setState together so you have to use setColor in a useEffect and remove that from handleChanges, something like this:
useEffect(() => {
setColor(randomColor({ hue: hueColor, luminosity: lum }));
}, [hueColor, lum]);
const handleHueChange = (e) => {
const newHue = e.target.value;
setHueColor(newHue);
};
const handleLumChange = (e) => {
const newLum = e.target.value;
setLum(newLum);
};
Inside handleHueChange you are updating hueColor and in next line you were thinking that you can use that new state immediately so you wrote setColor(randomColor({ hue: hueColor, luminosity: lum }));, but hueColor you passed here is still old one because you are reading it from clousure, meaning only after rerender you will be able to access that new state. Solution is to just replace that line with setColor(randomColor({ hue: newHue, luminosity: lum })); - as you can see here I am using newHue instead of hueColor in order to update colors.

How to uncheck all state selected rows key using onclick function Table - Ant Design

I have function button where this button need to clear all selected rows key, what happen right now, the state is clear already but still checked on the table. I don't know if my function is right or is there need to change inside my function or use the useEffect. I am using Ant Design for the table and React Js for the front end side.
Here is my Button:
<Button type="primary" onClick={unCheckHandler} className="btn-pink">Clear</Button>
Here is my handler function:
const [selectedRowsKeys, setSelectedRowsKeys] = useState([]);
const [selectedRows, setSelectedRows] = useState([]);
const onSelectedRowKeysChange = (selectedRows, selectedRowsKeys) =>{
setSelectedRows(selectedRows)
setSelectedRowsKeys(selectedRowsKeys);
}
const rowSelection = {
selectedRows,
selectedRowsKeys,
onChange: onSelectedRowKeysChange
};
const unCheckHandler = () => {
setSelectedRowsKeys([])
setSelectedRows([])
}
useEffect(() => {
},[unCheckHandler])
console.log(selectedRowsKeys,"---", selectedRows)
Table:
<Table
dataSource={props.dataSource}
columns={props.columns}
size="small"
rowSelection={{
type: "checkbox",
...rowSelection,
}}
pagination={false}
/>
Sample Log:
You need to set the rowSelection.selectedRowKeys property as per the Ant Design documentation. You're only setting selectedRows at the moment which doesn't seem to work.
The example here demonstrates how it works.
const rowSelection = {
selectedRowKeys,
onChange: onSelectedRowKeysChange
};

React - UseEffect not re-rendering with new data?

This is my React Hook:
function Student(props){
const [open, setOpen] = useState(false);
const [tags, setTags] = useState([]);
useEffect(()=>{
let input = document.getElementById(tagBar);
input.addEventListener("keyup", function(event) {
if (event.keyCode === 13) {
event.preventDefault();
document.getElementById(tagButton).click();
}
});
},[tags])
const handleClick = () => {
setOpen(!open);
};
function addTag(){
let input = document.getElementById(tagBar);
let tagList = tags;
tagList.push(input.value);
console.log("tag");
console.log(tags);
console.log("taglist");
console.log(tagList);
setTags(tagList);
}
const tagDisplay = tags.map(t => {
return <p>{t}</p>;
})
return(
<div className="tags">
<div>
{tagDisplay}
</div>
<input type='text' id={tagBar} className="tagBar" placeholder="Add a Tag"/>
<button type="submit" id={tagButton} className="hiddenButton" onClick={addTag}></button>
<div>
);
What I am looking to do is be able to add a tag to these student elements (i have multiple but each are independent of each other) and for the added tag to show up in the tag section of my display. I also need this action to be triggerable by hitting enter on the input field.
For reasons I am not sure of, I have to put the enter binding inside useEffect (probably because the input element has not yet been rendered).
Right now when I hit enter with text in the input field, it properly updates the tags/tagList variable, seen through the console.logs however, even though I set tags to be the re-rendering condition in useEffect (and the fact that it is also 1 of my states), my page is not updating with the added tags
You are correct, the element doesn't exist on first render, which is why useEffect can be handy. As to why its not re-rendering, you are passing in tags as a dependency to check for re-render. The problem is, tags is an array, which means it compares the memory reference not the contents.
var myRay = [];
var anotherRay = myRay;
var isSame = myRay === anotherRay; // TRUE
myRay.push('new value');
var isStillSame = myRay === anotherRay; // TRUE
// setTags(sameTagListWithNewElementPushed)
// React says, no change detected, same memory reference, skip
Since your add tag method is pushing new elements into the same array reference, useEffect thinks its the same array and is not re-triggers. On top of that, React will only re-render when its props change, state changes, or a forced re-render is requested. In your case, you aren't changing state. Try this:
function addTag(){
let input = document.getElementById(tagBar);
let tagList = tags;
// Create a new array reference with the same contents
// plus the new input value added at the end
setTags([...tagList, input.value]);
}
If you don't want to use useEffect I believe you can also use useRef to get access to a node when its created. Or you can put the callback directly on the node itself with onKeyDown or onKeyPress
I can find few mistake in your code. First, you attaching event listeners by yourself which is not preferred in react. From the other side if you really need to add listener to DOM inside useEffect you should also clean after you, without that, another's listeners will be added when component re-rendered.
useEffect( () => {
const handleOnKeyDown = ( e ) => { /* code */ }
const element = document.getElementById("example")
element.addEventListener( "keydown", handleOnKeyDown )
return () => element.removeEventListener( "keydown", handleOnKeyDown ) // cleaning after effect
}, [tags])
Better way of handling events with React is by use Synthetic events and components props.
const handleOnKeyDown = event => {
/* code */
}
return (
<input onKeyDown={ handleOnKeyDown } />
)
Second thing is that each React component should have unique key. Without it, React may have trouble rendering the child list correctly and rendering all of them, which can have a bad performance impact with large lists or list items with many children. Be default this key isn't set when you use map so you should take care about this by yourself.
tags.map( (tag, index) => {
return <p key={index}>{tag}</p>;
})
Third, when you trying to add tag you again querying DOM without using react syntax. Also you updating your current state basing on previous version which can causing problems because setState is asynchronous function and sometimes can not update state immediately.
const addTag = newTag => {
setState( prevState => [ ...prevState, ...newTage ] ) // when you want to update state with previous version you should pass callback which always get correct version of state as parameter
}
I hope this review can help you with understanding React.
function Student(props) {
const [tags, setTags] = useState([]);
const [inputValue, setInputValue] = useState("");
const handleOnKeyDown = (e) => {
if (e.keyCode === 13) {
e.preventDefault();
addTag();
}
};
function addTag() {
setTags((prev) => [...prev, inputValue]);
setInputValue("");
}
return (
<div className="tags">
<div>
{tags.map((tag, index) => (
<p key={index}>{tag}</p>
))}
</div>
<input
type="text"
onKeyDown={handleOnKeyDown}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Add a Tag"
/>
<button type="submit" onClick={addTag}>
ADD
</button>
</div>
);
}

React getting state

I am currently creating my own mini-bbcode editor for my ReactJS application. I use functional components. In the TextEditor.jsx file I have the following code that handles the textarea.
const [bbcodeText, setBbcodeText] = useState("");
const update = (e) => {
setSelection(null);
setBbcodeText(e.target.value);
if (typeof props.onChange == 'function') {
props.onChange(e.target.value);
}
}
<textarea className="form-input forum__body-input" ref={textAreaRef} value={bbcodeText} onChange={update} />
The above works correctly, however I installed [emoji-mart] 1 to have access to the option to place emojis in the textarea, for which I use the following code:
const handleEmoji = (emoji) => {
let newText = bbcodeText + " " + emoji.native;
setBbcodeText(newText);
}
<Picker title="Emojis" emoji="grinning" set='apple' style={{width: '100%'}} onSelect={handleEmoji} />
The textarea updates correctly, the problem is that this TextEditor component accesses it from a parent component of the form:
const [bbcode, setBBcode] = useState();
const update = (v) => {
setBBcode(v);
};
const handlePost = async() => {
console.log(bbcode)
}
<TextEditor onChange={update} onSelect={handleEmoji} />
<button className="button purple" onClick={handlePost}>Agregar respuesta</button>
The problem occurs if I select an emoji and try to access the "bbcode" state, the result is an empty string, but if I write something after it, the emoji is already shown with the text at the end, that is, I need to fire the onChange to display the updated data.
What I need to know is a guide on how to get the updated status, if I only want to insert emojis in the textarea, for example.
In this case bbcode returns undefined if I only use "emojis" but if I write text it works correctly.
I'd suggest moving the onChange callback logic to an useEffect hook with a dependency on the bbcodeText state. This way anything that updates the bbcodeText state value will trigger the effect to invoke the onChange handler to update anything in the parent component.
const [bbcodeText, setBbcodeText] = useState("");
const update = (e) => {
setSelection(null);
setBbcodeText(e.target.value);
}
const handleEmoji = (emoji) => {
setBbcodeText(bbcodeText => bbcodeText + " " + emoji.native);
}
useEffect(() => {
if (typeof props.onChange == 'function') {
props.onChange(bbcodeText);
}
}, [bbcodeText]);

How to lock a row in a react material-table (with remote data) on edit?

Using material-table i try to lock a row on the server
using a remote api call with a value from the edited row, like an id column, as soon as the user clicks the edit action button on that row.
The onRowUpdate() callback is called when the data is to be written, so too late to be useful here.
What can be used to achieve this pre-Edit callback?
Overriding EditRow somehow...?
Not yet perfect but seems the way to go:
add a new state
const [lock, setLock] = useState({});
and
components={{
EditRow: (props) => <MyTableEditRow onEdit={applyLock} offEdit={removeLock} {...props} />,
}}
to the tabel definition and a new Component MyTableEditRow
const MyTableEditRow = (props) => {
const { onEdit, offEdit, ...rest } = props;
useEffect(() => {
console.log('MyTableEditRow useEffect');
if (onEdit && typeof onEdit === 'function') onEdit(props.data);
return () => {
console.log('MyTableEditRow cleanup');
if (offEdit && typeof offEdit === 'function') offEdit(props.data);
};
}, [onEdit, offEdit, props.data]);
return <MTableEditRow {...rest} />;
};
this components is a small wrapper around the built in edit row component and calls the two given function on create and destroy respectively who should get and remove the lock.
with the two callback to do the lock stuff on the rmeote api like: (add some error handling and stuff, getLock() and deleteLock() are basically axios calls to the remote api...)
const applyLock = (rowData) => {
getLock(api, rowData._id).then((lock_data) => {setLock(lock_data);})
}
};
const removeLock = () => {
deleteLock(api, lock).then((r) => { setLock({}); });
}
};
TODO
What is not working right now is onRowDelete(). the cancel button works, but the save just does nothing. Adding a row works, strangely...
EDIT
onRowDelete works, bug in my code.(do not use a non-existing state...)

Resources