useEffect triggers when I change a state value changes to false - javascript

This thing is driving me crazy I hope someone can light me up!
I have this reducer:
deleteNoteById: (state, action) => {
//...
state.active = false;
//...
}
That reducer changes the state.active to false and should activate this useEffect code that I call from a component:
const { active } = useSelector( state => state.journal );
useEffect(() => {
// Code...
}, [active]);
But it doesn't. It only executes the code inside useEffect when the action is different to false.
For example, if the action is something like:
{
title: '',
body: '',
date: new Date().getTime(),
imagesUrls:[]
}
It will execute the useEffect code but if the action is false it won't.
I checked my state with Redux Devtools and the state.active changes to false but the useEffect code won't be executed.
Diff tab:
I thought that any change (even set the variable to false) in the useEffect dependencies should triggers it.
Any ideas?

in reducer you change state.active
but in useSelector you get state.journal

Related

React/Redux component with checkboxes does not update when click on checkbox even though I'm returning new state

I've been stuck for a while trying to make the re-render in the checkboxes to work, the variables are being updated but it's just the rendering that doesn't happen.
I'm receiving a response from the backend that contains an object with an array of steps, I'm going to render a checkbox for every step if it's from a specific type. As soon as I received the object, I add in every step a new property value to use it later for checking the checkboxes.
This is my reducer:
export const MyObject = (state: MyObject = defaultState, action: FetchMyObjectAction | UpdateStepsInMyObjectAction) => {
switch (action.type) {
case "FETCH_MYOBJECT":
return {
...action.payload, // MyObject
steps: action.payload.steps.map((step) => {
if (step.control.controlType === "1") { // "1" = checkbox
return {
...step,
value: step.control.defaultValues[0] === "true" ? true : false, // Adding the property value
};
}
return step;
}),
};
case "UPDATE_STEPS":
return {
...state,
steps: state.steps.map((step) => {
if (step.id === action.payload.stepId) { // if this is the checkbox to update
return {
...step,
value: action.payload.checked,
};
}
return step;
}),
};
default:
return state;
}
This is how I'm rendering the checkboxes:
for (let step of steps) {
if (step.control.controlType === "1") {
controls.push(
<Checkbox
label={step.displayName}
checked={step.value}
onChange={(_ev, checked) => {
callback(step.id, checked);
}}
disabled={false}
className={classNames.checkbox}
/>
);
}
}
callback is a function that calls the reducer above for the case "UPDATE_STEPS".
After inspecting the variables I can see that they are being updated properly, it's just that the re-render doesn't happen in the checkboxes, not even the first time I check the box, the check doesn't appear. If I move to a different component and then go back to the component with the checkboxes I can see now the checks. But if I check/uncheck within the same component, nothing happens visually.
As far as I know, I'm returning new objects for every update, so mutability is not happening. Can you see what I'm missing?
Thanks!
First I would inspect if the checkbox works with useState to manage your state.
import { useState } from "react";
function CheckBoxForm() {
const [checked, setChecked] = useState(false);
return <Checkbox checked={checked} onChange={() => setChecked(!checked)} />;
}
Then I would check if you have wired up the reducer correctly using redux or useReducer. When you dispatch an action it should trigger a rerender. For troubleshooting this using redux please refer to the redux docs: https://react-redux.js.org/troubleshooting#my-views-aren-t-updating-when-something-changes-outside-of-redux.
You may be updating the object directly rather than dispatching an action using the function provided by the redux store. If you are using hooks, here is how you wire up your app to make sure the component props are subscribed to changing in the redux store. You must wrap with a provider, use redux hooks like useSelector and use their provided dispatch function: https://react-redux.js.org/api/hooks
Using useReducer is a much simpler process and will be easier to troubleshoot: https://beta.reactjs.org/reference/react/useReducer

How to remove a functional dependancy from a state dispatch update

The issue I'm facing is that I have an item in state called foo and then I have a useCallBack that fires a series of dispatches and references the state item foo.
I'm also firing a UseEffect on page load to handle any changes from the database.
The issue I'm having is as follows
The page loads
useEffect triggers and loads data from server
The useEffect runs it's code and if required calls the useCallBack which is a dependancy of the useEffect
The useCallback fires a series of dispatches that update foo
State Item foo has changed
The useCallback is watching foo and updates
The useEffect is watching the useCallback and as it has changed it runs again
Steps 3-7 repeat infinitely
An example in code format:
const { state, dispatch } = React.useContext(AppContext};
const { foo } = state;
const handleBar = React.useCallBack((bar: IBarType) => {
const bar_exists = _.find(foo, {id: bar.id});
if (bar){
dispatch({ type: 'rainbows', payload: bar });
} else {
dispatch({ type: 'clouds', payload: bar });
}
}, [foo]}
React.useEffect(() => {
serverFunction...
if (x){
handleBar(data)
}
}, [handleBar]}
My assumption is that the dependancy on foo within the handleBar callback is causing the infinite loop, I know you can remove the dependancy from a reactHook by using something like setState(bar => bar ...).
However, I Just can't seem to apply the same rules to the handleBar function.
I've tried const bar_exists = _.find(foo: as IFooType => foo, { id: bar.id}); to no luck.

Why does my state not appear to be updating in my component?

I am new to Redux, though I have done a bit of work with React before.
I'm using a tutorial to test using Actions, Action Creators, and Reducers in my application, and so far I think I'm about 90% of the way there.
componentDidMount() {
this.props.levelUp();
this.props.levelUp();
this.props.levelUp();
this.props.levelUp();
this.props.levelUp();
console.log("final prop");
console.log(this.props);
}
const mapStateToProps = (state) => {
console.log("state general");
console.log(state.general);
return {
general: state.general,
};
};
Both of the console logs get triggered here, and they increment with each level up operation or decrement with every level down operation.
function mapDispatchToProps(dispatch) {
return bindActionCreators(generalActions, dispatch);
}
This is in my reducer file:
export default (state = 1, action) => {
console.log(state);
switch (action.type) {
case 'LEVEL_UP':
console.log(action.type);
return state + 1;
case 'LEVEL_DOWN':
return state - 1;
}
return state;
};
My console logs here seem to be capturing the right increment - the value in the reducer goes up one every time I call this.props.levelUp()
However when I do the final logging of the props in componentDidMount(), the value is 1.
Why is this? Am I not persistently saving the data? Is there some other reason why I'm not returning state the way I am envisioning?
componentDidMount will be fired once the component did mount. Afterwards your actions are fired hence why you should do your console.log() statements inside of something like componentDidUpdate() or static getDerivedStateFromProps().
More about lifecycles in react: https://reactjs.org/docs/state-and-lifecycle.html
Greetings

Can not get new props after action dispatching

I have a React component:
class Board extends React.Component {
// ...
compareLastPair() {
const {gameplay} = this.props;
console.log('after dispatching, before compare', gameplay.clickedTiles); //-> [1]
// i got old state as before dispatching an action, but expected refreshed props
// thus, condition below never executes
if(gameplay.board.length === gameplay.pairedTiles.length + gameplay.clickedTiles.length) {
this.compareTiles();
}
}
handleClick(id) {
const {gameplay, dispatch} = this.props;
// ...some code
else if(!gameplay.clickedTiles.includes(id) && gameplay.clickedTiles.length < 2) {
console.log('before dispatching', gameplay.clickedTiles); // -> [1]
dispatch(clickTile(id));
this.compareLastPair();
}
}
//...
}
My reducer dispatches sync action:
const gameplay = (state = {board: [], clickedTiles: [], pairedTiles: [], round: 0}, action) => {
switch(action.type) {
case 'CLICK_TILE':
return {...state, ...{clickedTiles: state.clickedTiles.concat(action.id)}}
}
}
My question is: why my compareLastPair function gets the same props as before dispatching in handleClick function, despite the fact that the state was updated by Redux(you can see it in Redux-logger at the image) and clickedTiles array should be concantenated by reducer.
Even if your dispatch action is synchronous (but we don't know... you didn't shared the code), props update in the React component follow the normal asynchronous lifecycle, while you are explicitly calling compareLastPair after the dispatch.
React/Redux do not work this way: new props will be received by your component after your call.
For your test, I suggest you to call compareLastPair inside the componentDidUpdate lifecycle method, which is called after prop changes.

Adding/Removing animation classes with react + redux

I want to display quick flash animations on certain events (eg. a red border flash for each incorrect keystroke).
To do this with css animations, I need to remove and add the animation class each time I want to trigger the flash. (Unless there's another way to retrigger an animation?).
There are a few suggestions for doing this on this github thread: https://github.com/facebook/react/issues/7142
However, in my case the state that triggers the flash is the redux state. And in many cases the state hasn't actually changed, so it doesn't cause a rerender.
Here's the best solution I've got, which involves setting a random number to force a re-render. Is there a better way to do this?
reducer.js
//Reducer function to update redux state
function setError(state, action) {
state.hasError = true;
state.random = Math.random();
return state;
}
export default function allReducers(state = initialState, action) {
switch (action.type) {
case ActionTypes.SUBMIT_VALUE_BUTTON:
return Object.assign({}, state, setError(state, action));
default:
return state;
}
}
react component and container
const mapStateToProps = (state, ownProps) => {
return {
random: state.random,
hasError: state.hasError,
}
}
componentWillReceiveProps() {
this.setState({hasError: this.props.hasError});
setTimeout(() => {
this.setState({hasError: false});
}, 300)
}
render() {
return <div className = {`my-component ${this.state.hasError ? 'has-error':''}`} />;
}
Edit: It's worth noting that the redux documentation says that you shouldn't call non-pure functions like Math.random in a reducer method.
Things you should never do inside a reducer:
Call non-pure functions, e.g. Date.now() or Math.random().
Your code has a few problems in it, I'll go one by one...
You can't mutate the state object on the reducer. Here it is from the redux docs:
Note that:
We don't mutate the state. We create a copy with Object.assign().
Object.assign(state, { visibilityFilter: action.filter }) is also
wrong: it will mutate the first argument. You must supply an empty
object as the first parameter. You can also enable the object spread
operator proposal to write { ...state, ...newState } instead.
In your code setError receives the state as a prop and mutates it. setError should look like this:
function setError(state, action) {
let newState = Object.assign({}, state);
newState.hasError = true;
newState.random = Math.random();
return newState;
}
The second problem might be because there's some code missing but I cant see when your'e changing your state back to no errors so the props doesnt really change.
In your componentWillReceiveProps your referencing this.props instead of nextProps.
componentWillReceiveProps should look like this:
componentWillReceiveProps(nextProps) {
if (nextProps.hasError !== this.props.hasError && nextProps.hasError){
setTimeout(() => {
// Dispatch redux action to clear errors
}, 300)
}
}
And in your component you should check for props and not state as getting props should cause rerender (unless the render is stopped in componentShouldUpdate):
render() {
return <div className={`my-component ${this.props.hasError ? 'has-error':''}`} />;
}

Categories