useReducer/useState updates too slow - javascript

I'm trying to remove redux of our application. To do so I try to use the new React Hooks API.
I wrote my own hook which is an extension of the useReducer function, to persist the state each time its updating.
export default (reducer, persistKey, onRehydrate) => {
let initialState = rehydrate(persistKey)
if (onRehydrate && typeof onRehydrate === 'function') {
initialState = onRehydrate(initialState)
}
const [state, setState] = useState(initialState)
function dispatch (action) {
const nextState = reducer(state, action)
setState(nextState)
persist(nextState, persistKey)
}
return [state, dispatch]
}
My problem is that I have use case where my dispatch function is called several times within a short interval which are relaying on each other. Like an array which always gets a new item added on each dispatch. To do so I merge the last state and the new state. Since it's getting called before the state has updated the last state isn't the new state of the last call. Therefore my state only saves the last dispatch.
Is there any way to solve that (besides changing the way the updates are called since they are from a thirdparty component)
Thanks in advance.

I can't verify whether this works, but I suspect it will. You could change your setState call within dispatch to the callback form, which allows you to reference the existing state before passing it into your reducer.
export default (reducer, persistKey, onRehydrate) => {
let initialState = rehydrate(persistKey)
if (onRehydrate && typeof onRehydrate === 'function') {
initialState = onRehydrate(initialState)
}
const [state, setState] = useState(initialState)
function dispatch (action) {
setState(prevState => { // Use callback form here.
const nextState = reducer(prevState, action)
persist(nextState, persistKey)
return nextState
});
}
return [state, dispatch]
}

Related

Does `localStorage.setItem` inside a `setState` fonction affect performance?

In React setState can take a callback fonction that return new state and take previousState as argument.
To avoid using a useCallback I try to not use the previous state inside a function.
const [state, setState] = useState();
const setValue = (callback) => {
setState(prev => {
const newValue = callback(prev);
localStorage.setItem("demo", JSON.stringify(newValue))
return newValue;
})
}
I know I can use useEffect instead, but I just want to know if this code is ok without useEffect

useEffect rewrites the entire store

