I am trying to replace a state array using a separately given array. I am unable to get my state to update to hold the values of the separate array. I've tried several things
const [userFriendsList, setUserFriendsList] = useState([]);
window.chatAPI.recvFriendsList(message => {
if (message.length > userFriendsList.length)
{
console.log(message)
setUserFriendsList(message);
console.log(userFriendsList)
}
});
const [userFriendsList, setUserFriendsList] = useState({friendsList: []});
window.chatAPI.recvFriendsList(message => {
if (message.length > userFriendsList['friendsList'].length)
{
console.log(message)
setUserFriendsList({friendsList : message});
console.log(userFriendsList)
}
});
const [userFriendsList, setUserFriendsList] = useState([]);
window.chatAPI.recvFriendsList(message => {
if (message.length > userFriendsList.length)
{
console.log(message)
setUserFriendsList([ ... userFriendsList, message]);
console.log(userFriendsList)
}
});
None of these are updating the state.
Edit:
Component -
const FriendsList = () =>
{
const [checkFriendsList, setCheckFriendsList] = useState(true)
const [userFriendsList, setUserFriendsList] = useState([]);
window.chatAPI.recvFriendsList(message => {
console.log(message)
setUserFriendsList(oldFriendList => [ ...oldFriendList, ...message]);
console.log(userFriendsList)
});
useEffect ( () => {
if (checkFriendsList)
{
setCheckFriendsList(false);
window.chatAPI.getFriendsList();
}
}, [checkFriendsList])
return (
<div className="friends-list-container">
<List className="friends-list">
<ListItem className="friends-list-title"> Friends List </ListItem>
</List>
</div>
);
}
output:
The problem is in the condition if (message.length > userFriendsList.length).
If message is a non empty-string it will always be longer that your empty userFriendsList state, remove the condition and update the array with:
const [userFriendsList, setUserFriendsList] = useState([]);
window.chatAPI.recvFriendsList(message => {
setUserFriendsList(oldFriendList => [ ...oldFriendList, message]);
});
If message is an array just do:
const [userFriendsList, setUserFriendsList] = useState([]);
window.chatAPI.recvFriendsList(message => {
setUserFriendsList(oldFriendList => [ ...oldFriendList, ...message]);
});
Try disabling the if statement.
// if( your condition)
{
setUserFriendsList([ ... userFriendsList, ...message]);
}
Related
I have these properties declared in my app:
const [lockfileData, setLockFileData] = useState({});
const [socket, setSocket] = useState<RiotWSProtocol>(null);
const [api, setApi] = useState<LoLAPI>(null);
const [champions, setChampions] = useState<Champion[]>([]);
const [summoner, setSummoner] = useState<Summoner>(null);
const [autoAcceptQueue, setAutoAccept] = useState(true);
const [instalockEnabled, setEnableInstalock] = useState(true);
const [selectedChampion, setSelectedChampion] = useState<Champion>(null);
const [callRoleEnabled, setCallRoleEnabled] = useState(true);
const [selectedRole, setSelectedRole] = useState<Role>('Mid');
I have an event handler in my useEffect hook, and inside that it handles more events:
const onJsonApiEvent = useCallback(
(message: any) => {
//console.log(message);
if (
message.uri === '/lol-matchmaking/v1/ready-check' &&
autoAcceptQueue
) {
if (
message.data?.state === 'InProgress' &&
message.data?.playerResponse !== 'Accepted'
) {
api.acceptQueue();
}
} else if (
message.uri === '/lol-champ-select/v1/session' &&
message.eventType === 'Update'
) {
console.log('enabled?', instalockEnabled)
if (instalockEnabled) {
const myCellId = message.data.localPlayerCellId as number;
const myAction = (message.data.actions[0] as any[]).find(
(x) => x.actorCellId === myCellId
);
if (
!myAction.completed &&
myAction.isInProgress &&
myAction.type === 'pick'
) {
api.pickAndLockChampion(1, myAction.id);
}
console.log('myAction', myAction);
}
}
},
[api, autoAcceptQueue, instalockEnabled]
);
const onSocketOpen = useCallback(() => {
console.log('socket', socket);
if (socket) {
socket.subscribe('OnJsonApiEvent', onJsonApiEvent);
}
}, [onJsonApiEvent, socket]);
const onConnect = useCallback((data: LCUCredentials) => {
setLockFileData(data);
const lolApi = new LoLAPI(data);
setApi(lolApi);
lolApi.getOwnedChampions().then((champs) => {
setSelectedChampion(champs[0]);
setChampions(champs);
});
lolApi.getCurrentSummoner().then((summoner) => {
setSummoner(summoner);
});
const wss = new RiotWSProtocol(
`wss://${data.username}:${data.password}#${data.host}:${data.port}`
);
setSocket(wss);
}, []);
useEffect(() => {
if (socket) {
socket.on('open', onSocketOpen);
}
connector.on('connect', onConnect);
connector.start();
return () => {
connector.stop();
};
}, [onConnect, onSocketOpen, socket]);
The dependencies appear to be correct, so it should be using the up to date values in each handler.
However, inside the onJsonApiEvent handler, properties such as instalockEnabled are always the default value.
I am updating the value of instalockEnabled in a component on my page:
<FormControlLabel
control={
<Checkbox
checked={instalockEnabled}
name="instalockEnabled"
color="primary"
onChange={handleInstalockEnableChange}
/>
}
label="Enabled"
/>
And its handler looks like this:
const handleInstalockEnableChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
setEnableInstalock(e.target.checked);
};
How come this is happening when it is a dependency?
The janky solution I've come up with for now is to have a separate variable that is useRef and update that at the same time as updating the state, therefore it persists:
const [instalockEnabled, setEnableInstalock] = useState(true);
const instalockEnabledRef = useRef(instalockEnabled);
const handleInstalockEnableChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
setEnableInstalock(e.target.checked);
instalockEnabledRef.current = e.target.checked;
};
And then just use instalockEnabledRef.current inside of the event handlers where it needs to know the current value.
Can someone please tell me what's wrong with this and why the state of the 'video variable' remains false? So, even after the h2 element has rendered and is visible (i.e. the state of the video variable has been updated to true), when I click and call the hideVideo function, the video state remains false? Many thanks.
export default function App() {
const [message, showMessage] = useState(false);
const [video, setVideo] = useState(false);
let modalTimeout, videoTimeout;
useEffect(() => {
window.addEventListener("click", hideVideo);
setupTimeouts();
return () => {
clearTimeout(modalTimeout);
clearTimeout(videoTimeout);
};
}, []);
const setupTimeouts = () => {
modalTimeout = setTimeout(() => {
showMessage(true);
videoTimeout = setTimeout(() => {
showMessage(false);
setVideo(true);
}, 4000);
}, 2000);
};
const hideVideo = () => {
console.log(video);
showMessage(false);
if (video === true) {
setVideo(false);
}
};
return (
<div className="App">
{message && <h1>Message</h1>}
{video && <h2>Video</h2>}
</div>
);
}
When you call useEffect the window listener attach the default video value that is false to the function hideVideo() so it will be always false, I created a button to show you that the video state value does change. check the last test function
export default function App() {
const [message, showMessage] = useState(false);
const [video, setVideo] = useState(false);
let modalTimeout, videoTimeout;
useEffect(() => {
window.addEventListener("click", hideVideo);
setupTimeouts();
return () => {
clearTimeout(modalTimeout);
clearTimeout(videoTimeout);
};
}, []);
const setupTimeouts = () => {
modalTimeout = setTimeout(() => {
showMessage(true);
videoTimeout = setTimeout(() => {
showMessage(false);
setVideo(true);
}, 4000);
}, 2000);
};
const hideVideo = () => {
console.log(video);
showMessage(false);
if (video) {
setVideo(false);
}
};
const test = (event) => {
event.stopPropagation();
console.log(video)
}
return (
<>
{message && <h1>Message</h1>}
{video && <h2>Video</h2>}
<button onClick={test}>test</button>
</>
);
}
Not to sure why, but I am getting this error message:
TypeError: Invalid attempt to spread non-iterable instance
Is there a way to map over data to be able to store in the useEffect before the useEffect is triggered?
As it looks like this map does not seem to work.
const projectId = scheduleData && scheduleData.map(item => item.project_id).map(x => ({label: x, value: x}))
const schedules = () => {
const loading = useContext(LoadingContext)
const snackbar = useContext(SnackbarContext)
const user = useContext(UserContext)
const [scheduleData ,setScheduleData] = useState(null)
const [usageMode, setUsageMode] = useState(false)
const [scheduleFieldData, setScheduleFieldData] = useState({})
const [projectsAutoComplete, setProjectsAutoComplete] = useState([])
const [domainsAutoComplete, setDomainsAutoComplete] = useState([])
console.log(projectId, 'projectId');
console.log(projectsAutoComplete)
const projectId = scheduleData && scheduleData.map(item => item.project_id).map(x => ({label: x, value: x}))
useEffect(() => {
setProjectsAutoComplete([...projectId])
}, [])
console.log(projectsAutoComplete);
useEffect(() => {
async function onLoadScheduleData(){
loading.setLoading(true)
const results = await get('get_testing_schedules', user.user)
setScheduleData(results.data)
loading.setLoading(false)
}
onLoadScheduleData()
},[])
const onClick = () => {
setUsageMode(!usageMode)
}
console.log(usageMode);
const onScheduleFieldUpdate = (e, valueFromAutoComplete, nameFromAutoComplete) => {
const name = nameFromAutoComplete ? nameFromAutoComplete
: e.target.name || e.target.getAttribute('name')
const value = valueFromAutoComplete ? valueFromAutoComplete.map(val => val.value).join(',')
: e.target.innerText ? e.target.innerText
: e.target.value
setScheduleFieldData({...scheduleFieldData, ...{[name]: value}})
}
const onDomainAutoCompleteFieldUpdate = (e) => {
const value = e.target.innerText.toLowerCase()
const projectIdFiltered = projectId.filter(id => id.label.toLowerCase().startsWith(value))
setProjectsAutoComplete(projectIdFiltered)
}
console.log(scheduleFieldData);
return (
<div className=' CriticalObjectsGrid'>
<React.Fragment>
{configs.map((config, k) => {
const Field = config.field
return (
<div key={k} className='Main' style={{textAlign: 'center'}} >
<Field
uniqueIdentifier={k}
name={config.name}
onChange={onScheduleFieldUpdate}
onSelect={onScheduleFieldUpdate}
onAutoCompleteOnChange={onDomainAutoCompleteFieldUpdate}
value={scheduleFieldData[config.name]}
initialValues={
scheduleFieldData[config.name]}
options={projectsAutoComplete}
/>
</div>
)
})}
</React.Fragment>
}
</div>
)
}
export default schedules
Updated code
const schedules = () => {
const loading = useContext(LoadingContext)
const snackbar = useContext(SnackbarContext)
const user = useContext(UserContext)
const autoComplete = useContext(AutoCompleteContext)
const [scheduleData ,setScheduleData] = useState(null)
const [usageMode, setUsageMode] = useState(false)
const [scheduleFieldData, setScheduleFieldData] = useState({})
const [projectsAutoComplete, setProjectsAutoComplete] = useState([])
// const projectId = scheduleData && scheduleData.map(item => item.project_id).map(x => ({label: x, value: x}))
const [ScheduleAutoComplete, setScheduleAutoComplete] = useState([])
const intervals = ['"Daily"', '"Weekly"']
const projectId = scheduleData && scheduleData.map(item => item.project_id).map(x => ({label: x, value: x}))
useEffect(() => {
// setProjectsAutoComplete([...projectId])
setScheduleAutoComplete([...intervals])
}, [])
console.log(projectsAutoComplete);
useEffect(() => {
async function onLoadScheduleData(){
loading.setLoading(true)
const results = await get('get_testing_schedules', user.user)
setScheduleData(results.data)
if (projectId){setProjectsAutoComplete([...projectId])
}
loading.setLoading(false)
}
onLoadScheduleData()
},[])
const onClick = () => {
setUsageMode(!usageMode)
}
console.log(projectsAutoComplete);
const onScheduleFieldUpdate = (e, valueFromAutoComplete, nameFromAutoComplete) => {
const name = nameFromAutoComplete ? nameFromAutoComplete
: e.target.name || e.target.getAttribute('name')
const value = valueFromAutoComplete ? valueFromAutoComplete.map(val => val.value).join(',')
: e.target.innerText ? e.target.innerText
: e.target.value
setScheduleFieldData({...scheduleFieldData, ...{[name]: value}})
}
const onDomainAutoCompleteFieldUpdate = () => {
setScheduleAutoComplete(intervals)
setProjectsAutoComplete(projectId)
}
console.log(projectsAutoComplete);
return (
<div className=' CriticalObjectsGrid'>
{usageMode === false ?
<React.Fragment>
<Button
text='Creat schedule'
onClick={onClick}
/>
</React.Fragment>
:
<React.Fragment>
{configs.map((config, k) => {
const Field = config.field
return (
<div key={k} className='Main' style={{textAlign: 'center'}} >
<Field
uniqueIdentifier={k}
name={config.name}
onChange={onScheduleFieldUpdate}
onSelect={onScheduleFieldUpdate}
onAutoCompleteOnChange={onDomainAutoCompleteFieldUpdate}
value={scheduleFieldData[config.name]}
initialValues={scheduleFieldData[config.name]}
options={config.name === 'interval' ? ScheduleAutoComplete : config.name === 'project' ? ['projectsAutoComplete'] : [] }
/>
</div>
)
})}
</React.Fragment>
}
</div>
)
}
export default schedules
This error is referring to your attempt to spread projectId in your first useEffect hook. At the time of execution, projectId has a value of null, since it's getting it's value from the following line where scheduleData has an initial value of null.
const projectId = scheduleData && scheduleData.map(item => item.project_id).map(x => ({label: x, value: x}))
To avoid this error you could simply wrap setProjectsAutoComplete([...projectId]) in an if (projectId) check like:
useEffect(() => {
if (projectId){
setProjectsAutoComplete([...projectId])
}
}, [])
but a better solution would be to combine your two useEffect hooks, so that setProjectsAutoComplete([...projectId]) is run after onLoadScheduleData()
EDIT:
Try this:
const [projectId, setProjectId] = useState([])
const intervals = ['"Daily"', '"Weekly"']
async function onLoadScheduleData(){
loading.setLoading(true)
const results = await get('get_testing_schedules', user.user)
if (results.data) {
id = results.data.map(item => item.project_id).map(x => ({label: x, value: x}))
setProjectId(id)
setProjectsAutoComplete([...id])
}
loading.setLoading(false)
}
useEffect(() => {
setScheduleAutoComplete([...intervals])
onLoadScheduleData()
},[])
const onClick = () => {
setUsageMode(!usageMode)
}
I've removed the scheduleData variable entirely as it doesn't look like it needs to be kept in state. Instead I've made projectId a stateful variable, as it does seem to be used elsewhere. I moved the onScheduleLoadData function definition to outside of the useEffect hook and moved the projectId logic inside of it. I've also combined both useEffect hooks because they had the same dependencies. Let me know if this is more what you were looking for.
I have written the following React component and it's messy with the the dependencies and need for
useCallback
My question is: What about hooks am I misunderstanding to where I need to use this useCallback all the time?
const TagPicker = ({ value, defaultValue, tags, updateTags }) => {
const thisPicker = useRef(null);
const thisInput = useRef(null);
// Managing Tags
const [ selectedTags, setSelectedTags ] = useState(value || defaultValue);
const getIds = useCallback(() => {
let ids = [];
for(let x in selectedTags) {
ids.push(selectedTags[x].id);
}
return ids;
}, [ selectedTags ]);
const addTag = useCallback(tag => {
let ids = getIds();
if(!ids.includes(tag.id)) setSelectedTags([ ...selectedTags, tag ]);
}, [ selectedTags, getIds ]);
const removeTag = tag => {
let ids = getIds();
if(ids.includes(tag.id)) setSelectedTags([ ...selectedTags].filter(t => (t.id !== tag.id)));
}
// Typed text / Filtered List
const [ text, setText ] = useState('');
const [ filteredList, setFilteredList ] = useState([]);
useEffect(() => {
let ids = getIds();
if(text.length) {
let filteredTags = tags.filter(tag => (!ids.includes(tag.id)));
setOpen(true);
let list = [];
for(let x in filteredTags) {
if(filteredTags[x].title.toLowerCase().includes(text.toLowerCase())) list.push(filteredTags[x]);
}
setFilteredList(list);
} else {
setOpen(false);
setFilteredList([])
}
}, [ selectedTags, tags, text, getIds ]);
useEffect(() => {
updateTags(selectedTags);
}, [ updateTags, selectedTags ]);
// Toggling dropdown menu
const [ open, setOpen ] = useState(false);
useEffect(() => {
if(open) getCoordinates(thisPicker);
}, [ open ]);
// Handling Coordinates
const [ coords, setCoords ] = useState(null);
const getCoordinates = ({ current }) => {
if(current) {
const rect = current.getBoundingClientRect();
setCoords({
left: rect.x,
top: rect.y + window.scrollY + 40
});
}
}
useEffect(() => {
window.addEventListener('resize', () => getCoordinates(thisPicker));
return () => window.removeEventListener('resize', () => getCoordinates(thisPicker));
}, []);
// Handle Enter
const handleEnter = useCallback(e => {
if(e.keyCode === 13 && text) {
if(filteredList.length) addTag(filteredList[0]);
else addTag({
id: Math.random(),
title: text
});
setText('');
setOpen(false);
}
}, [ addTag, text, filteredList ]);
useEffect(() => {
let el = thisInput.current;
el.addEventListener('keyup', e => handleEnter(e));
return () => el.removeEventListener('keyup', e => handleEnter(e));
}, [ handleEnter ]);
return(
<div className='tag-picker' ref={thisPicker}>
<input type='text' value={text} onChange={e => setText(e.target.value)} ref={thisInput} />
<div className='selected-tags'>
{ selectedTags.map(tag => (
<Tag tag={tag} key={tag.id} removeTag={removeTag} />
)) }
</div>
{ open ?
<Portal>
<List text={text} items={filteredList} onSelect={addTag} setOpen={setOpen} coords={coords} />
</Portal>
: null }
</div>
);
};
There are some issues with your first useEffect. It has an extra dependency selectedTags.
If you don’t use useCallback to wrap the callback, the effect indeed depends on selectedTags. If you use useCallback, the effect only depends on the callback.
Effectively their meaning are the same. If the dependencies of useCallback change, the callback will also be recalculated. Hence the change of selectedTags triggers the changes of the callback (your getIds) and this triggers the effect: Without useCallback you put the dependencies directly inside the dependencies array of useEffect so their changes directly trigger the effect.
I have a small issue with a really simple component that doesn't display what I want.
const UserCards = (props) => {
const [retrievedData, setRetrievedData] = useState();
useEffect(() => {
const data = [];
props.users.map((user) => {
data.push(<UserCard key={user.username} user={user} />);
});
setRetrievedData(data);
}, []);
return (
<div className={styles.userCards}>{retrievedData && retrievedData}</div>
);
};
When I refresh the page it will not display my User cards. But If I had a timeout on useEffect like this :
const UserCards = (props) => {
const [retrievedData, setRetrievedData] = useState();
useEffect(() => {
const data = [];
setTimeout(function () {
props.users.map((user) => {
data.push(<UserCard key={user.username} user={user} />);
});
setRetrievedData(data);
}, 3000);
}, []);
return (
<div className={styles.userCards}>{retrievedData && retrievedData}</div>
);
};
Everything's fine!
I thought props were usable immediately but it seems I was wrong.
I tried to add [props] at the end of useEffect to be sure my state will be updated if props changed, but nothing...
I'm sure it's nothing but I've been struggling since yesterday!
Thank you!
Just add useEffect dependency, which will call your useEffect content every time, when dependency changed:
const UserCards = (props) => {
const [retrievedData, setRetrievedData] = useState();
useEffect(() => {
const data = [];
props.users.map((user) => {
data.push(<UserCard key={user.username} user={user} />);
});
setRetrievedData(data);
}, [props]);
return (
<div className={styles.userCards}>{retrievedData && retrievedData}</div>
);
};