I am creating a timer and i want the timer to start ticking backwards based on the input value provided. I am confused on how to set the initialState in the useState hook instead of taking the default value as 0
Timer.js
const Timer = () => {
const [input,setInput] = useState();
const inputHandler = (e) => {
setInput(e.target.value);
}
const [time,setTime] = useState(0);
let timer;
useEffect(()=>{
timer = setInterval(() => {
if(time > 0){
setTime(time-1);
}
},1000)
return () => clearInterval(timer);
},[])
return (
<>
<h1>Timer</h1>
<input value = {input} onChange = {inputHandler}/>
<h1>{time}</h1>
</>
)
}
App.js
import './App.css';
import Timer from './components/Timer';
function App() {
return (
<div className="App">
<Timer />
</div>
);
}
export default App;
You can specify the type of your input
and initialize it like that. Suppose it's a number and the initial value is 1:
const [input, setInput] = useState<number>(1);
const Timer = () => {
const [input, setInput] = useState(0)
const [time, setTime] = useState(0)
const inputHandler = (e) => {
setTime(e.target.value)
setInput(e.target.value)
}
let timer
useEffect(() => {
timer = setInterval(() => {
if (time > 0) {
setTime(time - 1)
setInput(time - 1)
console.log('1')
}
}, 1000)
return () => clearInterval(timer)
}, [input])
return (
<>
<h1>Timer</h1>
<input defaultValue={input} onChange={inputHandler} />
<h1>{time}</h1>
</>
)
}
Use this code bro 😎
Related
I was trying to build a simple counter application, the app starts counting (+1/sec) when I click the start button and stops when I click the stop button.
I came up with 2 different solutions, one using setTimeout and the other using a for loop with delay. both these solutions work for incrementing the number. Are both of these valid react ways for creating a counter? is there a better way to do this?
When stopping the app I can stop it by changing the reference variable halt.current = true but changing the state setStop(true) does nothing, why is that?
function App() {
const [get, set] = useState(0);
const [stop, setStop] = useState(false);
const halt = useRef(false);
const loopfn = async () => {
halt.current = false;
setStop(false);
while (true) {
if (halt.current || stop) break;
await new Promise((res) => setTimeout(res, 1000));
set((prev: number) => prev + 1);
}
};
const timeoutloopfn = () => {
halt.current = false;
setStop(false);
setTimeout(() => {
if (halt.current || stop) return;
set((prev: number) => prev + 1);
timeoutloopfn();
}, 1000);
};
const stoploopref = () => {
halt.current = true;
};
const stoploopst = () => {
setStop((prev: boolean) => true);
};
return (
<div>
<button onClick={loopfn}>for-loop increment</button>
<button onClick={timeoutloopfn}>timeout increment</button>
<button onClick={stoploopref}>stop using ref</button>
<button onClick={stoploopst}>stop using state</button>
<button onClick={() => set(0)}>reset</button>
<p>{get}</p>
</div>
);
}
You may consider using the setInterval function instead, storing its id in a state and clearing it when stop is set to true:
function App() {
const [get, set] = useState(0);
const [stop, setStop] = useState(false);
const [intervalId, setIntervalId] = useState(-1);
const halt = useRef(false);
useEffect(() => {
// Stop the loop
if (stop && intervalId !== -1) {
clearInterval(intervalId)
setIntervalId(-1)
}
}, [stop])
const timeoutloopfn = () => {
halt.current = false;
setStop(false);
const newIntervalId = setInterval(() => {
set((prev: number) => prev + 1);
}, 1000);
setIntervalId(newIntervalId)
};
I would totally recommend useEffect for every function that requires timing.
import React, { useRef, useEffect, useState } from "react";
export default function App() {
const [number, setNumber] = useState(100);
let intervalRef = useRef();
const decreaseNum = () => setNumber(prev => prev - 1);
useEffect(() => {
intervalRef.current = setInterval(decreaseNum, 1000);
return () => clearInterval(intervalRef.current);
}, []);
return <div>{number}</div>;
}
I'm getting this error in React Hooks. The function exists but every time I type something in to the search bar I get this TypeError.
TypeError : setSearchField is not a function
Here's the code for reference :
export default function StudentAPI() {
const [searchField, setSearchField] = ('');
const [students, setStudents] = useState([]);
const getStudents = async () => {
return axios
.get("https://api.hatchways.io/assessment/students")
.then((res) => {
setStudents(res.data.students);
})
.catch((err) => console.log(err));
};
useEffect(() => {
getStudents();
}, []);
const handleChange = (e) => {
setSearchField(e.target.value);
}
const filteredStudents = students.filter((student) => {
console.log(student.firstName);
// return student.firstName.toLowerCase().includes(search.toLowerCase()) ||
// student.lastName.toLowerCase().includes(search.toLowerCase());
})
return (
<div className="container">
<SearchBox
placeholder={'Search by name'}
handleChange={handleChange}
value={searchField}
/>
{filteredStudents.length > 0 ? filteredStudents.map((student) => {
return <Student key={student.id} student={student}/>;
}) : students.map((student) => {
return <Student key={student.id} student={student}/>;
})}
</div>
);
};
You have to use the hook useState
const [searchField, setSearchField] = usestate('');
You must have the state declaration above
const [searchField,setSearchField]=useState()
You have an error because useState is not written!
You must change
const [searchField, setSearchField] = ('');
to
const [searchField, setSearchField] = useState('');
I'm trying to implement debounce in a small/test React application.
It's just an application that fetch data from an API and it has a text field for an auto complete.
import React, { useEffect, useState, useMemo } from 'react';
import axios from 'axios';
const API = 'https://jsonplaceholder.typicode.com/posts';
const AutoComplete2 = () => {
const [ text, setText ] = useState("")
const [ posts, setPosts ] = useState([])
useEffect(() => {
async function fetchData() {
const data = await axios.get(API);
if(parseInt(data.status) !== 200) return;
setPosts(data.data)
}
fetchData();
}, [])
const handleTextChange = (event) => setText(event.target.value);
const handleSelectOption = (str) => setText(str);
const showOptions = useMemo(() => {
if(text === '') return;
const showPosts = [...posts].filter((ele) => ele.title.toLowerCase().includes(text.toLowerCase()));
if(showPosts.length === 1) {
setText(showPosts[0].title);
} else {
return (
<div>
{showPosts.map((obj, index) => {
return (
<div key={index} >
<span onClick={() => handleSelectOption(obj.title)} style={{cursor: 'pointer'}}>
{obj.title}
</span>
</div>
)
})}
</div>
)
}
}, [text, posts])
// addding debounce
const debounce = (fn, delay) => {
let timer;
return function() {
let context = this;
let args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args)
}, delay);
}
}
const newHandleTextChange = ((val) => debounce(handleTextChange(val), 5000));
return (
<div>
<input type="text" value={text} onChange={newHandleTextChange} />
{showOptions}
</div>
)
}
export default AutoComplete2;
The application works, but not the debounce. I add a 5 seconds wait to clearly see if it is working, but every time I change the input text, it calls the function without the delay. Does anyone know why it is happening?
Thanks
A more idiomatic approach to debouncing in React is to use a useEffect hook and store the debounced text as a different stateful variable. You can then run your filter on whatever that variable is.
import React, { useEffect, useState, useMemo } from "react";
import axios from "axios";
const API = "https://jsonplaceholder.typicode.com/posts";
const AutoComplete2 = () => {
const [text, setText] = useState("");
const [debouncedText, setDebouncedText] = useState("");
const [posts, setPosts] = useState([]);
useEffect(() => {
async function fetchData() {
const data = await axios.get(API);
if (parseInt(data.status) !== 200) return;
setPosts(data.data);
}
fetchData();
}, []);
// This will do the debouncing
// "text" will always be current
// "debouncedText" will be debounced
useEffect(() => {
const timeout = setTimeout(() => {
setDebouncedText(text);
}, 5000);
// Cleanup function clears timeout
return () => {
clearTimeout(timeout);
};
}, [text]);
const handleTextChange = (event) => setText(event.target.value);
const handleSelectOption = (str) => setText(str);
const showOptions = useMemo(() => {
if (debouncedText === "") return;
const showPosts = [...posts].filter((ele) =>
ele.title.toLowerCase().includes(debouncedText.toLowerCase())
);
if (showPosts.length === 1) {
setText(showPosts[0].title);
} else {
return (
<div>
{showPosts.map((obj, index) => {
return (
<div key={index}>
<span
onClick={() => handleSelectOption(obj.title)}
style={{ cursor: "pointer" }}
>
{obj.title}
</span>
</div>
);
})}
</div>
);
}
}, [debouncedText, posts]);
return (
<div>
<input type="text" value={text} onChange={handleTextChange} />
{showOptions}
</div>
);
};
export default AutoComplete2;
import { useEffect, useState, useRef } from "react";
import axios from "axios";
import { backend_base_url } from "../constants/external_api";
export default function DebounceControlledInput() {
const [search_category_text, setSearchCategoryText] = useState("");
let debounceSearch = useRef();
useEffect(() => {
const debounce = function (fn, interval) {
let timer;
return function (search_key) {
clearTimeout(timer);
timer = setTimeout(() => {
fn(search_key);
}, interval);
};
};
const getCategories = function (search_key) {
axios
.get(`${backend_base_url}categories/${search_key}`)
.then((response) => {
console.log("API Success");
})
.catch((error) => {});
};
debounceSearch.current = debounce(getCategories, 300);
//use for initial load
//debounceSearch.current('');
}, []);
const searchCategory = (search_key) => {
debounceSearch.current(search_key);
};
return (
<form
className="form-inline col-4"
onSubmit={(e) => {
e.preventDefault();
}}
autoComplete="off"
>
<input
type="text"
placeholder=""
id="search"
value={search_category_text}
onChange={(e) => {
searchCategory(e.target.value);
setSearchCategoryText(e.target.value);
e.preventDefault();
}}
/>
</form>
);
}
i need a help please i am new in reactjs
how can i stop this counter and put the final value in databases using react js axios
the code
https://codesandbox.io/s/react-hook-counter-c2cei?file=/src/index.js:24-43
import React, { useState, useRef, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Counter() {
const [counter, setCounter] = useState(0);
useInterval(() => setCounter(counter + 1), 1000);
return (
<div className="App">
<h2>Counter: {counter}</h2>
<button type="button" onClick={() => setCounter(0)}>
Reset
</button>
</div>
);
}
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function runCallback() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(runCallback, delay);
return () => clearInterval(id);
}
}, [delay]);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
If you want to "stop" the timer and do something with the current counter value then I have some suggested tweaks to allow your timer to be paused/stopped and issue a side-effect with the current state.
Modify the useInterval hook to always run the cleanup function when the dependency updates. You can also simplify the interval callback, i.e. pass the callback ref's current value.
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
let id;
if (delay) {
id = setInterval(savedCallback.current, delay);
}
return () => clearInterval(id);
}, [delay]);
}
Add a delay state to your component that you can toggle between null and 1000.
const [delay, setDelay] = useState(1000);
Use a functional state update in your setCounter state updater to avoid stale state enclosures.
useInterval(() => setCounter((c) => c + 1), delay);
Add a button to toggle the interval running.
<button
type="button"
onClick={() => setDelay((delay) => (delay ? null : 1000))}
>
{delay ? "Pause" : "Start"}
</button>
Use an useEffect hook to run a side-effect to do what you need with the counter state when the interval is paused/stopped
useEffect(() => {
if (delay === null) { /* do stuff */ }
}, [counter, delay]);
Demo
Full code:
function Counter() {
const [counter, setCounter] = useState(0);
const [delay, setDelay] = useState(1000);
useInterval(() => setCounter((c) => c + 1), delay);
useEffect(() => {
if (delay === null) { /* do stuff */ }
}, [counter, delay]);
return (
<div className="App">
<h2>Counter: {counter}</h2>
<button type="button" onClick={() => setCounter(0)}>
Reset
</button>
<button
type="button"
onClick={() => setDelay((delay) => (delay ? null : 1000))}
>
{delay ? "Pause" : "Start"}
</button>
</div>
);
}
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
let id;
if (delay) {
id = setInterval(savedCallback.current, delay);
}
return () => clearInterval(id);
}, [delay]);
}
store setInterval id in a useState and return a function that clears interval, please check as answer if this solves your issue :)
import React, { useState, useRef, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Counter() {
const [counter, setCounter] = useState(0);
const [stopInterval] = useInterval(() => setCounter(counter + 1), 1000);
const onStopCounterClick = () => {
stopInterval();
// axios.post(...., {counter}); you can send here
}
return (
<div className="App">
<h2>Counter: {counter}</h2>
<button type="button" onClick={() => setCounter(0)}>
Reset
</button>
<button type="button" onClick={onStopCounterClick}>
Stop
</button>
</div>
);
}
function useInterval(callback, delay) {
const savedCallback = useRef();
const [intervalId, setIntervalId] = useState();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function runCallback() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(runCallback, delay);
setIntervalId(id);
return () => clearInterval(id);
}
}, [delay]);
const stopCounter = () => {
clearInterval(intervalId);
}
return [stopCounter];
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
I'm trying to set a state, which I fetch from an API in the form of an array.
Tried this in every way possible, doesn't work.
Any ideas on how to fix this?
instance is an axios.create that creates the instance to a localhost django server which has CORS-ALLOW-CROSS-ORIGIN True
import React, { useState } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const fetchText = async () => {
const response = await instance.get(`/one/list/`);
setOne(response.data);
};
fetchText();
return (
<>
<div>Hello World.</div>
{one.forEach(o => (
<p>o.text</p>
))}
</>
);
};
export default OneList;
Do it like this,
import React, { useState } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const fetchText = async () => {
const response = await instance.get(`/one/list/`);
setOne(response.data);
};
useEffect(() => {
fetchText();
},[ any variable you want it to fetch that again ]);
return (
<>
<div>Hello World.</div>
{one.forEach(o => (
<p>o.text</p>
))}
</>
);
};
export default OneList;
This looks to be a good use case for a useEffect hook. Also, you need to await async functions within useEffect statement. The useEffect hook cannot be an async function in itself. Also, the original implementation would result in an infinite loop. The setState function would trigger a re-render which would then trigger the fetch function to fire off again. Consider the following:
import React, { useState, useEffect } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const fetchText = async () => {
const request= await instance.get(`/one/list/`);
request.then( r => setOne(r.data))
};
useEffect(() => {
(async () => {
await fetchText();
})();
}, []);
return (
<>
<div>Hello World.</div>
{one.forEach(o => (
<p>o.text</p>
))}
</>
);
};
export default OneList;
Based on you guys' advice, I came up with the below code which works fine so far.
import React, { useState, useEffect } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const [el, setEl] = useState(null);
const fetchText = async () => {
let res = await instance.get("/one/list/");
setOne(res.data);
};
useEffect(() => {
(async () => {
await fetchText();
})();
}, []);
useEffect(() => {
const handleClick = e => {
let newEl = document.createElement("input");
newEl.value = e.target.innerHTML;
newEl.id = e.target.id;
newEl.addEventListener("keypress", e => handleInput(e));
newEl.addEventListener("focusout", e => handleInput(e));
e.target.parentNode.replaceChild(newEl, e.target);
newEl.focus();
};
const handleInput = async e => {
console.log(e.type);
if (
e.target.value !== "" &&
(e.key === "Enter" || e.type === "focusout")
) {
let payload = { text: e.target.value };
try {
e.preventDefault();
let res = await instance.put(`/one/update/${e.target.id}`, payload);
let text = res.data.text;
e.target.value = text;
let newEl = document.createElement("span");
newEl.innerHTML = text;
newEl.addEventListener("click", e => handleClick(e));
newEl.id = e.target.id;
e.target.parentNode.replaceChild(newEl, e.target);
} catch (e) {
console.error(e);
}
}
};
setEl(
one.map(o => (
<p>
<span id={o.id} onClick={e => handleClick(e)}>
{o.text}
</span>
</p>
))
);
}, [one]);
return <>{el}</>;
};
export default OneList;