I have a redux store with 3 reducers:
let reducers = combineReducers({
config: configReducer,
data: dataReducer,
currentState: gameStateRecuder})
let store = createStore(reducers, applyMiddleware(thunkMiddleware));
In each of those reducers the initial store is empty, but once the App component mounts I use useEffect to replace each initial store inside a reducer with the one I receive with axios.get using redux-thunk. It looks like this in every reducer:
let initialState = [];
const SET_STATE = 'SET_STATE';
const configReducer = (state = initialState, action) => {
switch (action.type) {
case SET_STATE: {
return { ...action.state};
}
default:
return state;
}
const setState = (state) => ({ type: SET_STATE, state });
export const getConfigState = () => (dispatch) => {
getAPI.getConfig() //I import getAPI with all the REST API logic//
.then(response => {
dispatch(setState(response));
})
};
And the App trigger is:
const App = (props) => {
useEffect(() => {
props.getConfigState();
props.getDataState();
props.getGameState();
}, []);
return (
//JSX//
);
}
export default compose(connect(null, { getConfigState, getDataState, getGameState }))(App);
However, when the App mounts, I have this mess:
In the end, I get the state of each reducer replaced with the state of the one whose promise resolved the last one. I can try to wrap the app 2 more times with a HOC that does nothing but re-writes a state of the precise reducer, but I would still like to understand what causes a promise to affect other reducers besides the one he needs to effect.
A silly mistake, but maybe someone has the exact same problem - the solution is to give different case names for each reducer - SET_STATE need to become SET_GAME_STATE, SET_CONFIG_STATE, SET_DATA_STATE respectivly. I believe that's because of my misunderstanding on how the dispatch works.

Access the latest state from external function React JS

I have a simple React component (Hooks):
// inside React component
import { someExternalFunction } from "functions"
const [value, setValue] = useState(0)
const handleChange = () => {
someExternalFunction(value, setValue)
}
// outside of React component
const someExternalFunction = (value, setValue) => {
console.log(value) // 0
// testing "set" method
setValue(100) // working
// "set" is async, so lets wait
// testing "get" method
setTimeout(() => console.log(value), 5000) // 0
// not working
}
PROBLEM: "value"/state is always the same, it is captured at the moment when state is passed to a function.
QUESTION: How to access the latest state in external function?
CLARIFICATION: Of course it is not working with the "value"/state, I just tried to illustrate the problem that I'm facing with preferred method for providing the latest state to the function (trouh the direct reference) .
Value is primitive type, not reference type, it was pass into function as a copied value, of cause it is never changed. And setTimeout always access a copied value.
As long as you don't trigger "handleChange" again, the function won't use the new value. Your state is a number so it will not pass a reference but rather the value itself (in this case 0). When you call setValue, the state is updated but your timeOut callback still uses the stale value that was passed when you triggered "handleChange".
If you want to react to a state change a useEffect hook would be better.
You can also simply put a console.log(value) below the line where you define the state, to check if it changes.
// Update:
const [value, setValue] = React.useState(0);
const handleChange = () => {
setValue(100);
}
React.useEffect(() => console.log(value), [value]);
Or if you want to move that logic to a reusable hook:
// useExternalFunction.js
export useExternalFunction = () => {
const [value, setValue] = React.useState(0);
const handleChange = () => {
setValue(100);
}
React.useEffect(() => console.log(value), [value]);
return {value, handleChange};
}
// component
const {value, handleChange} = useExternalFunction();
return <button onClick={handleChange}>{value}</button>;

What is the best or most efficient method for removing keys when setting state via React's useState hook?

Our project is embracing the new functional React components and making heavy use of the various hooks, including useState.
Unlike a React Class's setState() method, the setter returned by useState() fully replaces the state instead of merging.
When the state is a map and I need to remove a key I clone the existing state, delete the key, then set the new state (as shown below)
[errors, setErrors] = useState({})
...
const onChange = (id, validate) => {
const result = validate(val);
if (!result.valid) {
setErrors({
...errors,
[fieldId]: result.message
})
}
else {
const newErrors = {...errors};
delete newErrors[id];
setErrors(newErrors);
}
Is there a better alternative (better being more efficient and/or standard)?
If you need more control when setting a state via hooks, look at the useReducer hook.
This hook behaves like a reducer in redux - a function that receives the current state, and an action, and transforms the current state according to the action to create a new state.
Example (not tested):
const reducer = (state, { type, payload }) => {
switch(type) {
case 'addError':
return { ...state, ...payload };
case 'removeError':
const { [payload.id]: _, ...newState };
return newState;
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, {});
...
const onChange = (id, validate) => {
const result = validate(val);
if (!result.valid) {
dispatch({ type: 'addError', payload: { [id]: result.message }})
}
else {
dispatch({ type: 'removeError', payload: id })
}

Redux-Thunk getStore() doesn't retain state. Returning 'undefined'

This might be a question of best practices but I'd appreciate an explanation on why this doesn't work. I'm using Typescript + Redux + Thunk and trying to call actions like this:
export const requestUserDashboards = createAction<DashboardModel>(Type.REQUEST_USER_DASHBOARDS);
Dispatch in the fetch:
export const fetchDashboards = () => {
return async (dispatch: Dispatch, getState: any) => {
try {
dispatch(requestUserDashboards({
currentDashboard: getState.currentDashboard,
dashboards: getState.dashboards,
hasDashboards: false,
error: getState.error
}))
...
}
})
}
Here's the corresponding reducer:
export const dashboardReducer = handleActions<RootState.DashboardState, DashboardModel>(
{
[DashboardActions.Type.REQUEST_USER_DASHBOARDS]: (state = initialState, action): RootState.DashboardState => ({
currentDashboard: action.payload!.currentDashboard,
dashboards: action.payload!.dashboards,
hasDashboards: action.payload!.hasDashboards,
error: action.payload!.error
})
},
initialState
);
dispatch is working, however, getState doesn't correctly collect the current store state. I'm testing this by doing the following in the component receiving the updated store:
componentWillReceiveProps(nextProps: Login.Props) {
console.log(nextProps.defaultAccounts.defaultAccount);
}
Calling this in the component using:
this.props.defaultAccountActions.fetchUserDefaultAccount();
The action is working as the values from the fetch are being captured.
However, where I am using the getState.xxxx, these values are returning as undefined:
index.tsx:84 Uncaught TypeError: Cannot read property 'defaultAccount' of undefined
The initialState from my reducer is working. I can see this from doing the console.log(this.props.defaultAccounts.defaultAccount) from the componentWillMount() function.
I'm not sure what else I can provide. I think I'm actually just fundamentally misunderstanding how actions/reducers manage the store.
Questions
I am trying to get the current store values by using the getState.xxxx in the dispatch. Is this the correct way to do this?
isn't getState a function in that place? So you would need to do something
const state = getState();
and then use state inside dispatch
found in documentation, yeah it is a function at that place so you should firstly invoke a function to get state and then use it (e.g. from documentation below)
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
If you are using mapstatetoprops in your component you can use that to get the values from store. mapStateToProps first argument is actually the Redux state. It is practically an abstracted getState().
const mapStateToProps = function(state, ownProps) {
// state is equivalent to store.getState()
// you then get the slice of data you need from the redux store
// and pass it as props to your component
return {
someData: state.someData
}
}

Categories