I cannot toggle my boolean value in Redux - javascript

I am working on an anime project. My problem is that when I click the input, I want to bring it to the forefront. So, I used "webkitRequestFullscreen()", but I cannot toggle my boolean to true in order to make it. I looked for some solutions, but I guess, the version I use for Redux is different from theirs and I didn't understand them.
Here is my initial state and reducer function:
const initialState = {
items: [],
fullScreen: false,
}
reducers: {
toggleInputScreen: (state, action) => {
const id = action.payload
state.fullScreen = !state.fullScreen
},
},
The main component:
let activeFullScreen = useSelector((state) => state.anime.fullScreen)
// console.log(activeFullScreen)
if (activeFullScreen) {
return inputRef.current.webkitRequestFullscreen()
}
<div
className="input-container"
onClick={() => dispatch(toggleInputScreen())}
id="input-div">
<input type="search" placeholder="Search..." ref={inputRef} />
</div>
I know the reducer function is a total disaster. How can I toggle the fullScreen boolean when I click the input? Thanks in advance.

The problem is probably the reducer function.
In Redux, the state is immutable, which means you never modify the state, instead, create a new copy. Another mistake is, that you have to return the new state in the reducer function.
Reducers are functions that take the current state and an action as arguments, and return a new state result. [...] They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values. - redux guide
You can solve your problem by following the just appointed rules:
toggleInputScreen: (state, action) => {
const id = action.payload
//create and return a new object, which is the new state
return {
items: state.items,
fullScreen: !state.fullScreen,
}
}
If the state is an object and grows in size, you can use the Object.assign() method to create a copy of the object and change values of it easily. Check it out here and here. For redux you would use the following schema: Object.assign({}, state, changes). In you case, that would look like this:
toggleInputScreen: (state, action) => {
const id = action.payload
//create and return a new object, which is the new state
return Object.assign({}, state, { fullscreen: !state.fullscreen });
}

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

In Redux, why do we set the initialState?

According to the Redux documentation, it seems to be a standard practice to set an initialState on your reducer. However this initialState needs to be maintained and if the state is being populated based on an API response, then you may have the initial state out of sync with the API response. This is especially true in cases where the state is made up of nested objects.
Is it to avoid null-checking (sometimes the initial state is set to null), are there any performance benefits? Does it improve code readability?
Taken from Redux docs:
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
}
Then in our reducer we may have an action which replaces the value in the state (for example based on an API response). Such as:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_TODOS:
return Object.assign({}, state, {
todos: action.todos
})
default:
return state
}
}
However the same behaviour may be achieved without using an initialState, by checking the state in the component (or selector).
const MyComponent = ({todos}) => {
if (!todos) { // if we do not have an initialState, todos will be undefined if SET_TODOS hasn't been called
return null;
}
return <div>{todos.map(n => ...)}</div>
}
If the API returns a new property (notes), we would need to update as follows:
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: [],
notes: [] // <-----
}
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_TODOS:
return Object.assign({}, state, {
todos: action.todos,
})
case SET_NOTES:
return Object.assign({}, state, {
notes: action.notes,
})
default:
return state
}
}
This is further complicated in cases when you have nested objects. If the todos has a child property subtasks: [], why are we not setting an initial state for it?
I think it's more of a convenience than anything else. I highly doubt there is any performance implications. Also while in my personal opinion it is cleaner to add all the properties you expect upfront, you are not really required to add it to the initial state. You can simply add it when returning the new state, obviously if you're using typescript this is a different story.
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
// notes: [] you don't necessarily need to add it to the initial state
}
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_TODOS:
return Object.assign({}, state, {
todos: action.todos,
notes: action.notes // <-----
})
default:
return state
}
}
Having an initial state helps to have "cleaner" code. Like Brian Thompson said in the comments, code is more predictable if your data structure stays consistent.
It is also recommended (by some ppl) to avoid having multiples returns in one function. It may not be the best perf-wise, but it might be the easiest to read.
So having to do an early return in your component might not be the cleanest way to achieve the behavior your want.
That being said, if your implementation works, why not use it ? Well i think it's best when working as a team to stick to the conventions as much as possible.

Updating and merging state object using React useState() hook

