Implementing undo/redo function in react - reactjs

I am trying to implement simple undo/redo function in my react app. So I am trying to maintain an array with old state values. But when I check the values of the old states, its all updated with the new state.
state :
state = {
schedule : [],
loads:[],
undo:[],
redo:[]
};
const redoUndoObj ={
oldStateSchedule : [...this.state.schedule],
oldStateLoads : [...this.state.loads]
}
this.setState({ undo : [ ...this.state.undo , redoUndoObj]});

I hope this give you an idea on how to solve the problem. I made code only for undo now to point you in the right direction. This example I made via React functional component using useState instead of Component class.
const [schedule, setSchedule] = useState([]);
const [loads, setLoads] = useState([]);
const [undo, setUndo] = useState([]);
const [redo, setRedo] = useState([]);
const updateData = (newSchedule, newLoads) => {
setSchedule([...newSchedule]);
setLoads([...newLoads]);
const newUndo = {
schedule: [...newSchedule],
loads: [...newLoads],
};
setUndo([...undo, ...newUndo]);
}
const undoChanges = () => {
const lastElement = undo[undo.length - 1];
const copyOfUndo = [...undo];
// Update redo to be able to rollback
setRedo([...undo]);
// Set the previous values to Schedule and Loads
schedule([...lastElement.schedule]);
loads([...lastElement.loads]);
// Remove the last element from undo
lastElement.pop();
undo([...lastElement]);
}

Related

React Js - On change all select are changing

I'm trying to create a list of select with one option but when changing any one they all changed, i want to handle them one by one. Im using map in order to get the data and display it using useState.
Here is the code below :
const positions = patients.data.map((element) => {
return element.first_name + " " + element.last_name;
});
const [Position, setPosition] = useState(positions[0]);
const [serviceList, setServiceList] = useState([{service:""}]);
const handleServiceAdd = () =>{
setServiceList([...serviceList,{service:""}])
}
const handleServiceRemove = (index) => {
const list = [...serviceList];
list.splice(index,1);
setServiceList(list);
}
If you are using styled components, place them before writing the function that renders your component.
For example, your component name is "Component", your code shouldn't look like:
const Component => {
const YourStyledComponent = styled.div``
return(...)
}
But it should look like:
const YourStyledComponent = styled.div``
const Component => {
return(...)
}
Why ? Because everytime you will change one state, everything inside your component will rerender

Execute Function when a State Variable Changes inside of a useEffect() Hook

so I am trying to create a graph visualization front-end using Antv's G6 and React. I have this useState() variable and function as shown below:
const [hideNode, sethideNode] = useState("");
const hideN = () => {
const node = graph.findById(hideNode);
node.hide();
};
The function is in charge of hiding the selected node. However, the problem with running this function as it is, is that it will raise the error TypeError: Cannot read properties of null (reading 'findById') because graph is assigned inside of the useEffect() hook, as shown below:
useEffect(() => {
if (!graph) {
graph = new G6.Graph();
graph.data(data);
graph.render();
hideN();
}
}, []);
It only works as intended if I call the function hideN() inside of the useEffect() hook, otherwise outside of the useEffect() if I console.log(graph) the result would be undefined.
So I wanted to ask, is there a way I could have this function run when the state changes while inside of the useEffect(), or is there a better way to go about this. I'm sorry I am super new to React so still learning the best way to go about doing something. I'd appreciate any help you guys can provide.
Full code:
import G6 from "#antv/g6";
import React, { useEffect, useState, useRef } from "react";
import { data } from "./Data";
import { NodeContextMenu } from "./NodeContextMenu";
const maxWidth = 1300;
const maxHeight = 600;
export default function G1() {
let graph = null;
const ref = useRef(null);
//Hide Node State
const [hideNode, sethideNode] = useState("");
const hideN = () => {
const node = graph.findById(hideNode);
node.hide();
};
useEffect(() => {
if (!graph) {
graph = new G6.Graph(cfg);
graph.data(data);
graph.render();
hideN();
}
}, []);
return (
<div>
<div ref={ref}>
{showNodeContextMenu && (
<NodeContextMenu
x={nodeContextMenuX}
y={nodeContextMenuY}
node={nodeInfo}
setShowNodeContextMenu={setShowNodeContextMenu}
sethideNode={sethideNode}
/>
)}
</div>
</div>
);
}
export { G1 };
Store graph in a React ref so it persists through rerenders. In hideN use an Optional Chaining operator on graphRef.current to call the findById function.
Add hideNode state as a dependency to the useEffect hook and move the hideN call out of the conditional block that is only instantiating a graph value to store in the ref.
const graphRef = useRef(null);
const ref = useRef(null);
//Hide Node State
const [hideNode, sethideNode] = useState("");
const hideN = () => {
const node = graphRef.current?.findById(hideNode);
node.hide();
};
useEffect(() => {
if (!graphRef.current) {
graphRef.current = new G6.Graph(cfg);
graphRef.current.data(data);
graphRef.current.render();
}
hideN();
}, [hideNode]);

Redux useDispatch Hook on click event of nodeListOf<HTMLElement>

