I have two states:
const [firstState, setFirstState] = useState(6.5)
const [secondState, setSecondState] = useState(3.5)
I wish to combine these two states into an object, like this:
const [myObject, setMyObject] = useState({
first: {firstState},
second: {secondState}
});
//NOTE: This does not compile when i try to create state-object with state values.
I then send the object to a child-component as a prop which is later used for display.
<ChildComponent objectToChild={myObject}/>
What's the best pratice to combine states into an object?
Their are different option
1. Using the useState Hook
const [myObject, setMyObject] = useState({
first: firstState,
second: secondState
});
modify state
setMyObject({...myObject, firstState })
2. Using the useReducer
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
const initialState = {
first: 'name',
second: 'surname'
};
const reducer = (state, action) => {
switch (action) {
case 'first': return { ...state, first: state.first };;
case 'second': return { ...state, second: state.second };;
default: throw new Error('Unexpected action');
}
};
use it like
const [state, dispatch] = useReducer(reducer, initialState);
You can simply create an object as an argument in useState method, so It would be like this:
const [user, setUser] = useState({});
and call setUser method like this:
setUser(prevState => {
return {...prevState, [user.firstState]: 6.5}
}
Read more about useState hook here:
https://pl.reactjs.org/docs/hooks-reference.html#usestate
Related
So I'm using React Native with Redux Toolkit and I'm facing this issue where once I dispatch and decrement a store value, the variable inside the component that grabs the state from the store does not update until the next render. It would be nice to know what the current value is at all times. I was thinking something like useEffect(), but I'm not sure how I would set that up. Any ideas would be appreciated.
Here is the component where the dispatch happens(inside handlePress function) and also where the redux state variable "exercises" I grab is found.
const LoggingButton = ({ disabled, maxReps, setIndex, exerciseIndex }) => {
const dispatch = useDispatch()
if (!maxReps)
maxReps = 5
const [count, setCount] = useState(maxReps)
const [active, setActive] = useState(false)
const { exercises } = useSelector((state) => state.workout);
const handlePress = () => {
if (!active)
setActive(true)
else {
dispatch(decrement({exerciseIndex:exerciseIndex,setIndex:setIndex}))
}
console.log("pressed",exercises[exerciseIndex].sets)
}
if (disabled)
return (
<Button
disabled round style={styles.disabledState}
iconSource={() => (<FontAwesomeIcon icon={faTimes} color={'#282c34'} size={30} />)}
/>)
else
return (
<Button
label={(count != 0) ? count : '0'} round
style={(active) ? styles.loggingState : styles.initialState} labelStyle={(active) ? styles.loggingLabel : styles.initialLabel}
onPress={handlePress}
/>)
}
Here is the workout state if it helps:
const currentWorkoutSlice = createSlice({
name: "currentWorkout",
initialState: {
id: "A",
exercises: [
],
timeStarted: '',
},
reducers: {
setWorkout(state, action) {
state.id = action.payload
},
setCurrentExercises(state, action) {
state.exercises = action.payload
},
setTimeStarted(state, action) {
state.timeStarted = action.payload
},
decrement(state, action) {
let newList = state.exercises.map(e=>e)
const {exerciseIndex,setIndex} = action.payload
newList[exerciseIndex].sets[setIndex] -=1
}
}
})
The whole point of useSelector is to update your component as soon as that state changes so you always render the most up-to-date state contents.
"Until next render" is an incredibly short amount of time here and will often even happen synchonically. You should not really care about that.
Also, due to scoping those are two very different variables that just randomly have the same name - there is no way one could update the other.
I created a custom hook to store Objects in a useState hook and allow changing properties without loosing the other entries.
const useObject = initialValue => {
const [state, setState] = useState(initialValue);
return [
state,
newState => {
setState({
...state,
...newState
});
}
];
};
This hook works in my component but doesn't when I assign it to my context.
Here is what I did:
I created a context:
export const navigation = createContext();
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/store.js:40-83
I created a useObject variable and assigned it as value to my Context Provider
<navigation.Provider value={useObject()}>
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/Layout.js:234-284
I load the context via useContext and change its value
const [navigationState, setNavigationState] = useContext(navigation);
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/App.js:476-616
Result:
The context always stores the new entry and removes all existing entries.
Anyone knows why ?
Here is the Sandbox link. You can test it by clicking the filter button. I expected to see {search:true, icon: 'times'} as context value. Thx!
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/App.js
There is one important things to note here. useEffect in App.js is run once and hence the onClick function set with setNavigationState will use the values from its closure at the point at which it is defined i.e initial render.
Due to this, when you call the function within Header.js from context's the value along with the localState are being reset to the initial value.
SOLUTION 1:
One solution here is to use callback approach to state update. For that you need to modify your implementation on useObject a bit to provide the use the capability to use the callback value from setState
const useObject = initialValue => {
const [state, setState] = useState(initialValue);
return [
state,
newState => {
if(typeof newState === 'function') {
setState((prev) => ({ ...prev, ...newState(prev)}));
} else {
setState({
...state,
...newState
});
}
}
];
};
and then use it in onContextClick function like
const onContextClick = () => {
setState(prevState => {
setNavigationState(prev => ({ icon: ICON[prevState.isOpen ? 0 : 1] }));
return { isOpen: !prevState.isOpen };
});
};
Working DEMO
SOLUTION 2:
The other simpler approach to solving the problem is to use useCallback for onContextClick and update the navigation state with useEffect, everytime the closure state is updated like
const onContextClick = React.useCallback(() => {
setNavigationState({ icon: ICON[state.isOpen ? 0 : 1] });
setState({ isOpen: !state.isOpen });
}, [state]);
useEffect(() => {
setNavigationState({
search: true,
icon: ICON[0],
onClick: onContextClick
});
}, [onContextClick]);
Working demo
I'm pretty new to React and React hooks in general,
I'm building a react app for my final project and I wanted to make some component (Advanced search in this example) as generalized as possible which means I want to pass "dataFields" and the component should be updated with a unique state value that originated from those data fields.
I know that I can use a general state and store changes in it with an array but I read that it's bad practice.
this is what I have now:
const [title,updateTitle] = useState({"enable":false,"value": "" });
const [tags,updateTags] = useState({"enable":false,"value": "" });
const [owner,updateOwner] = useState({"enable":false,"value": "" });
const [desc,updateDesc] = useState({"enable":false,"value": "" });
And I try to use this to achieve the same thing:
if(props?.dataFields) {
Object.entries(props.dataFields).forEach ( ([key,value]) => {
// declare state fields
const [key,value] = useState(value)
});
}
what is the proper way of doing it? is there is one?
Do 4 lines of useState or useReducer (local)
I would suggest someting like this for the initial state
const setItem = (enable = false, value = '') => ({ enable, value });
const [title, updateTitle] = useState(setItem());
const [tags, updateTags] = useState(setItem());
const [owner, updateOwner] = useState(setItem());
const [desc, updateDesc] = useState(setItem());
And you also can useReducer and define the initial state.
I add an example for useReducer and case dor change title.value
import React from 'react';
import { useReducer } from 'react';
const setItem = (enable = false, value = '') => ({ enable, value });
const initialState = { title: setItem(), tags: setItem(), owner: setItem(), desc: setItem() };
function reducer(state, action) {
switch (action.type) {
case 'CHANGE_TITLE':
return { ...state, title: setItem(null, action.payload) };
default:
return state;
}
}
function MyFirstUseReducer() {
const [state, dispatch] = useReducer(reducer, initialState);
const updateTitle = ev => {
if (ev.which !== 13 || ev.target.value === '') return;
dispatch({ type: 'CHANGE_TITLE', payload: ev.target.value });
ev.target.value = '';
};
return (
<>
<h2>Using Reducer</h2>
<input type="text" onKeyUp={updateTitle} placeholder="Change Title" />
<div>
<span>The State Title is: <strong>{state.title.value}</strong></span>
</div>
</>
);
}
export default MyFirstUseReducer;
I have something like this
const [state, setState] = useState({
num: 0
});
const validateBiggerThan0 = () => {
return state.num > 0;
};
const [validation, setValidation] = useState({
val: validateBiggerThan0
});
The problem is when I call validation.validateBiggerThan0, this function does not have the current state.
In this simple example, I could pass the value sate.num as a parameter to the validateBiggerThan0, but in my real application this is not feasible because I have multiple validations and some can use more than one value from the state. Is there any alternative to this?
A sandbox with the problem:
https://codesandbox.io/s/heuristic-mountain-2poz7?fontsize=14&hidenavigation=1&theme=dark
The solution to your problem is to not use useState for the function object.
const validation = {
val: validateBiggerThan0
};
Or to update the object on every change of your state:
useEffect(() => {
setValidation({
val: validateBiggerThan0
});
}, [ state, setValidation, validateBiggerThan0 ]);
// Don't remove "state" from the dependency array.
Don't forget to wrap your validation functions in a useCallback hook in case you use the useEffect method.
const validateBiggerThan0 = useCallback(() => {
return state.num > 0;
}, [state]);
Imagine a scenario that there's a same operation through different values (For instance generating a custom html element for different input values); in the case of using a component class, I would mocked this as follow:
const onFuncClicked = property => newVal => {
this.setState({ [property]: newVal })
}
but what if i use react hooks:
const onFuncClicked = property => newVal => {
eval(`set${property}(${newVal})`);
}
not only using eval is not recommended for thousands of reasons, but this code does not work at all!! It generates the correct useState function but the component does not know it even and gives a ReferenceError that that function (generated useState) is not defined
One way to approach this is to use a map of methods to the property names:
const [property, setProperty] = useState(defaultValue);
const methodMap = { propetryName: setPropertyName, /* ... more fields */ };
Then you can call it from your set method like so:
const onClicked = (property, value) => {
methodMap[property](value);
}
Another option, and probably more common one, would be to have state as an object with all your properties and change them by prop name:
const [state, setState] = useState({property: value});
const onClicked = (property, value) => {
setState(state => {...state, [property]: value});
}
well, if you are already using hooks and useState and did end up with a bunch of them that make your code look too complex I suggest using useReducer, here is a sample from official documentation:
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}```