I'm finding these two pieces of the React Hooks docs a little confusing. Which one is the best practice for updating a state object using the state hook?
Imagine a want to make the following state update:
INITIAL_STATE = {
propA: true,
propB: true
}
stateAfter = {
propA: true,
propB: false // Changing this property
}
OPTION 1
From the Using the React Hook article, we get that this is possible:
const [count, setCount] = useState(0);
setCount(count + 1);
So I could do:
const [myState, setMyState] = useState(INITIAL_STATE);
And then:
setMyState({
...myState,
propB: false
});
OPTION 2
And from the Hooks Reference we get that:
Unlike the setState method found in class components, useState does
not automatically merge update objects. You can replicate this
behavior by combining the function updater form with object spread
syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
As far as I know, both works. So, what is the difference? Which one is the best practice? Should I use pass the function (OPTION 2) to access the previous state, or should I simply access the current state with spread syntax (OPTION 1)?
Both options are valid, but just as with setState in a class component you need to be careful when updating state derived from something that already is in state.
If you e.g. update a count twice in a row, it will not work as expected if you don't use the function version of updating the state.
const { useState } = React;
function App() {
const [count, setCount] = useState(0);
function brokenIncrement() {
setCount(count + 1);
setCount(count + 1);
}
function increment() {
setCount(count => count + 1);
setCount(count => count + 1);
}
return (
<div>
<div>{count}</div>
<button onClick={brokenIncrement}>Broken increment</button>
<button onClick={increment}>Increment</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
If anyone is searching for useState() hooks update for object
Through Input
const [state, setState] = useState({ fName: "", lName: "" });
const handleChange = e => {
const { name, value } = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};
<input
value={state.fName}
type="text"
onChange={handleChange}
name="fName"
/>
<input
value={state.lName}
type="text"
onChange={handleChange}
name="lName"
/>
Through onSubmit or button click
setState(prevState => ({
...prevState,
fName: 'your updated value here'
}));
The best practice is to use separate calls:
const [a, setA] = useState(true);
const [b, setB] = useState(true);
Option 1 might lead to more bugs because such code often end up inside a closure which has an outdated value of myState.
Option 2 should be used when the new state is based on the old one:
setCount(count => count + 1);
For complex state structure consider using useReducer
For complex structures that share some shape and logic you can create a custom hook:
function useField(defaultValue) {
const [value, setValue] = useState(defaultValue);
const [dirty, setDirty] = useState(false);
const [touched, setTouched] = useState(false);
function handleChange(e) {
setValue(e.target.value);
setTouched(true);
}
return {
value, setValue,
dirty, setDirty,
touched, setTouched,
handleChange
}
}
function MyComponent() {
const username = useField('some username');
const email = useField('some#mail.com');
return <input name="username" value={username.value} onChange={username.handleChange}/>;
}
Which one is the best practice for updating a state object using the state hook?
They are both valid as other answers have pointed out.
what is the difference?
It seems like the confusion is due to "Unlike the setState method found in class components, useState does not automatically merge update objects", especially the "merge" part.
Let's compare this.setState & useState
class SetStateApp extends React.Component {
state = {
propA: true,
propB: true
};
toggle = e => {
const { name } = e.target;
this.setState(
prevState => ({
[name]: !prevState[name]
}),
() => console.log(`this.state`, this.state)
);
};
...
}
function HooksApp() {
const INITIAL_STATE = { propA: true, propB: true };
const [myState, setMyState] = React.useState(INITIAL_STATE);
const { propA, propB } = myState;
function toggle(e) {
const { name } = e.target;
setMyState({ [name]: !myState[name] });
}
...
}
Both of them toggles propA/B in toggle handler.
And they both update just one prop passed as e.target.name.
Check out the difference it makes when you update just one property in setMyState.
Following demo shows that clicking on propA throws an error(which occurs setMyState only),
You can following along
Warning: A component is changing a controlled input of type checkbox to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
It's because when you click on propA checkbox, propB value is dropped and only propA value is toggled thus making propB's checked value as undefined making the checkbox uncontrolled.
And the this.setState updates only one property at a time but it merges other property thus the checkboxes stay controlled.
I dug thru the source code and the behavior is due to useState calling useReducer
Internally, useState calls useReducer, which returns whatever state a reducer returns.
https://github.com/facebook/react/blob/2b93d686e3/packages/react-reconciler/src/ReactFiberHooks.js#L1230
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
...
try {
return updateState(initialState);
} finally {
...
}
},
where updateState is the internal implementation for useReducer.
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
If you are familiar with Redux, you normally return a new object by spreading over previous state as you did in option 1.
setMyState({
...myState,
propB: false
});
So if you set just one property, other properties are not merged.
One or more options regarding state type can be suitable depending on your usecase
Generally you could follow the following rules to decide the sort of state that you want
First: Are the individual states related
If the individual state that you have in your application are related to one other then you can choose to group them together in an object. Else its better to keep them separate and use multiple useState so that when dealing with specific handlers you are only updating the relavant state property and are not concerned about the others
For instance, user properties such as name, email are related and you can group them together Whereas for maintaining multiple counters you can make use of multiple useState hooks
Second: Is the logic to update state complex and depends on the handler or user interaction
In the above case its better to make use of useReducer for state definition. Such kind of scenario is very common when you are trying to create for example and todo app where you want to update, create and delete elements on different interactions
Should I use pass the function (OPTION 2) to access the previous
state, or should I simply access the current state with spread syntax
(OPTION 1)?
state updates using hooks are also batched and hence whenever you want to update state based on previous one its better to use the callback pattern.
The callback pattern to update state also comes in handy when the setter doesn't receive updated value from enclosed closure due to it being defined only once. An example of such as case if the useEffect being called only on initial render when adds a listener that updates state on an event.
Both are perfectly fine for that use case. The functional argument that you pass to setState is only really useful when you want to conditionally set the state by diffing the previous state (I mean you can just do it with logic surrounding the call to setState but I think it looks cleaner in the function) or if you set state in a closure that doesn't have immediate access to the freshest version of previous state.
An example being something like an event listener that is only bound once (for whatever reason) on mount to the window. E.g.
useEffect(function() {
window.addEventListener("click", handleClick)
}, [])
function handleClick() {
setState(prevState => ({...prevState, new: true }))
}
If handleClick was only setting the state using option 1, it would look like setState({...prevState, new: true }). However, this would likely introduce a bug because prevState would only capture the state on initial render and not from any updates. The function argument passed to setState would always have access to the most recent iteration of your state.
Both options are valid but they do make a difference.
Use Option 1 (setCount(count + 1)) if
Property doesn't matter visually when it updates browser
Sacrifice refresh rate for performance
Updating input state based on event (ie event.target.value); if you use Option 2, it will set event to null due to performance reasons unless you have event.persist() - Refer to event pooling.
Use Option 2 (setCount(c => c + 1)) if
Property does matter when it updates on the browser
Sacrifice performance for better refresh rate
I noticed this issue when some Alerts with autoclose feature that should close sequentially closed in batches.
Note: I don't have stats proving the difference in performance but its based on a React conference on React 16 performance optimizations.
I find it very convenient to use useReducer hook for managing complex state, instead of useState. You initialize state and updating function like this:
const initialState = { name: "Bob", occupation: "builder" };
const [state, updateState] = useReducer(
(state, updates) => {...state, ...updates},
initialState
);
And then you're able to update your state by only passing partial updates:
updateState({ occupation: "postman" })
The solution I am going to propose is much simpler and easier to not mess up than the ones above, and has the same usage as the useState API.
Use the npm package use-merge-state (here). Add it to your dependencies, then, use it like:
const useMergeState = require("use-merge-state") // Import
const [state, setState] = useMergeState(initial_state, {merge: true}) // Declare
setState(new_state) // Just like you set a new state with 'useState'
Hope this helps everyone. :)

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':''}`} />;
}

