Reactjs custom hook won't fire using an if/else in useEffect? - javascript

I extracted my reducer function in a custom hook. When I try to fire decreaseMinutes from the custom hook nothing happens. The other functions of the hook work great tough - such as toggleActive- (probably because they are in an event handler).
Any idea how I can solve this?
Reducer + Hook Component:
import { useReducer } from "react";
import { defaultState } from "../setDefaultState";
const DECREASE_MINUTES = "decrease minutes";
const DECREASE_SECONDS = "decrease seconds";
const TOGGLE_ISACTIVE = "toggle isActive";
const RESET = "handle reset";
export const timerReducer = (state, action) => {
switch (action.type) {
case DECREASE_SECONDS:
console.log("decrease sec works");
return {
...state,
seconds: state.seconds - 1,
};
case DECREASE_MINUTES:
return { ...state, minutes: state.minutes - 1, seconds: 59 };
case TOGGLE_ISACTIVE:
return { ...state, isActive: !state.isActive };
case RESET:
return {
...state,
seconds: action.payloads.seconds,
minutes: action.payloads.minutes,
isActive: !state.isActive,
};
default:
return state;
}
};
//extracted custom Hook
export function useTimer() {
const [timerState, dispatch] = useReducer(timerReducer, defaultState);
const decreaseSeconds = () => dispatch({ type: DECREASE_SECONDS }, console.log("decrease hook works"));
const decreaseMinutes = () => dispatch({ type: DECREASE_MINUTES });
const toggleActive = () => dispatch({ type: TOGGLE_ISACTIVE });
const reset = () =>
dispatch({
type: RESET,
payloads: {
seconds: defaultState.seconds,
minutes: defaultState.minutes,
isActive: !state.isActive,
},
});
return {
timerState,
decreaseMinutes,
decreaseSeconds,
toggleActive,
reset,
};
}
Main Component:
const Timer = () => {
const { timerState, decreaseMinutes, decreaseSeconds, toggleActive, reset } = useTimer();
const [dateState, dispatchDate] = useReducer(dateReducer, defaultState);
useEffect(() => {
let interval = null;
// reduce seconds and minutes by 1
if (timerState.isActive) {
interval = setInterval(() => {
if (timerState.seconds > 0) {
decreaseSeconds; //--> this is what I'm trying to fire
console.log("conditional works");
} else if (timerState.seconds === 0) {
if (timerState.minutes === 0) {
clearInterval(interval);
} else {
decreaseMinutes;
}
}
}, 1000);
return () => clearInterval(interval);
}
}, [timerState.isActive, timerState.seconds, timerState.minutes]);

You need to call it. Since you defined them as function. Like following:
decreaseMinutes();
decreaseSeconds();

Related

React project using

I'm building UI using React, where I am using redux for increment & decrement function. Unfortunately my increment and decrement buttons are not working, may be this issue is coming due to some logical error. I will be very grateful if anyone help me to solve this issue. Here I am posting my source code.
**Creating Redux store code**
import {createStore} from 'redux';
const initialState ={counter:0, showCounter: true};
const counterReducer = (state =initialState,action) => {
if (action.type === 'increment') {
state.counter++;
return {counter: state.counter + 1,
showCounter: state.showCounter
};
}
if (action.type === 'increase') return{
counter: state.counter + action.amount,
}
if ( action.type ==='decrement'){
return {
counter: state.counter - 1,
};
}
if (action.type === 'toggle'){
return{
showCounter: !state.showCounter,
counter: state.counter
};
}
return state;
};
const store = createStore(counterReducer);
export default store;
**Counte.js code**
import {useDispatch, useSelector} from 'react-redux';
import classes from './Counter.module.css';
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter);
const show = useSelector(state => state.showCounter);
const incrementHandler = () => {
dispatch({type:'incremennt', amount:10});
};
const increaseHandler = () => {
dispatch({type:'decrement'});
};
const decrementHandler = () =>{
dispatch({type:'decremennt'});
};
const toggleCounterHandler = () => {
dispatch({type:'toggle'})
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{show && <div className={classes.value}>{counter}</div>}
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={increaseHandler}>Increase by 10</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
I hope this code can help you:
const counterReducer = (state = initialState, action) => {
if (action.type === 'increment') {
return {
...state,
counter: state.counter + 1
};
}
if (action.type === 'increase') {
return {
...state,
counter: state.counter + action.amount
};
}
if (action.type === 'decrement') {
return {
...state,
counter: state.counter - 1
};
}
if (action.type === 'toggle') {
return {
...state,
showCounter: !state.showCounter
};
}
return state;
};
After that, you should check the typo in the type of dispatch.
decremennt and incremennt.
Try this code:
const incrementHandler = () => {
dispatch({ type: 'increment', amount: 10 });
};
const increaseHandler = () => {
dispatch({ type: 'increase' });
};
const decrementHandler = () => {
dispatch({ type: 'decrement' });
};
const toggleCounterHandler = () => {
dispatch({ type: 'toggle' });
};

Why the event handler function cannot get the updated state object value?

Here is my code:
App.js:
import './App.css';
import { useAlarmClock } from "./useAlarmClock";
export default function App() {
const[action,data]=useAlarmClock();
let start=()=>{
action.start();
}
return (
<div className="App">
<button onClick={start}>Start Alarm Clock</button>
</div>
);
}
useAlarmClock.js
import { useReducer } from "react";
import AlarmClock from './AlarmClock';
let reducer = (state, action) => {
let result = { ...state };
console.log(action);
switch (action.type) {
case "init":
result = { "alarmClock": action.alarmClock }
break;
default: break;
}
return result
}
export function useAlarmClock() {
const [itemList, updateItemList] = useReducer(reducer, {});
let start = () => {
let alarmClock = new AlarmClock();
alarmClock.on("connectionTimeout", () => {
console.log(itemList);
})
alarmClock.start();
updateItemList({ "type": "init", alarmClock })
}
return [{
start: start
}, {
itemList
}];
}
AlarmClock.js
export default class AlarmClock {
constructor() {
let connectionTimeoutHandler;
/*=====================================================================*/
/* To configure handler for varies event */
/*=====================================================================*/
this.on = (eventType, param) => {
switch (eventType) {
case "connectionTimeout":
connectionTimeoutHandler = param;
break;
default: break;
}
};
this.start = () => {
setTimeout(() => {
connectionTimeoutHandler();
}, 5000);
}
}
}
I expect the output of the following function:
alarmClock.on("connectionTimeout", () => {
console.log(itemList);
})
should be:
{
"alarmClock":{}
}
However the actual result is as the following:
{}
So, I don't know why the console.log output does not contain the alarmClock object.
At each render a new object for itemList is created due immutability, but you have only link to the first instance of itemList in your 'connectionTimeout' callback. You can access needed version of itemList with ref hook, so you need to do smtn like this:
useAlarmClock.js
import { useReducer } from "react";
import AlarmClock from './AlarmClock';
let reducer = (state, action) => {
let result = { ...state };
console.log(action);
switch (action.type) {
case "init":
result = { "alarmClock": action.alarmClock }
break;
default: break;
}
return result
}
export function useAlarmClock() {
const [itemList, updateItemList] = useReducer(reducer, {});
const itemListRef = useRef(itemList);
itemListRef.current = itemList;
let start = () => {
let alarmClock = new AlarmClock();
alarmClock.on("connectionTimeout", () => {
console.log(itemListRef.current);
})
alarmClock.start();
updateItemList({ "type": "init", alarmClock })
}
return [{
start: start
}, {
itemList
}];
}
UPD: here is working example:
const {useReducer, useEffect, useRef} = React;
function App() {
const[action,data]=useAlarmClock();
let start=()=>{
action.start();
}
return (
<div className="App">
<button onClick={start}>Start Alarm Clock</button>
</div>
);
}
let reducer = (state, action) => {
let result = { ...state };
console.log(action);
switch (action.type) {
case "init":
result = { "alarmClock": action.alarmClock }
break;
default: break;
}
return result
}
function useAlarmClock() {
const [itemList, updateItemList] = useReducer(reducer, {});
const itemListRef = React.useRef(itemList);
itemListRef.current = itemList;
let start = () => {
let alarmClock = new AlarmClock();
alarmClock.on("connectionTimeout", () => {
console.log(itemListRef.current);
})
alarmClock.start();
updateItemList({ "type": "init", alarmClock })
}
return [{
start: start
}, {
itemList
}];
}
class AlarmClock {
constructor() {
let connectionTimeoutHandler;
/*=====================================================================*/
/* To configure handler for varies event */
/*=====================================================================*/
this.on = (eventType, param) => {
switch (eventType) {
case "connectionTimeout":
connectionTimeoutHandler = param;
break;
default: break;
}
};
this.start = () => {
setTimeout(() => {
connectionTimeoutHandler();
}, 5000);
}
}
}
ReactDOM.render(<App />,
document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

converting class to hooks getting messages

i'm new to react hooks, here i have been converting my project to hooks from classes, i'm getting this kind of message 'Error: Server error
at build_error (actions.js:57)
at eval (actions.js:83)' and 'GET http://127.0.0.1:8000/api/kamera/undefined 404 (Not Found)'
those errors come when i'm changing class to hooks (everything is set correcly using useState and useEffect), any idea ?
class:
initializeCollapses() {
const data = this.props[this.props.action];
let collapseStates = this.state.collapseStates;
if (!data || data.length < 1) {
return;
}
data.map((el) => {
collapseStates["" + el.name + el.identifier] = false;
return;
});
this.setState({
...this.state,
collapseStates: collapseStates,
});
}
componentDidMount() {
this.props.getItems[this.props.action](this.state.actionArgs).then(() => {
this.initializeCollapses();
});
}
Hooks:
const initializeCollapses = () => {
const data = [action];
if (!data || data.length < 1) {
return;
}
data.map((el) => {
collapseStates["" + el.name + el.identifier] = false;
return;
});
setCollapseStates(collapseStates);
};
useEffect(() => {
getItems[action](actionArgs).then(() => {
initializeCollapses();
});
}, []);
initializeCollapses() {
const data = this.props[this.props.action];
let collapseStates = this.state.collapseStates;
if (!data || data.length < 1) {
return;
}
data.map((el) => {
collapseStates["" + el.name + el.identifier] = false;
return;
});
this.setState({
...this.state,
collapseStates: collapseStates,
});
}
componentDidMount() {
this.props.getItems[this.props.action](this.state.actionArgs).then(() => {
this.initializeCollapses();
});
}
const mapDispatchToProps = (dispatch) => {
return {
getItems: {
analysers: (site) => dispatch(getAnalysers(site)),
platforms: (site) => dispatch(getPlatforms(site)),
brokers: (site) => dispatch(getBrokers(site)),
cameras: (site) => dispatch(getCameras(site)),
sites: (site) => dispatch(getSites())
},
};
};
The above class implementation in hooks would roughly be as below
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import getItems from "./store/actions";
or
import { cameras, sites, platform, brokers } from "./store/actions";
const actionArgs = useSelector(state => state.actionArgs); // In place of mapStateToProps
const dispatch = useDispatch();
useEffect(() => {
dispatch(getItems.cameras(actionArgs)) or dispatch(cameras(actionArgs)) //If destructured
}, []);
I have provided an understandable example with whatever data you provided. Refer this for a completely different approach or this one for the same mapDispatchToProps approach.
Good to refer
Example:
import React, {useReducer} from 'react';
const init = 0;
const myReducer = (state, action) => {
switch(action.type){
case 'increment':
return state + 1 // complex actions are kept in seperate files for better organised, clean code
case 'decrement':
return state - 1
case 'reset': // action types as well are kept as selectors
return init
default:
return state
}
};
function ReducerExample(){
const [count, dispatch] = useReducer(myReducer, init)
const add = () => {
dispatch({type: 'increment'})
}
const sub = () => {
dispatch({type: 'decrement'})
}
const reset = () => {
dispatch({type: 'reset'})
}
return (
<div>
<h4>Count: {count}</h4>
<button onClick={add} style={{margin: '10px'}}>Increment</button>
<button onClick={sub}>Decrement</button>
<button onClick={reset} style={{margin: '10px'}}>Reset</button>
</div>
)
}
export default ReducerExample;

useEffect is running when any function is running

First of all, I researched the question a lot, but I could not find a solution. I would appreciate if you help.
functional component
I add the code briefly below. this is not full code
state and props
// blog id
const { id } = props.match.params;
// state
const initialState = {
title: "",
category: "",
admin_id: "",
status: false
};
const [form, setState] = useState(initialState);
const [adminList, setAdminList] = useState([]);
const [articleText, setArticleText] = useState([]);
const [me, setMe] = useState([]);
const [locked, setLocked] = useState(true);
const timerRef = useRef(null);
// queries and mutations
const { loading, error, data } = useQuery(GET_BLOG, {
variables: { id }
});
const { data: data_admin, loading: loading_admin } = useQuery(GET_ADMINS);
const [editBlog, { loading: loadingUpdate }] = useMutation(
UPDATE_BLOG
);
const [lockedBlog] = useMutation(LOCKED_BLOG);
multiple useEffect and functions
useEffect(() => {
if (!loading && data) {
setState({
title: data.blog.title,
category: data.blog.category,
admin_id: data.blog.admin.id,
status: data.blog.status
});
setArticleText({
text: data.blog.text
});
}
console.log(data);
}, [loading, data]);
useEffect(() => {
if (!loading_admin && data_admin) {
const me = data_admin.admins.filter(
x => x.id === props.session.activeAdmin.id
);
setAdminList(data_admin);
setMe(me[0]);
}
}, [data_admin, loading_admin]);
useEffect(() => {
const { id } = props.match.params;
lockedBlog({
variables: {
id,
locked: locked
}
}).then(async ({ data }) => {
console.log(data);
});
return () => {
lockedBlog({
variables: {
id,
locked: false
}
}).then(async ({ data }) => {
console.log(data);
});
};
}, [locked]);
// if loading data
if (loading || loading_admin)
return (
<div>
<CircularProgress className="loadingbutton" />
</div>
);
if (error) return <div>Error.</div>;
// update onChange form
const updateField = e => {
setState({
...form,
[e.target.name]: e.target.value
});
};
// editor update
const onChangeEditor = text => {
const currentText = articleText.text;
const newText = JSON.stringify(text);
if (currentText !== newText) {
// Content has changed
if (timerRef.current) {
clearTimeout(timerRef.current);
}
setArticleText({ text: newText });
if (!formValidate()) {
timerRef.current = setTimeout(() => {
onSubmitAuto();
}, 10000);
}
}
};
// auto save
const onSubmitAuto = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
editBlog({
variables: {
id,
admin_id,
title,
text: articleText.text,
category,
status
}
}).then(async ({ data }) => {
console.log(data);
});
};
// validate
const formValidate = () => {
const { title, category } = form;
return !title || !articleText.text || !category;
};
// clear state
const resetState = () => {
setState({ ...initialState });
};
return (
// jsx
)
first issue, when call onSubmitAuto, first useEffect is running again. i dont want this.
because I just want it to work on the first mount.
second issue, if the articleText state has changed before, when mutation it does not mutate the data in the form state. but if the form state changes first, it mutated all the data. I think this issue is the same as the first issue.
I hope I could explain the problem. :/
Ciao, I have an answer to the first issue: when onSubmitAuto is triggered, it calls editBlog that changes loading. And loading is on first useEffect deps list.
If you don't want that, a fix could be something like that:
const isInitialMount = useRef(true);
//first useEffect
useEffect(() => {
if(isInitialMount.current) {
if (!loading && data) {
setState({
title: data.blog.title,
category: data.blog.category,
admin_id: data.blog.admin.id,
status: data.blog.status
});
setArticleText({
text: data.blog.text
});
}
console.log(data);
if (data !== undefined) isInitialMount.current = false;
}
}, [loading, data]);

Condition inside setInterval in functional component

I set an interval inside useEffect to update data every 33 seconds if a state variable can_update is true.
The initial value for can_pdate = true. The problem is, even if I change can_update to false (using disable_update function), in the update_groups function it still comes as true.
const [can_update, set_can_update] = useState(true);
const [groups, set_groups] = useState([]);
const intervalRef = useRef();
useEffect(() => {
update_groups();
const update_interval = setInterval(() => {
update_groups();
}, 33000);
intervalRef.current = update_interval;
return () => {
clearInterval(intervalRef.current);
};
}, [project_data.id]);
const update_groups = () => {
if (can_update) {
UI.get(`/project/${project_data.id}/controllers/`).then(
(data) => {
set_groups(data.groups);
},
(error) => {
console.log("Не удалось загрузить список групп");
},
);
}
};
const enable_update = () => {
set_can_update(true);
};
const disable_update = () => {
set_can_update(false);
};
I've tried moving condition into
setInterval: `const update_interval = setInterval(() => {
if (can_update){ update_groups()};
}
and replacing setInterval for recursive setTimeout. No changes.
I've had somewhat similar code inside a class component, and there didn't seem to be any problems like this.
You need add can_update to useEffect deps, otherwise
all values
from the component scope (such as props and state) that change over
time and that are used by the effect.
https://reactjs.org/docs/hooks-effect.html
In your case useEffect was called once, inside it every 33 seconds a function update_groups was called with scoped value can_update = true.
React.useEffect(() => {
if (can_update) {
update_groups();
const update_interval = setInterval(() => {
update_groups();
}, 33000);
intervalRef.current = update_interval;
return () => {
clearInterval(intervalRef.current);
};
}
}, [project_data.id, can_update]);
const update_groups = () => {
UI.get(`/project/${project_data.id}/controllers/`).then(
data => {
set_groups(data.groups);
},
error => {
console.log('Не удалось загрузить список групп');
},
);
};

Categories

Resources