I have a notification bell button with a dropdown. On top of it I have a small notification count.
I did the following logic to build the dropdown in React:
const [isOpen, setIsOpen] = useState(false);
const togglingMenu = () => setIsOpen(!isOpen);
const [isRed, setIsRed] = useState(true);
const toggleAlert = () => setIsRed(!isRed);
<NotificationsIcon onClick={() => { togglingMenu(); toggleAlert();}}></NotificationsIcon>
{!isOpen && (
<span><small className="notification-count">9+</small></span> )}
When I'm clicking on the bell button, the '9+' count disappears. How can I stop re-render it when I'm closing the notification dropdown?
You could use another useState like this:
const [isOpen, setIsOpen] = useState(false);
const [showNotificationCount, setShowNotificationCount] = useState(true);
const togglingMenu = () => {
if(showNotificationCount)
setShowNotificationCount(false)
setIsOpen(!isOpen);
}
const [isRed, setIsRed] = useState(true);
const toggleAlert = () => setIsRed(!isRed);
<NotificationsIcon onClick={() => { togglingMenu(); toggleAlert();}}></NotificationsIcon>
{showNotificationCount && (
<span><small className="notification-count">9+</small></span> )}
Related
When using React Bootstrap Typeahead's async typeahead, I get my search results, but then they very quickly disappear as soon as they appear, not allowing me to click on any of the search results. Here is my code:
const [searchOptions, setSearchOptions] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const filterBy = () => true;
const handleChange = (event) => {
console.log(event);
};
const handleSearch = async (query) => {
setIsLoading(true);
const results = await AdmiralService.searchByVendorIdOrTwitchName(query);
console.log(results.data);
setSearchOptions(results.data);
setIsLoading(false);
};
<AsyncTypeahead
filterBy={filterBy}
id="async-example"
labelKey={(option) => `${option.uid} - ${option.Name}`}
isLoading={isLoading}
minLength={3}
onSearch={handleSearch}
options={searchOptions}
onChange={handleChange}
placeholder="Search by VendorID/Username/Legal Name"
renderMenuItemChildren={(option, props) => (
<Fragment>
<div>{option.Name} - {option.uid}</div>
</Fragment>
)}
/>
I can see the results populate for just a second or so, and then they disappear.
(in REACT)
i have app function :
function App() {
const [welcomeMenu, setWelcomeMenu] = useState(true);
const [gameMenu, setGameMenu] = useState(false);
const [username, setUsername] = useState('');
const welcomeMenuShow = () => {
setWelcomeMenu(false);
}
const getUserName = (value) => {
setUsername(value);
console.log(username);
};
return (
<div className="App">
{
welcomeMenu ? <WelcomeMenu gameStarter={welcomeMenuShow} getUserName={getUserName}/> : null
}
</div>
);
}
in welcomemenu component i pass getUserName function to get username which user input
next in Welcome menu i have :
const WelcomeMenu = ({ gameStarter, getUserName }) => {
return (
<div className="welcome-menu">
<WelcomeText />
<WelcomeBoard gameStarter={gameStarter} getUserName={getUserName}/>
</div>
)
};
i pass get User Name in second time
in WelcomeBoard i have:
const WelcomeBoard = ({ gameStarter, getUserName }) => {
const [text, setText] = useState('');
const [warning, setWarning] = useState(false);
const checkBtn = (event) => {
if(text) {
gameStarter();
} else {
setWarning(true);
setTimeout(() => {
setWarning(false);
}, 3000);
}
};
const handleChange = (event) => {
setText(event.target.value);
};
return (
<div className="welcome-board">
<div className="username">Please enter the name</div>
<input type="text" value={text} onChange={handleChange} className="username-input" />
<button className="username-btn" onClick={() => {
getUserName(text);
checkBtn();
}}>start</button>
{warning ? <Warning /> : null}
</div>
)
};
in input onchange i make state and pass the input value on text state
next on button i have on click which active 2 function:
getUserName(text) // text is a state text with input value
checkBtn()
and after a click button in app i activate getUserName(text), this function pass the text in username state and here is a problem
when i try to see this text console.log(username) - it's give me null
but it if i try to see value console.log(value) - i see my input text
i don't understand how to fix that
react setState is async, which means those state variables are updated in the NEXT RENDER CYCLE(think of it as a thread or buffer).
try running this code if you want to understand what is happening BEHIND THE SCENES.
let renderCount = 0;
function TestApp() {
renderCount++;
const [state, setState] = useState(0);
const someRef = useRef(0);
someRef.current = state;
const someCallback = () => {
const someValue = new Date().getTime();
setState(someValue);
console.log(someRef.current, renderCount);
setTimeout(() => {
console.log(someRef.current, renderCount);
},100)
}
return <button onClick={someCallback}>clickme<button>;
}
I'm trying to create 2 buttons in react app using material ui buttons with both buttons are enabled at the start of page load. When onClick on one of the button, the other button should be disabled vice versa.
Initial state
When onClick
const [btn1, setBtn1] = useState(false);
const [btn2, setBtn2] = useState(false);
const onBtn1 = () => {
setBtn1(!btn1);
};
const onBtn2 = () => {
setBtn2(!btn2);
};
}
How do i go about doing it? is there anyway to just use a single useState hook to handle 2 state buttons?
You can achieve this with only one state variable and one function
Code
const [disabledButton, setDisabledButton] = useState('');
const onButtonClick = (param) => {
setDisabledButton(param);
}
<Button onClick={() => onButtonClick('btn2')} disabled={disabledButton === 'btn1'}>
Button 1
</Button>
<Button onClick={() => onButtonClick('btn1')} disabled={disabledButton === 'btn2'}>
Button 2
</Button>
You can just enable the other button when a button is clicked:
const onBtn1 = () => {
setBtn1(prevState => !prevState);
setBtn2(false);
};
const onBtn2 = () => {
setBtn2(prevState => !prevState);
setBtn1(false);
};
in the JSX:
<button onClick={onBtn1} disabled={btn1}>btn1</button>
<button onClick={onBtn2} disabled={btn2}>btn2</button>
Change the state of other button on press.
const [btn1, setBtn1] = useState(true); //Initially both buttons are enabled
const [btn2, setBtn2] = useState(true);
const onBtn1 = () => {
setBtn1(!btn1);
setBtn2(false);
};
const onBtn2 = () => {
setBtn2(!btn2);
setBtn1(false);
};
}
you can use a single state, please refer the suggestion below:
state and event handlers-
const [activeButton, setActiveButton] = useState("None");
const onBtn1 = () => {
setActiveButton("Button1");
};
const onBtn2 = () => {
setActiveButton("Button2");
};
HTML Element part -
<Button disabled={!['None', 'Button1'].includes(activeButton)}>Button1</Button>
<Button disabled={!['None', 'Button2'].includes(activeButton)}>Button2</Button>
How can I stop the both the timer when my button is clicked in reactjs.
I have noticed that when my timer is running my whole component is re-rendering every-time how to avoid this part.
export default function App() {
const [counterSecond, setCounterSecond] = React.useState(0);
const [counter, setCounter] = React.useState(120);
const [time, setTime] = React.useState("");
React.useEffect(() => {
setTimeout(() => setCounterSecond(counterSecond + 1), 1000);
setTimeout(() => setCounter(counter - 1), 1000);
}, [counterSecond , counter]);
const handletimer = () => {
setTime(counterSecond);
};
return (
<div className="App">
<div>Countdown: {counterSecond}</div>
<div>Countdown Reverse: {counter}</div>
<div>time: {time} </div>
<button onClick={handletimer}>Submit</button>
</div>
);
}
The best way is to add a state variable representing the status of work. ie: 'working', 'paused' and toggle it.
Also, you need to unsubscribe from timeout to avoid state updates if the component get unmounted.
here is an example where you can stop and resume the timers:
export default function App() {
const [counterSecond, setCounterSecond] = React.useState(0);
const [counter, setCounter] = React.useState(120);
const [time, setTime] = React.useState("");
const [status, setStatus] = React.useState("working");
React.useEffect(() => {
let secondCounterId;
let counterId;
if (status === "working") {
secondCounterId = setTimeout(
() => setCounterSecond(counterSecond + 1),
1000
);
counterId = setTimeout(() => setCounter(counter - 1), 1000);
}
return () => {
clearTimeout(counterId);
clearTimeout(secondCounterId);
};
}, [counterSecond, counter, status]);
const handletimer = () => {
setTime(counterSecond);
};
const stopTimers = () => {
setStatus("paused");
};
const resume = () => {
setStatus("working");
};
return (
<div className="App">
<div>Countdown: {counterSecond}</div>
<div>Countdown Reverse: {counter}</div>
<div>time: {time} </div>
<button onClick={handletimer}>Submit</button>
<button onClick={stopTimers}>Stop</button>
<button onClick={resume}>resume</button>
</div>
);
}
And a working codesandbox
You can create a timerRunning (boolean) variable to check if the timer should run in the useEffect() like this:
const [timerRunning, setTimerRunning] = React.useState(true);
React.useEffect(() => {
if (timerRunning) {
setTimeout(() => setCounterSecond(counterSecond + 1), 1000);
setTimeout(() => setCounter(counter - 1), 1000);
}
}, [counterSecond , counter, timerRunning]);
Then toggle the timerRunning in the handletimer:
const handletimer = () => {
setTimerRunning(false);
// ... other logic
};
The reason time is running because after each render useEffect() will be called.Hence the time. So to correct it, you can set like if "time" is in initialstate then do those functionality otherwise not. So after rendering time will be set set to new time and problem will be solved.
I have an infinite paging setup in a react redux project like this..
const ItemDashboard = () => {
const items= useSelector(state => state.items.items);
const dispatch = useDispatch();
const [loadedItems, setLoadedItems] = useState([]);
const [categories, setCategories] = useState([
'cycling',
'diy',
'electrical',
'food',
'motoring',
'travel'
]);
const initial = useRef(true);
const [loadingInitial, setLoadingInitial] = useState(true);
const [moreItems, setMoreItems] = useState([]);
const onChangeFilter = (category, show) => {
!show
? setCategories(categories.filter(c => c != category))
: setCategories([...categories, category]);
};
const loadItems = () => {
dispatch(getItems(categories, items && items[items.length - 1]))
.then(more => setMoreItems(more));
}
const getNextItems = () => {
loadItems();
};
useEffect(() => {
if(initial.current) {
loadItems();
setLoadingInitial(false);
initial.current = false;
}
}, [loadItems]);
useEffect(() => {
if(items) {
setLoadedItems(loadedItems => [...loadedItems, ...items]);
}
}, [items]);
useEffect(() => {
//this effect is fired on intial load which is a problem!
setLoadingInitial(true);
initial.current = true;
}, [categories]);
return (
<Container>
<Filter onFilter={onChangeFilter} categories={categories} />
{loadingInitial ? (
<Row>
<Col sm={8} className='mt-2'>
<LoadingComponent />
</Col>
</Row>
) : (
<ItemList
items={loadedItems}
getNextItems={getNextItems}
moreItems={moreItems}
/>
)}
</Container>
);
};
In the filter component, when the filter is changed the onChangeFilter handler method is fired which updates the array of categories in state. When this filter is changed I need to set the loadedItems in state to an empty array and call the load items callback again but I can't work out how to do it. If I add another effect hook with a dependency on categories state, it fires on the initial load also. I'm probably doing this all wrong as it feels a bit hacky the whole thing. Any advice much appreciated.