Why doesn't my code work? I am trying to separate these two things into one, but useSelector does not work.
This code works
const st = useSelector((state) =>
state.or.st
);
const ter = useSelector(
({ main }) => main.st.ter
);
This code does not work
I want to put these two things in one useSelector and not in two?
Issue 1: Incorrect Function Usage
The useSelector hook expects a function that takes a single parameter state.
state is the whole state held by redux.
This would mean that your combine function should look more like the following:
const { units, organistationTerminology } = useSelector(state => {
organistationTerminology = state.main.organisation.terminology;
units = state.organisationStructure.units.sort(
(a, b) => a.sortOrder - b.sortOrder
);
return {
organistationTerminology,
units
}
});
Issue 2: Changing Reference
The combined selector function returns a new object which will have a difference reference each time the selector function is computed.
The default comparison used by useSelector to detect a change between values is a reference comparison.
This will result in your component being rerendered on any store change regardless of whether state.main.organisation.terminology or state.organisationStructure.units is updated.
Issue 3: Computation in Selector
Selectors will run on each store change and the resulting value will be compared with the previous value to determine whether a component rerender needs to be triggered.
This means that even if state.organisationStructure.units never changes you will still be running a sort on every store change.
Computations should, where possible, be preformed outside of the selector function.
Issue 4: Sort on State Value
sort() sorts the array in place which would be mutating your state.
Conclusion
Selector functions should be kept as simple / quick as possible as they will be run on every store change.
Pulling multiple values from separate sections of state in one selector usually adds more complexity than it is worth.
My suggestion would be to keep it simple:
const units = useSelector(state => state.organisationStructure.units);
const organistationTerminology = useSelector(state => state.main.organisation.terminology);
const sortedUnits = [...units].sort();
// OR the sort can be memoized using units as a dependency;
const sortedUnits = useMemo(() => [...units].sort(), [units]);
If this is something you will be reusing in a number of components you could wrap it up in a custom hook:
const useTerminologyAndSortedUnits = () => {
const units = useSelector(state => state.organisationStructure.units);
const terminology = useSelector(state => state.main.organisation.terminology);
const sortedUnits = useMemo(() => [...units].sort(), [units]);
return {
units: sortedUnits,
terminology
}
};
// The following can then be used in any of your function components
const { units, terminology } = useTerminologyAndSortedUnits()
Related
I want to push an 2d array into the end of a state. But somehow, when i log the state every time it changes with a useEffect, i replaces every existing array with the new one.
The variable "position" is my 2d array, also a state. Might this cause any reference problems?
I have no idea and im starting to lose my mind about this...
const [positionList, setPositionList] = useState([]);
useEffect(() => {
const updatePosition = [...positionList, position]
setPositionList(updatePosition);
}, [playerTurn])
expected output (or what i want)
positionList = [position1, position2, position3, //and so on]
but what i get is this (lets say i try to push the array three times)
positionList = [position3, position3, position3]
EDIT:
So, after some good ideas from the community, i found out that i had to copy my array i want to push (the "position") before i push it into the state. So my working code looks now like this:
const [positionList, setPositionList] = useState([])
useEffect(() => {
let positionCopy = [];
for (let i = 0; i < position.length; i++) {
positionCopy[i] = position[i].slice();
}
setPositionList([...positionList, positionCopy]);
}
You didn't add positionList as a dependency of your useEffect callback, so the positionList captured by the callback doesn't necessarily reflect the latest value.
useEffect(() => {
// return early if the update already happened to prevent recursion
const updatePosition = [...positionList, position]
setPositionList(updatePosition);
}, [playerTurn, positionList])
TBH, using useEffect to update a state hook is difficult to maintain and prone to accidental recursive behaviour.
You might get better mileage by using a state reducer with the useReducer hook. For anything beyond trivial state management, the useReducer hook is worth getting to grips with.
try:
const [positionList, setPositionList] = useState([]);
useEffect(() => {
setPositionList(state=> [...state, position]);
}, [playerTurn])
One thing I've recently been confused about is what the best way to run effects after a useState call, when the useState doesn't necessarily change the previous state value. For example, say I have a submit function in a form. Also say I have a state called randomNumber, and say in that submit function I call setRandomNumber and set the random number state to a random number from 1-2. Now say that for some reason every time the randomNumber is set, regardless of whether its value changes or not, we want to update the number in some database and navigate to a different page. If I use a useEffect(() => {updateDatabase(); }, [randomNumber]), the problem is that this will update the database even when the functional component first renders, which I do not want. Also, if the randomNumber doesn't change because we pick the same random number twice, we won't update the database after the second setRandomNumber call and we won't navigate to a different page which I don't want.
What would be the best way to 'await' a useState call. I read some articles saying we should potentially use flushSync, but I tried this and it doesn't seem to be working and it seems like this is a very unstable solution as of now.
Thanks!!!
You have 2 different requests here.
How to skip a useEffect on the first render. For this you could employ useRef as this does not trigger a re-render
const Comp = () => {
const firstRender = useRef(true);
useEffect(() => {
if (firstRender.current) {
firstRender.current = false;
return
} else {
// do your stuff
}
}, [randomNumber])
}
How to trigger useEffect even if you get the same number. Probably the simplest way would be to wrap your number inside an object and always generate a new object when generating a new number. This way you'll get a new ref all the time and the useEffect will always trigger. If you already have an object you could create a copy using spread operator. Something like this:
const Comp = () => {
const [randomNumber, setRandomNumber] = useState({number: 0})
useEffect(() => {
console.log(randomNumber.number)
}, [randomNumber])
// generate a new random number and set it like this
setRandomNumber({number: Math.random() + 1))
}
There is an event handler for click and when it triggered i want to pull specific data from redux using selector where all logic many-to-many is implemented. I need to pass id to it in order to receive its individual data. Based on rules of the react the hooks can be called in function that is neither a React function component nor a custom React Hook function.
So what is the way to solve my problem ?
const handleMediaItemClick = (media: any): void => {
// For example i check media type and use this selector to pull redux data by id
const data = useSelector(playlistWithMediaSelector(imedia.id));
};
As stated in the error message, you cannot call hooks inside functions. You call a hook inside a functional component and use that value inside the function. The useSelector hook updates the variable each time the state changes and renders that component.
Also, when you get data with useSelector, you should write the reducer name you need from the redux state.
const CustomComponent = () => {
// The data will be updated on each state change and the component will be rendered
const data = useSelector((state) => state.REDUCER_NAME);
const handleMediaItemClick = () => {
console.log(data);
};
}
You can check this page for more information.https://react-redux.js.org/api/hooks#useselector
You should probably use local state value to track that.
const Component = () => {
const [imediaId, setImediaId] = useState(null);
const data = useSelector(playlistWithMediaSelector(imediaId));
function handleMediaClick(id) {
setImediaId(id)
}
useEffect(() => {
// do something on data
}, [imediaId, data])
return <div>...</div>
}
Does that help?
EDIT: I gather that what you want to do is to be able to call the selector where you need. Something like (considering the code above) data(id) in handleMediaClick. I'd bet you gotta return a curried function from useSelector, rather than value. Then you would call it. Alas, I haven't figured out how to that, if it's at all possible and whether it's an acceptable pattern or not.
I came from here so please don't mark it as a duplicate.
I'm trying to get every value of mainState to be placed in anotherState so at the end I have an array of arrays. mainState is an changes on hover and it changes a lot so I can't assign every value I have in it to anotherState, I only need the value when clicked.
const [mainState, setMainState] = useState([])
const [anotherState, setAnotherState] = useState([])
const [currentItem, setCurrentItem] = useState(0)
//mainState changes on hover and it can change a lot
const onClickHandler = () => {
setAnotherState([...anotherState, mainState])
if (currentItem >= 4) {
// done with this component move to the next component
} else {
setCurrentItem(currentItem + 1)
setMainState([])
}
}
return (
<div onClick={onClickHandler}></div>
However, this doesn't work and even though mainState updates perfectly everytime, anotherState is always one step behind. currentItem represents an array index so I can't click one more time to get the last value of mainState in anotherState. I , using useEffect() which broke React because hover changes a lot and I ended up with an array of hundreds of values.
// tried useEffect() like in the question above, ended up with an array of hundreds of values
useEffect(() => setAnotherState([...anotherState, mainState]))
// tried using callback function like in one of the comments which didn't work and remained the same
setAnotherState(() => [...anotherState, mainState])
// another different way not using hooks is I created an empty array constant in the component
// and on click I tried pushing the value of mainState to that array using deep copy
// I keep ending up with the last array pushed only
const arrays = []
const onClickHandler = () => {
arrays.push(JSON.parse(JSON.stringify(mainState)))
}
How can I work around this?
setAnotherState is asynchronous. So if you want to get latest update value of anotherState.
You should use useEffect like
useEffect(() =>
Console.log("anotherState",anotherState)
// Do your stuff
,[anotherState])
This hook will called whenever another state gets updated using setAnotherState.
Here is my code for the problem, my issue is that the event is triggering multiple times when it hits the 960px breakpoint. This should only fire once it reaches the breakpoint, but I am getting an almost exponential amount of event triggers.
const mediaQuery = '(max-width: 960px)';
const mediaQueryList = window.matchMedia(mediaQuery);
mediaQueryList.addEventListener('change', (event) => {
if (event.matches) {
setState((prevState) => ({
...prevState,
desktopNavActivated: false,
}));
} else {
setState((prevState) => ({
...prevState,
menuActivated: false,
navItemExpanded: false,
}));
}
});```
Hi there and welcome to StackOverflow!
I'm going to assume, that your code does not run within a useEffect hook, which will result in a new event listener being added to your mediaQueryList every time your component gets updated/rendered. This would explain the exponential amount of triggers. The useEffect hook can be quite unintuitive at first, so I recommend reading up on it a bit. The docs do quite a good job at explaining the concept. I also found this article by Dan Abramov immensely helpful when I first started using effects.
The way you call your setState function will always cause your component to update, no matter whether the current state already matches your media query, because you pass it a new object with every update. Unlike the setState method of class based components (which compares object key/values iirc), the hook version only checks for strict equality when determining whether an update should trigger a re-render. An example:
{ foo: 'bar' } === { foo: 'bar' } // Always returns false. Try it in your console.
To prevent that from happening, you could set up a state hook that really only tracks the match result and derive your flags from it. Booleans work just fine with reference equality:
const [doesMatch, setDoesMatch] = useState(false)
So to put it all together:
const mediaQuery = '(max-width: 960px)';
const mediaQueryList = window.matchMedia(mediaQuery);
const MyComponent = () =>
const [match, updateMatch] = useState(false)
useEffect(() => {
const handleChange = (event) => {
updateMatch(event.matches)
}
mediaQueryList.addEventListener('change', handleChange)
return () => {
// This is called the cleanup phase aka beforeUnmount
mediaQueryList.removeEventListener('change', handleChange)
}
}, []) // Only do this once, aka hook-ish way of saying didMount
const desktopNavActivated = !match
// ...
// Further process your match result and return JSX or whatever
}
Again, I would really advise you to go over the React docs and some articles when you find the time. Hooks are awesome, but without understanding the underlying concepts they can become very frustrating to work with rather quickly.