Hello i'm trying to create some kind of lottery and i'm wondering which approach of modifying state by actions payload should be used.
Let's say i have state
type initialCartState = {
productsFromPreviousSession: Product[]
selectedProduct: Product
balance: number,
productsInCart: Product[]
}
and our reducer looks like
const reducers = {
addProduct(state, action) => {
state.products.push(state.action.payload.product)
},
addProductsFromPreviousSession(state, action) => {
state.products.push(...state.productsFromPreviousSession)
},
}
And i noticed i used completely two different approaches with these two types cuz in my component it looks like
const component = () => {
const selectedProduct = useSelector(state => state.cart.selectedProduct);
const availableBalance = useSelector(state => state.cart.balance - sum(state.cart.products, 'price'));
const dispatch = useDispatch()
const sumOfProductsFromPreviousSession = useSelector(state => sum(state.cart.products,'price'))
return (
<div>
<div onClick={() => {
if((balance - selectedProduct.price) > 0) {
dispatch(cartActions.addProduct(selectedProduct))
}
}}/>
<div onClick={() => {
if((balance - sumOfProductsFromPreviousSession) > 0) {
dispatch(cartActions. addProductsFromPreviousSession())
}
}}/>
</div>
)
}
There are two different types of handling actions, in addProduct i used selector and pass value in action payload. In Add products from previous session we rely on state inside reducer (Also have middleware for purpose of saving in localStorage, but there i used store.getState()). Which kind of approach is correct ?
Also how it will change when we move balance to another reducer, and then we will not have access to that i cartReducer?
I saw there are bunch of examples on counter when increment and decrement rely on current reducerState and there are actions without payload, but there is no validation which is used in my example.
Thanks in advance !
Both approaches can be used. Basically, if you need to show state data in UI or other parts of processes, you should read with selector. This way, changes inside the store can be reflected in the components reactively.
In your case, you are just updating the state value with currently available data from the state. So, you can dispatch action without payload.
In your example, even though you pass the payload from onClick event, you are still reading the value from the state itself.
I have a reducer function whose sole purpose it to toggle a style on and off. This is a global style and this is why it is in my Redux store.
However, the code looks overly obtuse for such a simple toggle.
const Style = (state = {current: true}, action) => {
if(action.type === "toggleAppStyle"){
const newState = { ...state };
newState.current = !state.current;
return newState;
}
return state;
};
I recently realized that redux runs all reducer functions for each single action, which I find strange, so the returned state must equal the initial state if the action.type is not called for that particular reducer.
Here is the one place I use it:
// ... snip
const mapStateToProps = state => {
return {
Style: state.Style
}
}
// ... snip
render() {
return (
<img className = 'icon_all'
id = {this.props.Style.current === true ? 'icon_10' : 'icon_90'}
onClick = {this.clickHandler} src='/images/favicon-optimized.svg'/>
)
}
the code looks overly obtuse for such a simple toggle.
Overly obtuse is a bit much, this is a pretty standard immutable update pattern, i.e. shallow copy into new object reference and update the necessary properties.
It can't get much simpler than what you've already got, but here is an example returning the new object directly.
const Style = (state = {current: true}, action) => {
if (action.type === "toggleAppStyle") {
return {
...state,
current: !state.current,
};
}
return state;
};
I recently realized that redux runs all reducer functions for each
single action, which I find strange, so the returned state must equal
the initial state if the action.type is not called for that particular
reducer.
Nothing strange here really. The reducer function either acts on the action and operates on the state, thus returning a new state reference and value, otherwise it simply returns the current state value. Other than the action part this is exactly how React reconciliation works. When you update state you are creating new object references or primitive values. This is what triggers rerenders.
Of course, if your objective is to be as short and succinct as possible, you could reduce the state slice to just the boolean value and use a ternary operator to return the toggled state or current state value. Adjust your redux selectors accordingly.
const Style = (state = true, action) => action.type === "toggleAppStyle"
? !state
: state;
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. :)
I have a React Functional Component that behaves in this way:
const Child = ({objValues, number}) => {
const emptyState = {
a: { b: "", c:""}
d: { e: "", f:""}
}
const initialState = {...emptyState, ...objValues}
const [state, dispatch] = useReducer(reducer, {...initialState})
return (
// render something based on state's nested objects.
// and the number prop.
)
}
const Parent = () => {
[objValues, setObjValues] = useState({});
[number, setNumber] = useState("");
//... some network requests to populate values
// and change number (using setState);
useEffect(() => {
// fetchAPI and then set values using spread operator
// to force creating a new object.
setObjValues({...values})
}, [number])
return ( <Child objValues={objValues} number={number}/>
I always get unique items (number, objValues) from the API I am using. Inside the component, I see that whenever I fire a new network request, the value of number gets updated in the UI, but the objValues shows the same values as the previous object. In the Component tabs in the react dev tools, I see that these values do update, nevertheless the UI stays the same.
const Child = ({objValues, number}) => {
const emptyState = {
a: { b: "", c:""}
d: { e: "", f:""}
}
const initialState = {...emptyState, ...objValues}
const [state, dispatch] = useReducer(reducer, {...initialState})
useEffect(()=>{
dispatch({
...objValues
})
},[objValues])
return (
// render something based on state's nested objects.
// and the number prop.
)
}
Couple of things
do not use the spread everywhere, dont see a reason for any of them in your code, if you pass object to setState it will take the current value and put it to state as value not reference so no reason to "force"
try typescript or try to not mix types, in your numer state you have default value a string, either have it empty, null or something that is close to a number
To your problem: since the reducer will resolve data, I dont think it has to update based on change in its initial data, what you should do, is ditch this populating some data and mixing them with redux data, but make the api request and once its done, save the data to redux and your reducer in another component will update itself
EDIT: Iam dumb its not useSelector from redux, regardless initial data got to be used only once, and they dont force the hook to update its value, basicaly any solid library for react or react itself, will look at initial data only when its called the first time, and then it doesnt matter how many times or how hard you change the initial data, it shouldnt affect it (most component or form libraries do follow this rule), basically in this case I would do useeffect and I would update the reducer with the data, or if that is not possible declare new constant, where you spread your default value and then spread your reducer data
say I have a reducer like this:
export const fetchStuff = (state, action) => {
const s = state || someDefaultState
switch (action.type) {
case FETCH_STUFF:
return {...s, isFetching: true}
case SET_STUFF:
return {...s, isFetching: false, stuff: action.values}
default:
return s
}
}
In this case if actions.values has objects that are null they will be very hard to deal with in my components because I will manually have to ensure that the component is not passed a null prop and then also manually have to deal with null fields in the component somewhere.
const component = ({ prop }) => {
return {
<div>
<span>{prop.testnull ? '' : prop.testnull}</span>
<div
}
}
const mapStateToProps = (state) => {
const p = prop || someDefaultProp
return {
prop: state.prop
}
}
It might seem easy enough in this example, but I have found it a pain to manage a bigger component/component-set. What is the idiomatic way of doing this? DO I have to bite the bullet and manage it in the component and mapStateToProps? or is there a better way to manage it in the reducer?
EDIT:
I should clarify that I am not trying to take care of the case where state is null I am trying to take care of the case where , on fetching of state, some attribute if state is set to null
We have worked with a few similar solutions for a while, and found this to be a nice way of handling this use case in our reducers. Lodash's _.get is a nice solution to provide a default value for multi-level get in your reducer:
https://lodash.com/docs/4.17.4#get
_.get(object, path, [defaultValue])
object (Object): The object to query.
path (Array|string): The path of the property to get.
[defaultValue] (*): The value returned for undefined resolved values.
For example:
const testValue = get(action, 'values.test', {});