How does reselect affect rendering of components

I don't really understand how does reselect reduces component's rendering. This is what I have without reselect:
const getListOfSomething = (state) => (
state.first.list[state.second.activeRecord]
);
const mapStateToProps = (state, ownProps) => {
console.log(state.first.list, state.second.activeRecord);
return {
...ownProps,
listOfSomething: getListOfSomething(state)
}
};
It compounds an element from some list based on some value. Render is called each time anything in the state changes, so for example my console.log outputs:
{}, ""
{}, ""
{}, ""
{}, "1"
{"filled", "1"}
because something is going on in the different part of store. Thus the component is rendered 5 times, 2 redundantly.
Using reselect however:
const getList = state => state.first.list;
const getActiveRecord = state => state.second.activeRecord;
const listOfSomething = (list, activeRecord) => {
console.log(list, activeRecord);
return list[activeRecord];
}
const getListOfSomething = createSelector(getList, getActiveRecord, listOfSomething);
const mapStateToProps = (state, ownProps) => {
console.log(state.first.list, state.second.activeRecord);
return {
...ownProps,
listOfSomething: getListOfSomething(state)
}
};
Here my first selector console.log outputs:
{}, ""
{}, "1"
{"filled", "1"}
The second:
{}, ""
{}, ""
{}, ""
{}, "1"
{"filled", "1"}
And the component is rendered properly - 3 times !
Why is that so? Why is the component rendered only 3 times? What's exectly going on here?
React-Redux's connect function relies on shallow-equality comparisons. Each time the store updates and a component's mapState function runs, that connected component checks to see if the contents of the returned object changed. If mapState returned something different, then the wrapped component must need to re-render.
Reselect uses "memoization", which means it saves a copy of the last inputs and outputs, and if it sees the same inputs twice in a row, it returns the last output rather than recalculating things. So, a Reselect-based selector function will return the same object references if the inputs didn't change, which means that it's more likely that connect will see that nothing is different and the wrapped component won't re-render.
See the new Redux FAQ section on Immutable Data for more info on how immutability and comparisons work with Redux and React-Redux.

Categories