import { useState } from 'react'
import { AiOutlinePlus } from 'react-icons/ai'
import { TimerForm } from './TimerForm'
export const ToggleAddTimer = () => {
const [formState, setFormState] = useState(false);
const handleFormOpen = () => {
setFormState(true);
}
const handleFormClose = () => {
setFormState(false);
}
if (formState) {
return (
<TimerForm formClose={handleFormClose()} />
)
}
else {
return (
<button onClick={() => {handleFormOpen()}}> this function isn't getting invoked
<AiOutlinePlus/>
</button>
)
}
}
Try <TimerForm formClose={handleFormClose} />
and in your else
<button onClick={() => handleFormOpen()}>
Do this:
const [formState, setFormState] = useState(false)
const toggleFormState = () => {
setFormState(state => !state);
}
<button onClick={toggleFormState}>
<AiOutlinePlus/>
</button>
Test:
function App() {
const [formState, setFormState] = useState(false)
const toggleFormState = () => {
setFormState(state => !state);
}
return (
<button onClick={toggleFormState}>
Form State is {formState ? "true": "false"}
</button>
)
}
Related
the bellow component is for testing how to update a set with useState. The error occurs at state.has(num)
The state variable from useState is a set and has is a set build-in function. Why this error happens?
import { useState } from "react";
function App() {
const [state, setState] = useState(new Set());
console.log(typeof state);
function clickHandler(num) {
if (!state.has(num)) {
setState(prevState => {
return setState(new Set(prevState).add(num))
})
} else {
setState((prevState) => {
return setState(new Set(prevState).delete(num));
});
}
}
return (
<>
<button onClick={() => clickHandler(1)}>1</button>
<button onClick={() => clickHandler(2)}>2</button>
<button onClick={() => clickHandler(3)}>3</button>
{ state}
</>
);
}
export default App;
setState returns undefined
Remove setState from callback of setState
function clickHandler(num) {
if (!state.has(num)) {
setState(prevState => {
const set = new Set(prevState)
set.add(num)
return set
})
} else {
setState((prevState) => {
const set = new Set(prevState)
set.delete(num);
return set
});
}
}
import { useState, useRef, useEffect } from "react";
function App() {
const [stateValue, setStateValue] = useState(0);
const previousValue = useRef(null);
console.log(typeof stateValue);
useEffect(() => {
previousValue.current = stateValue;
}, [stateValue]);
function clickHandler(num) {
setStateValue(num);
}
return (
<>
<button onClick={() => clickHandler(1)}>1</button>
<button onClick={() => clickHandler(2)}>2</button>
<button onClick={() => clickHandler(3)}>3</button>
<br />
<h5>number: {stateValue}</h5>
<h5>previous number: {previousValue.current}</h5>
</>
);
}
export default App;
best of luck :)
As Dan Abramov introduced, I wrote an interval using useRef so it does not get created on every render. My code below saves the redux store into local storage every 20 seconds. However, the store inside of setInterval does not get updated which leads to saving the same initial store every 20 seconds.
import { useRef, useState } from "react";
import { useSelector } from "react-redux";
import styles from "./AutoSaveButton.module.scss";
import LOCAL_STORAGE_KEY from "../../../constants/localStorage";
const TWO_SECONDS = 2000;
const TWENTY_SECONDS = 20000;
function AutoSaveButton() {
const store = useSelector((state) => state);
const interval = useRef(null);
const [isAutoSaveOn, setIsAutoSaveOn] = useState(false);
const [isSavedMsgDisplayed, setIsSavedMsgDisplayed] = useState(false);
const toggleAutoSave = () => {
if (isAutoSaveOn) {
setIsAutoSaveOn(false);
clearInterval(interval.current);
return;
}
setIsAutoSaveOn(true);
interval.current = setInterval(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(store)); <-- how to update?
setIsSavedMsgDisplayed(true);
setTimeout(() => {
setIsSavedMsgDisplayed(false);
}, TWO_SECONDS);
}, TWENTY_SECONDS);
};
return (
<span
className={isAutoSaveOn ? styles.active : styles.stale}
onClick={toggleAutoSave}
>
{isAutoSaveOn ? "auto save off" : "auto save on"}
{isAutoSaveOn && (
<span
className={`material-symbols-outlined ${
isSavedMsgDisplayed ? styles["icon-visible"] : styles["icon-greyed"]
}`}
>
done
</span>
)}
</span>
);
}
export default AutoSaveButton;
since closure will always capture the variable store each time it's reinitialized. use [YOUR STORE].getState().[your reducer] instead
const { useRef, useState } = React
const { useSelector,Provider,useDispatch } = ReactRedux
const { configureStore,createSlice } = window.RTK
//import styles from "./AutoSaveButton.module.scss";
const LOCAL_STORAGE_KEY = "XXX"
const slice = createSlice({
name: 'data',
initialState: {
value: 0,
},
reducers: {
setData: (state, action) => {
state.value += action.payload
},
},
})
const mystore=configureStore({
reducer: {
data: slice.reducer
},
})
const TWO_SECONDS = 2000;
const TWENTY_SECONDS = 20000;
function AutoSaveButton() {
const store = useSelector((state) => state.data);
const interval = useRef(null);
const [isAutoSaveOn, setIsAutoSaveOn] = useState(false);
const [isSavedMsgDisplayed, setIsSavedMsgDisplayed] = useState(false);
const toggleAutoSave = (store) => {
if (isAutoSaveOn) {
setIsAutoSaveOn(false);
clearInterval(interval.current);
return;
}
setIsAutoSaveOn(true);
console.log("store outside", store);
interval.current = setInterval(() => {
setIsSavedMsgDisplayed(true);
console.log("store inside", mystore.getState().data);
setTimeout(() => {
setIsSavedMsgDisplayed(false);
}, TWO_SECONDS);
//localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(mystore.getState().data)); //<-- how to update?
}, 1000);
};
return (
<span
className=""
onClick={()=>toggleAutoSave(store)}
>
{isAutoSaveOn ? "auto save off" : "auto save on"}
{isAutoSaveOn && (
<span
className={`material-symbols-outlined `}
>
done
</span>
)}
</span>
);
}
const App=()=>{
return <Provider store={mystore}>
<AutoSaveButton />
<B/>
</Provider>
}
const B=()=>{
const dispatch=useDispatch()
return <button onClick={()=>dispatch(slice.actions.setData(12))}>
setData
</button>
}
ReactDOM.render(<App/>,document.getElementById("App"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/8.0.2/react-redux.min.js"></script>
<script src="https://unpkg.com/#reduxjs/toolkit#1.8.3/dist/redux-toolkit.umd.js"></script>
<div id="App">
</div>
second way watchc store with useEffect and put it to ref
const { useRef, useState, useEffect} = React
const { useSelector,Provider,useDispatch } = ReactRedux
const { configureStore,createSlice } = window.RTK
//import styles from "./AutoSaveButton.module.scss";
const LOCAL_STORAGE_KEY = "XXX"
const slice = createSlice({
name: 'data',
initialState: {
value: 0,
},
reducers: {
setData: (state, action) => {
state.value += action.payload
},
},
})
const mystore=configureStore({
reducer: {
data: slice.reducer
},
})
const TWO_SECONDS = 2000;
const TWENTY_SECONDS = 20000;
function AutoSaveButton() {
const store = useSelector((state) => state.data);
const interval = useRef(null);
const mystore = useRef(store);
useEffect(()=>{
mystore.current=store
},[store])
const [isAutoSaveOn, setIsAutoSaveOn] = useState(false);
const [isSavedMsgDisplayed, setIsSavedMsgDisplayed] = useState(false);
const toggleAutoSave = () => {
if (isAutoSaveOn) {
setIsAutoSaveOn(false);
clearInterval(interval.current);
return;
}
setIsAutoSaveOn(true);
console.log("store outside", store);
interval.current = setInterval(() => {
setIsSavedMsgDisplayed(true);
console.log("store inside", mystore.current);
setTimeout(() => {
setIsSavedMsgDisplayed(false);
}, TWO_SECONDS);
//localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(mystore.current); //<-- how to update?
}, 1000);
};
return (
<span
className=""
onClick={toggleAutoSave}
>
{isAutoSaveOn ? "auto save off" : "auto save on"}
{isAutoSaveOn && (
<span
className={`material-symbols-outlined `}
>
done
</span>
)}
</span>
);
}
const App=()=>{
return <Provider store={mystore}>
<AutoSaveButton />
<B/>
</Provider>
}
const B=()=>{
const dispatch=useDispatch()
return <button onClick={()=>dispatch(slice.actions.setData(12))}>
setData
</button>
}
ReactDOM.render(<App/>,document.getElementById("App"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/8.0.2/react-redux.min.js"></script>
<script src="https://unpkg.com/#reduxjs/toolkit#1.8.3/dist/redux-toolkit.umd.js"></script>
<div id="App">
</div>
I think I found an answer myself, but if any of you find this dumb, please leave a comment. I am open to discussions!
import { useEffect, useRef, useState } from "react";
import { useSelector } from "react-redux";
import styles from "./AutoSaveButton.module.scss";
import LOCAL_STORAGE_KEY from "../../../constants/localStorage";
const TWO_SECONDS = 2000;
const TWENTY_SECONDS = 5000;
function AutoSaveButton() {
const store = useSelector((state) => state);
const interval = useRef(null);
const [isAutoSaveOn, setIsAutoSaveOn] = useState(false);
const [isSavedMsgDisplayed, setIsSavedMsgDisplayed] = useState(false);
const toggleAutoSave = () => {
if (isAutoSaveOn) {
setIsAutoSaveOn(false);
clearInterval(interval.current);
return;
}
setIsAutoSaveOn(true);
interval.current = setInterval(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(store));
setIsSavedMsgDisplayed(true);
setTimeout(() => {
setIsSavedMsgDisplayed(false);
}, TWO_SECONDS);
}, TWENTY_SECONDS);
};
/* I added below useEffect and got desired result! */
useEffect(() => {
if (!interval.current) return;
clearInterval(interval.current);
interval.current = setInterval(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(store));
setIsSavedMsgDisplayed(true);
setTimeout(() => {
setIsSavedMsgDisplayed(false);
}, TWO_SECONDS);
}, TWENTY_SECONDS);
}, [store]);
return (
<span
className={isAutoSaveOn ? styles.active : styles.stale}
onClick={toggleAutoSave}
>
{isAutoSaveOn ? "auto save off" : "auto save on"}
{isAutoSaveOn && (
<span
className={`material-symbols-outlined ${
isSavedMsgDisplayed ? styles["icon-visible"] : styles["icon-greyed"]
}`}
>
done
</span>
)}
</span>
);
}
export default AutoSaveButton;
I have a simple app, sorta for chat purpuses. I fetch data from static file in json format. So this app shows all the messages from that file but also I want to edit the messeges, delete them and add via local storage. For that I used useEffect, but after refresh all the changes I do disappear.
This is my component:
export const WorkChat = (props) => {
const [messageValue, setMessageValue] = useState('');
const [edit, setEdit] = useState(null);
const [editmessageValue, setMessageEditValue] = useState('')
const submitMessage = () => {
const newMessage = {
id: Math.floor(Math.random() * 10000),
message: messageValue
}
props.addMessage(newMessage);
setMessageValue('')
}
const removeMsg = (id) => {
props.deleteMessage(id)
}
const goToEditMode = (message) => {
setEdit(message.id);
setMessageEditValue(message.message)
}
const saveChanges = (id) => {
const newMessagesArray = props.messages.map(m => {
if(m.id === id){
m.message = editmessageValue
}
return m
})
props.updateMessage(newMessagesArray);
setEdit(null)
}
useEffect(()=> {
let data = localStorage.getItem('work-messages');
if(data){
props.setMessages(JSON.parse(data))
}
}, []);
useEffect(()=> {
localStorage.setItem('work-messages', JSON.stringify(props.messages))
},[props.messages])
return (
<div className={s.workChatContainer}>
<input className={s.workInput} placeholder='Enter work message...' onChange={(e)=> setMessageValue(e.target.value)} value={messageValue}/>
<button className={`${s.btn} ${s.sendBtn}`} onClick={()=>submitMessage()}><SendIcon style={{fontSize: 20}}/></button>
<div>
{props.messages.map(m => (
<div key={m.id} className={s.messages}>
{edit !== m.id ? <div>
<span className={s.message}>{m.message}</span>
<button className={`${s.btn} ${s.deleteBtn}`} onClick={()=> removeMsg(m.id)}><DeleteOutlineIcon style={{fontSize: 15}}/></button>
<button className={`${s.btn} ${s.editBtn}`} onClick={()=> goToEditMode(m)}><EditIcon style={{fontSize: 15}}/></button>
</div>
:
<form>
<input className={s.editInput} value={editmessageValue} onChange={(e)=> setMessageEditValue(e.target.value)}/>
<button className={`${s.btn} ${s.saveBtn}`} onClick={()=> saveChanges(m.id)}><BeenhereIcon style={{fontSize: 15}}/></button>
</form>
}
</div>
))}
</div>
</div>
)
}
Just in case, this is my container component:
import { connect } from "react-redux"
import { setFloodMessagesAC, addFloodMessageAC, deleteFloodMessageAC, upadateMessageAC } from "../../redux/flood-reducer"
import { FloodChat } from "./FloodChat"
import { useEffect } from 'react'
import data from '../../StaticState/dataForFlood.json'
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)
Why useEffect doesn't work? It seems to me like it should, but it doesnt.
I figured it out. Since I use data from static file, I need to implement functions that get/set data from/to local storage right where I import it which is container component. Once I put those useEffect functions in container component it works perfectly well.
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
useEffect(()=> {
let data = JSON.parse(localStorage.getItem('flood-messages'));
if(data){
props.setFloodMessages(data)
}
console.log('get')
}, [])
useEffect(() => {
localStorage.setItem('flood-messages', JSON.stringify(props.messages));
console.log('set')
}, [props.messages]);
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)
what are the different kind of use cases of React.useCallBack, React.useMemo and React.Memo ?
import _ from 'lodash';
import React, { useState, useRef, useCallback } from 'react';
import PropTypes from 'prop-types';
import { useOutsideClickHandler } from 'utilities/method';
import { useTranslation } from 'react-i18next';
function FilterIndicators({
filters,
onDeleteFilter,
onRemoveFilter,
removeAllFilters,
}) {
const { t } = useTranslation();
const [openedTagIndex, setOpenedTagIndex] = useState(null);
const handleFilterButtonClick = useCallback(
(index) => {
if (openedTagIndex === index) {
setOpenedTagIndex(null);
} else {
setOpenedTagIndex(index);
}
},
[openedTagIndex],
);
const hideOptions = useCallback(() => {
setOpenedTagIndex(null);
}, []);
const deleteFilters = useCallback(
(e, filterName) => {
e.preventDefault();
onDeleteFilter(filterName);
hideOptions();
},
[onDeleteFilter, hideOptions],
);
const removeFilter = useCallback(
(e, filterOption, filterName) => {
e.preventDefault();
onRemoveFilter(filterOption, filterName);
hideOptions();
},
[onRemoveFilter, hideOptions],
);
const wrapperRef = useRef(null);
useOutsideClickHandler(wrapperRef, hideOptions);
return (
<FilterIndicatorsSC data-testid="admin-filter-indicators" ref={wrapperRef}>
{filters.map((filter, index) => {
if (filter.isShow) {
return (
<FilterTag
key={filter.id}
data-testid="admin-filter-sub-indicators"
>
<FilterButton onClick={() => handleFilterButtonClick(index)}>
<FilterName>{filter.name}</FilterName>
<CaretIcon className="fa fa-caret-down" />
<CloseIcon
className="fa fa-times"
title={t('asset_configuration_panel.remove_filter')}
onClick={(e) => deleteFilters(e, filter.filterName)}
/>
</FilterButton>
{openedTagIndex === index ? (
<FilterPopup>
{filter.filterOptions &&
filter.filterOptions.map((filterOption) => (
<FilterOption key={filterOption}>
<FilterOptionLabel>
{'' + filterOption}
</FilterOptionLabel>
<FilterOptionCloseIcon
className="fa fa-times"
onClick={(e) =>
removeFilter(e, filterOption, filter.filterName)
}
/>
</FilterOption>
))}
</FilterPopup>
) : null}
</FilterTag>
);
} else {
return null;
}
})}
{filters.filter((filter) => filter.isShow).length > 0 && (
<FilterBarClearButton
title={t('clear_all_filters')}
onClick={removeAllFilters}
>
{t('clear_all')}
</FilterBarClearButton>
)}
</FilterIndicatorsSC>
);
}
FilterIndicators.propTypes = {
/** List of all the filters applied */
filters: PropTypes.array,
onRemoveFilter: PropTypes.func.isRequired,
onDeleteFilter: PropTypes.func.isRequired,
removeAllFilters: PropTypes.func.isRequired,
};
FilterIndicators.defaultProps = {
filters: [],
onRemoveFilter: () => {},
onDeleteFilter: () => {},
removeAllFilters: () => {},
};
const areEqual = (prevProps, nextProps) => {
return _.isEqual(prevProps, nextProps);
};
export default React.memo(FilterIndicators, areEqual);
You can see above example in which I'm using React.memo, useCallback
for optimizing the app performance, it saved many re-rendering but be
careful when using these optimization.
I use the library react-use-modal, and
I'm trying to read the updated value of confirmLoading when inside the handleClick function.
handleClick does read the first value of confirmLoading defined when doing const [ confirmLoading, setConfirmLoading ] = useState(false), but never updates when I setConfirmLoading inside handleOk.
I don't understand what I'm doing wrong
import { Button, Modal as ModalAntd } from 'antd'
import { useModal } from 'react-use-modal'
export interface ModalFormProps {
form: React.ReactElement
}
export const ModalForm: React.FC = () => {
const [ confirmLoading, setConfirmLoading ] = useState(false)
const { showModal, closeModal } = useModal()
const handleOk = () => {
setConfirmLoading(true)
setTimeout(() => {
setConfirmLoading(false)
closeModal()
}, 1000)
}
const handleCancel = () => {
closeModal()
}
const handleClick = () => {
showModal(({ show }) => (
<ModalAntd
onCancel={handleCancel}
onOk={handleOk}
title='Title'
visible={show}
>
// the value of confirmLoading is always the one defined
// with useState at the beginning of the file.
<p>{confirmLoading ? 'yes' : 'no'}</p>
</ModalAntd>
))
}
return (
<div>
<Button onClick={handleClick}>
Open Modal
</Button>
</div>
)
}
This is happening because of closures. The component that you pass to showModal remembers confirmLoading and when you call function setConfirmLoading your component renders again and function handleClick is recreated. 'Old' handleClick and 'old' component in showModal know nothing about the new value in confirmLoading.
Try to do this:
export const ModalForm: React.FC = () => {
const { showModal, closeModal } = useModal();
const handleClick = () => {
showModal(({ show }) => {
const [ confirmLoading, setConfirmLoading ] = useState(false);
const handleOk = () => {
setConfirmLoading(true)
setTimeout(() => {
setConfirmLoading(false)
closeModal()
}, 1000)
};
const handleCancel = () => {
closeModal()
};
return (
<ModalAntd
onCancel={handleCancel}
onOk={handleOk}
title='Title'
visible={show}
>
// the value of confirmLoading is always the one defined
// with useState at the beginning of the file.
<p>{confirmLoading ? 'yes' : 'no'}</p>
</ModalAntd>
);
})
};
return (
<div>
<Button onClick={handleClick}>
Open Modal
</Button>
</div>
)
}