State changes are not being reflected in ReactJS - react-hooks

So here I have some elements mapped out based on state. I need the elements to reflect the current state after I set state. After hours of researching, I figured updating the variable with a spread operator would force my component to re-render when I set state with said variable. However, the state changes are not being reflected without refreshing.
const updateCurrentChat = () => {
let filtered = {}
findChat.forEach((chat) => {
chat.psuedoID.includes(userInfo._id) &&
chat.psuedoID.includes(currentTarget._id)
? (filtered = { ...chat }) // updating variable
: console.log(chat)
})
setPMData(filtered) // setting state
}
function PrivateMessages() {
const { pmData } = useContext(newContext)
return (
<>
<PrivateChatArea>
{pmData
? pmData.messages.map((messages) => {
return (
<PrivateChatContent id={messages.messageID}>
{messages.message}
</PrivateChatContent>
)
})
: null}
</PrivateChatArea>
</>
)
}

Hi #Romeo Richardson,
You can use useEffect hook to ensure that the component re-renders when the pmData state changes.
function PrivateMessages() {
const { pmData } = useContext(newContext)
const [, setRerender] = useState(0)
useEffect(() => {
setRerender((rerender) => rerender + 1)
}, [pmData])
return (
<>
<PrivateChatArea>
{pmData
? pmData.messages.map((messages) => {
return (
<PrivateChatContent id={messages.messageID}>
{messages.message}
</PrivateChatContent>
)
})
: null}
</PrivateChatArea>
</>
)
}
Alternatively, if you don't want to use useEffect. Then, you can use React.memo. You only need to export your component like this export default React.memo(PrivateMessages);.

Related

Is it possible to BehaviorSubject reset my useState value?

Problem: Whenever I click 't' key my theme changed as I expected. But for some reason this cause isModalVisible state reset. This cause one problem - When I have my modal open and click t - the modal disapear. I don't know why. Maybe BehaviorSubject cause the problem? Additionally, if I create another useState for test, which initial value is string 'AAA', and on open modal I set this state to string 'BBB'. Then I click 't' to change theme this test useState back to beginning value 'AAA'
My code looks like this:
const Component1 = () => {
const [mode, setMode] = useRecoilState(selectedModeState);
const { switcher, status } = useThemeSwitcher();
const toggleMode = (newTheme: MODE_TYPE) => {
theme$.next({ theme: newTheme });
localStorage.setItem('theme', newTheme);
};
useEffect(() => {
const mode_sub = theme$.subscribe(({ theme }) => {
switcher({ theme: theme });
setMode(theme);
});
return () => {
mode_sub.unsubscribe();
};
}, []);
return <Component2 toggleMode={toggleMode} currentMode={mode} />
}
const Component2 = ({toggleMode, currentMode}) => {
const [mode, setMode] = useState(currentMode);
const [savedTheme, setSavedTheme] = useRecoilState(selectedThemeState);
const getTheme = mode === MODE_TYPE.LIGHT ? MODE_TYPE.DARK : MODE_TYPE.LIGHT;
const themeListener = (event: KeyboardEvent) => {
switch (event.key) {
case 't':
setMode(getTheme);
toggleMode(getTheme);
break;
}
};
useEffect(() => {
document.addEventListener('keydown', themeListener);
document.body.setAttribute('data-theme', savedTheme);
localStorage.setItem('color', savedTheme);
return () => {
document.removeEventListener('keydown', themeListener);
};
}, [savedTheme]);
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
return (
<Modal isModalVisible={isModalVisible} setIsModalVisible={setIsModalVisible} />
)
}
UTILS:
export const selectedModeState = atom<MODE_TYPE>({
key: 'selectedThemeState',
default: (localStorage.getItem('theme') as MODE_TYPE) || MODE_TYPE.LIGHT,
});
export const selectedThemeState = atom<string>({
key: 'selectedColorState',
default: (localStorage.getItem('color') as string) || 'blue',
});
export const theme$ = new BehaviorSubject({
theme: (localStorage.getItem('theme') as THEME_TYPE) || THEME_TYPE.LIGHT,
});
I would like the theme change not to set visibleModal to false which causes the modal to close