I have a component that renders a grid. I'm trying to count the moves made (onclick of each grid box).
But when I include dispatch on the eventListener it returns an error. The moveCharacter function is supposed to move the character around those boxes and its working well. I just need a way to be able to count the moves made (onclick of each box) and store in general state to use in another component.
function GridBoxes():JSX.Element{
const gridValue: number = useSelector<IStateProps, IStateProps["grid"]>((state)=> state.grid);
const totalMoves: number = useSelector<IStateProps, IStateProps["totalMoves"]>((state)=> state.totalMoves);
const history = useHistory();
const dispatch = useDispatch();
useEffect(()=>{
const boxElements = document.querySelectorAll('.box');
boxElements.forEach(element => {
element.addEventListener('click', (e)=>{
moveCharacter(element.id, getCharacterPosition(boxElements));
dispatch({type: "COUNT_MOVES", totalMoves: totalMoves + 1});
console.log("moves");
});
});
});
useEffect(()=> {
let t = setInterval(()=> {
const timeSpent = document.getElementById("time-spent");
const indicator = document.getElementById("indicator");
let countDown = Number(timeSpent?.innerHTML);
countDown = countDown - 1;
let timeTakenPercent = ((gridValue*3) - countDown) / (gridValue*3) * 100;
dispatch({type:"SET_TIME", payload: (gridValue*3)-countDown});
(indicator as any).style.width = timeTakenPercent+"%";
(timeSpent as any).innerHTML = countDown.toString().length < 2 ? countDown.toString().padStart(2,"0") : countDown;
if(countDown < 1){
clearInterval(t);
history.push("/over");
play("https://freesound.org/data/previews/175/175409_1326576-lq.mp3");
}
}, 1000);
});
const [emptyBox, characterBox, foodBox]: string[] = ['<div class="box"></div>',`<div class="box"><img src=${assets.character} /></div>`, `<div class="box"><img src=${assets.food} /></div>`];
const generatedGrids: string[][] = gridPattern({grid: gridValue, box:emptyBox, character: characterBox, food: foodBox});
return (
<>
{generatedGrids.map((box, i)=> {
box = setElementId(box,i);
return <div key={i} className="col">{ReactHtmlParser(box.join(" "))}</div>;
})}
</>
);
}
export default GridBoxes;
Error gotten
The error does not really come from Redux, but from some manual DOM manipulation you are doing.
Your dispatch call only surfaces it: when calling dispatch, redux state will change which will trigger a React rerender - while you are manually fiddling around with the DOM and the two things collide.
My question is: why do you do all this? The whole point of React is that is builds the DOM for you and attaches event handlers for you. At no point should you be using something like react-html-parser, manually concatenate html strings, manually call addEventListener, modify innerHTML, style or anything else on DOM elements.
Let React build your DOM for you and this error will go away.

React-native infinite re-renders

I am build a replica of the 2048 game.
I am working on the animations right now and I seem to get infinite re-renders for some reason when updating the state with any kind of array.
It seems that passing anything but an array to 'setAppearAnimations' works fine.
Thanks in advance :)
const GameProvider = (props) => {
const [appearAnimations, setAppearAnimations] = useState([])
const addAppearAnimation = (animation) => {
const newAppearAnimations = appearAnimations.concat([animation])
console.log(newAppearAnimations)
setAppearAnimations(newAppearAnimations)
}
const Piece = ({ piece }) => {
const { value, prevX, prevY, x, y, hasJustAppeared } = piece
let appearAnimation = 1
useEffect(() => {
//Add appear animation
if (hasJustAppeared) {
appearAnimation = new Animated.Value(0)
addAppearAnimation(appearAnimation)
}
}, [])
I changed the way the animations are being called so now i declare it inside A Piece component and start it inside a 'useEffect' inside it.

React Hooks reversing syntax of useState - pretty strange

So I've used React Hooks somewhat heavily in the past few months on one particular project - this is the first time I've seen anything like this and was wondering if anybody had an explanation as to what is happening.
I have the following:
const [ setSectionDefects, sectionDefects ] = useState([]);
const { propertyDefects, propertySections } = props;
useEffect(()=> {
const defectsBySection = [];
propertySections.map(propertySection => {
const sectionId = propertySection.SectionId;
const defectArray = [];
const sectionObject = { id: sectionId, defects: defectArray };
propertyDefects.map(propertyDefect => {
if (propertyDefect.WosectionId == sectionId) {
defectArray.push(propertyDefect);
}
})
defectsBySection.push(sectionObject);
})
// setSectionDefects(defectsBySection);
// sectionDefects(defectsBySection);
}, []);
console.log(setSectionDefects, sectionDefects)
When the code reaches the console.log statement, it says that 'setSectionDefects' is an array and 'sectionDefects' is the function to set it!
My mind is blown, I can't figure it out for the life of me - the syntax, as I've learned it, is the function declaration first, and then the variable to be set -
ie: const [ setSectionDefects, sectionDefects ] = useState([]);
Has anyone else ran into this?
the first item in useState is the state itself and and second item is function to update it.
https://reactjs.org/docs/hooks-reference.html#usestate.
In your case just swipe the names.
const [ sectionDefects , setSectionDefects] = useState([]);
You have array destructuring the wrong way around.
It should be:
const [ sectionDefects, setSectionDefects ] = useState([]);

Resources