Reputation: 323
After I set the state from the loadable within the App.js file:
import React from 'react';
import { useRecoilState, useSetRecoilState, useRecoilValueLoadable } from 'recoil';
import './App.css';
import { Point } from './components/Point';
import { FocusState } from './context/FocusState';
import { ItemListState } from './context/ItemListState';
import { RootState } from './context/RootState';
import { DataState } from './context/DataState';
function App() {
const setFocusState = useSetRecoilState(FocusState);
const setItemListState = useSetRecoilState(ItemListState);
const [rootState, setRootState] = useRecoilState(RootState);
const dataStateLoadable = useRecoilValueLoadable(DataState);
switch (dataStateLoadable.state) {
case 'hasValue':
let dataState = dataStateLoadable.contents;
let {root, focus, items} = dataState;
setFocusState(focus);
setItemListState(items);
setRootState(root);
return (
<div className="App">
<Point key={rootState} id={rootState} depth={0} />
</div>
)
case 'loading':
return (
<div className="App">
<p>Loading...</p>
</div>
)
case 'hasError':
throw dataStateLoadable.contents;
default:
return (
<div className="App">
<p>Loading...</p>
</div>
)
}
}
export default App;
Calling the setFocusState
function from within the Point
component doesn't seem to work:
export const Point: React.FC<{id: string, depth: number}> = ({id, depth}) => {
const pointRef = useRef<HTMLDivElement>(null);
const [focusState, setFocusState] = useRecoilState(FocusState);
const [itemState, setItemState] = useRecoilState(SingleItemStateFamily(id))
const parentPoint = useRecoilValue(SingleItemStateFamily(itemState.parent));
const grandparentPoint = useRecoilValue(SingleItemStateFamily(parentPoint.parent));
const setCursor = () => {
// mignt be null
const node = pointRef.current;
let range = document.createRange();
let sel = window.getSelection();
if (node !== null && node.childNodes.length > 0 && focusState.id === id) {
console.log(node, node.childNodes, focusState)
// select a range first
range.setStart(node.childNodes[0], focusState.cursorPosition);
range.setEnd(node.childNodes[0], focusState.cursorPosition);
// perform selection
sel?.removeAllRanges();
sel?.addRange(range);
node.focus();
}
}
const handleChange = (evt) => {
let newState = {...itemState};
newState.content = evt.currentTarget.innerHTML;
setItemState(newState);
}
const handleKeyEvent = (evt) => {
switch (evt.key) {
case "ArrowUp":
evt.preventDefault();
console.log("Shift focus to item above", parentPoint.children, itemState.id, parentPoint.children.indexOf(itemState.id));
// if it is the first child of a parent, shift focus to the parent
if (parentPoint.children.indexOf(itemState.id) === 0) {
console.log("Shift focus to parent")
setFocusState({id: parentPoint.id, cursorPosition: focusState.cursorPosition});
console.log(focusState);
}
// else, go to the next highest sibling
// the cursorPosition should be min(focusState.curpos, newPoint.content.length)
else {
console.log("Shift focus to previous sibling")
setFocusState({
id: parentPoint.children[parentPoint.children.indexOf(itemState.id)-1],
cursorPosition: focusState.cursorPosition
});
console.log(focusState);
}
break;
case "ArrowDown":
evt.preventDefault();
console.log("Shift focus to item below", parentPoint.children, itemState.id, parentPoint.children.indexOf(itemState.id));
// if it is the last child of a parent, shift focus to the parent's next sibling
if (parentPoint.children.indexOf(itemState.id) === parentPoint.children.length - 1) {
console.log("Shift focus to parent's next sibling")
setFocusState({
id: grandparentPoint.children[grandparentPoint.children.indexOf(parentPoint.id) + 1],
cursorPosition: focusState.cursorPosition
})
}
// else if it has any children, shift focus to the first child
else if (itemState.children.length > 0) {
console.log("Shift focus to first child")
setFocusState({
id: itemState.children[0],
cursorPosition: focusState.cursorPosition
})
}
// else, go to the next lowest sibling
else {
console.log("Shift focus to next sibling")
setFocusState({
id: parentPoint.children[parentPoint.children.indexOf(itemState.id)+1],
cursorPosition: focusState.cursorPosition
});
}
break;
case "Tab":
evt.preventDefault();
if (evt.shiftKey) {
console.log("Dedent item");
} else {
console.log("Indent item");
}
break;
default:
break;
}
}
const handleBlur = (evt) => {
let sel = window.getSelection();
let offset = sel?.anchorOffset as number;
setFocusState({
id: focusState.id,
cursorPosition: offset
})
}
useEffect(() => {
setCursor();
// eslint-disable-next-line
}, [])
return (
<ul className="point-item">
<li>
<ContentEditable onChange={handleChange}
html={itemState.content}
onKeyDown={handleKeyEvent}
innerRef={pointRef}
onBlur={handleBlur}
/>
</li>
{itemState.children.map((child_id: string) => (
<Point key={child_id} id={child_id} depth={depth+1}/>
))}
</ul>
)
}
When I add console.log(focusState)
to the relevant parts of the switch statement within function handleKeyEvent
, it shows that every time setFocusState
is called from within the Point component, nothing changes and the value of focusState
remains the same value as the initial setting. I am guessing this is why setCursor
doesn't get called via useEffect.
Would anyone be able to advise what is going wrong here? What would need to be changed for
setFocusState
to actually modify the value of focusState
when called from within the Point
componentsetCursor
Upvotes: 0
Views: 1131
Reputation: 3709
First of all: if you log focusState
right after setting it, you won't even log the new value because:
the Point
component is rendered with focusState
' old value (let's call it the #1 value)
you add an effect that calls setCursor
, the effect has empty array dependencies it gets called but it won't get called again
you set focusState
(to a #2 value) into the handler. Contextually, you log it hoping it contains the new value but...
since you set focusState
the component re-renders to render with the new focusState
value (#2)
your effect doesn't call setCursor
because it doesn't depend on focusState
I think that this is your problem 😉
Did you add the // eslint-disable-next-line
for the sake of asking this question or because you'd like to avoid calling setCursor
every time the reference to setCursor
is new (at every render)? If the latter, please consider refactoring it to
useEffect(() => {
// mignt be null
const node = pointRef.current;
let range = document.createRange();
let sel = window.getSelection();
if (node !== null && node.childNodes.length > 0 && focusState.id === id) {
console.log(node, node.childNodes, focusState)
// select a range first
range.setStart(node.childNodes[0], focusState.cursorPosition);
range.setEnd(node.childNodes[0], focusState.cursorPosition);
// perform selection
sel?.removeAllRanges();
sel?.addRange(range);
node.focus();
}
}, [id, focusState])
and removing the setCursor
function at all.
Please note: this answer could be incomplete, please update the question with a playable CodeSandbox to fix definitely your problem.
Upvotes: 1