React - useEffect not using updated value in websocket onmessage

I have a simple issue where a state value updates in my code but is not using the new value. Any ideas what I can do to adjust this?
const [max, setMax] = useState<number>(10);
useEffect(() => {
console.log('max', max); //This outputs correct updated value.
ws.onmessage = (message: string => {
console.log('max', max); //This is always 10.
if (max > 100) {
doSomething(message);
}
}
},[]);
function onChange() {
setMax(1000);
}
<Select onChange={onChange}></Select> //this is abbrev for simplicity
Your useEffect is running only once, on the initial render, using the values from initial render, so max variable is closure captured and not updated in any way. But the solution will be pretty simple, using useRef and one more useEffect to update the ref variable when max variable updates.
const maxRef = useRef(10); // same value
const [max, setMax] = useState(10);
// Only used to update ref variable
useEffect(() => {
maxRef.current = max;
}, [max]);
useEffect(() => {
console.log("max", maxRef.current);
ws.onmessage = (message) => {
console.log("max", maxRef.current);
if (maxRef.current > 100) {
doSomething(message);
}
};
}, []);

Why does my timer hook not update it's internal state?

For some reason my timer is not updating it's internal Timer State after I modify the input field. Here is the intial state of my page and State.
This is what my screen and state look like after I modify the input from 10 to 8 seconds. Notice that the Timer State does not update
Here is my code for the workout page:
function WorkoutPage(props: any) {
const DEFAULT_SECONDS_BETWEEN_REPS: number = 10
const [secondsBetweenRepsSetting, setSecondsBetweenRepsSetting] = useState(DEFAULT_SECONDS_BETWEEN_REPS)
const {secondsLeft, isRunning, start, stop} = useTimer({
duration: secondsBetweenRepsSetting,
onExpire: () => sayRandomExerciseName(),
onTick: () => handleTick(),
})
const onTimeBetweenRepsChange = (event: any) => {
const secondsBetweenRepsSettingString = event.target.value;
const secondsBetweenRepsSettingInt = parseInt(secondsBetweenRepsSettingString)
setSecondsBetweenRepsSetting(secondsBetweenRepsSettingInt)
}
return <React.Fragment>
<input type="number" name="secondsBetweenRepsSetting" value={secondsBetweenRepsSetting} onChange={onTimeBetweenRepsChange}/>
</React.Fragment>
}
Here is my useTimer Class:
import { useState } from 'react';
import Validate from "../utils/Validate";
import useInterval from "./useInterval";
export default function useTimer({ duration: timerDuration, onExpire, onTick}) {
const [secondsLeft, setSecondsLeft] = useState(timerDuration)
const [isRunning, setIsRunning] = useState(false)
function start() {
setIsRunning(true)
}
function stop() {
setIsRunning(false)
}
function handleExpire() {
Validate.onExpire(onExpire) && onExpire();
}
useInterval(() => {
const secondsMinusOne = secondsLeft - 1;
setSecondsLeft(secondsMinusOne)
if(secondsMinusOne <= 0) {
setSecondsLeft(timerDuration) // Reset timer automatically
handleExpire()
} else {
Validate.onTick(onTick) && onTick();
}
}, isRunning ? 1000 : null)
return {secondsLeft, isRunning, start, stop, }
}
My full codebase is here in case someone is interested: https://github.com/kamilski81/bdt-coach
Here's the sequence of events you expect:
User changes the input
The change handler fires and calls setSecondsBetweenRepsSetting with the new value
The component re-renders with the new value for secondsBetweenRepsSetting
useTimer is invoked with a duration property of the new value
The secondsLeft state in the useTimer hook changes to the new duration value <-- oops! this does not happen
Why doesn't this last item happen? Because within the useTimer implementation, the only place you use the duration is as the initial value of secondsLeft. Calling the hook a second time with a new duration value will not change the secondsLeft state, and this is by design.
My recommendation would be to include setSecondsLeft in the return value of the useTimer hook to give you a way to override the time left in the timer. You could then use setSecondsLeft directly in the input change handler:
const { secondsLeft, setSecondsLeft, isRunning, start, stop } = useTimer({
duration: secondsBetweenRepsSetting,
onExpire: () => sayRandomExerciseName(),
onTick: () => handleTick(),
});
const onTimeBetweenRepsChange = (event: any) => {
const secondsBetweenRepsSettingString = event.target.value;
const secondsBetweenRepsSettingInt = parseInt(
secondsBetweenRepsSettingString
);
setSecondsBetweenRepsSetting(secondsBetweenRepsSettingInt);
setSecondsLeft(secondsBetweenRepsSettingInt);
};

public properties when using React hooks

a start-up component can new a class and set values of some public properties in it to be used later in methods of same class.
What will be the equivalent of this in a hook component? Should these properties be added to a context and read from there? Is there a simpler option?
Thank you
If this are constant properties, you can define consts inside or outside the component.
const noOfItems = 5
const Comp = () => {
const items = 'cats'
return (
<div>{noOfItems} {items }></div>
)
}
If the values can change, and changing them causes re-render, then you should use useState or useReducer:
const Comp = ({ items }) => {
const [noOfItems, setNoOfItems] = useState(0)
return (
<>
<div>{noOfItems} {items}></div>
<button onClick={() => setNoOfItems(x => x + 1)}>
Add Items
</button>
</>
)
}
And if it's something you wish to mutate, but changing it won't cause re-render, you can do so with useRef:
const Comp = ({ items, doSomething }) => {
const [noOfItems, setNoOfItems] = useState(0)
const fn = useRef() // fn would be preserved between renders, and mutating it won't cause re-renders
useEffect(() => {
fn.current = doSomething // changing this won't cause a re-render
});
useEffect(() => {
fn.current(noOfItems)
}, [noOfItems])
return (
<>
<div>{noOfItems} {items}></div>
<button onClick={() => setNoOfItems(x => x + 1)}>
Add Items
</button>
</>
)
}
Although not public, you can can also keep variables (and consts) inside a closure - for example useEffect, as long as it's not invoked again:
const Comp = ({ items }) => {
const [noOfItems, setNoOfItems] = useState(0)
useEffect(() => {
const interval = setInterval(() => /* do something */) // the interval would preserved in the closure at it ins't called again
return () => clearInterval(interval)
}, [])
return (
<>
<div>{noOfItems} {items}></div>
<button onClick={() => setNoOfItems(x => x + 1)}>
Add Items
</button>
</>
)
}

React Hooks useState promptly Update

How can I promptly update the number state and watch console.log(number) updated value?
const [number,setNumber] = useState(0);
const minus = () => {
setNumber(number-1);
console.log(number);
}
return (
<>
<div>{number}</div>
<button onClick={minus}>-</button>
</>
)
What you are trying to do is a side-effect: print something onto the console.
This is what useEffect hook is for - it lets you perform side effects.
So here is a possible implementation:
function App() {
const [number, setNumber] = useState(0);
const minus = () => {
setNumber(number - 1);
};
useEffect(() => {
console.log(number);
}, [number]);
return (
<>
<div>{number}</div>
<button onClick={minus}>-</button>
</>
);
}
Of course, it may be an overkill solution if you are just using console.log for debugging purpose. If that's the case, #zynkn and #deepak-k's answers work just fine.
Try this
setNumber((number)=> {number-1 ; console.log(number)});
const [number,setNumber] = useState(0);
const minus = () => {
// setNumber(number-1);
// It is also work well but it is working with async.
// So you can't see the below console.log().
// console.log(number);
setNumber((prevNumber) => {
newNumber = prevNumber - 1;
console.log(newNumber);
return newNumber;
});
}
return (
<>
<div>{number}</div>
<button onClick={minus}>-</button>
</>
)

Resources