Updater function with useReducer? - javascript

In react, with useState, I can pass it an updater function to get the current state within the same function. Currently I update a state multiple times within the same function.
Is that possible with a reducer dispatch with useReducer?
Thanks.
const someFunc = () => {
// some other computation
const result = someOtherFunc();
setState((state)=>{
// do something with current state
return {...state, result}
})
const result2 = someOtherFunc2();
setState((state)=>{
// do something with current state
return {...state, result2}
})
}

Yes it is.
You can do this by grouping all your states in like an initialState variable:
import { useReducer, useEffect } from 'react';
const SampleComponent = () => {
const reducer = (state, action) => ({...state, ...action})
const initialState = {
someName: '',
someCounter: 0,
isLoading: false
}
const [{
someName, someCounter, isLoading
}, dispatch ] = useReducer(reducer, initialState )
useEffect(() => {
dispatch({ isLoading: true }
const res = await fetch('https://someapi')
const json = await res.json()
if (json) {
dispatch({
someName: json.someNameValue,
someCounter: someCounter + json.someCounterValue,
isLoading: false
})
}
}, [])
return (
<>
<h1>{someName}</h1>
<span>{someCounter}</span>
</>
)
}
There maybe other ways to do this, but the way works best for me is to:
Declare a reducer: const reducer = (state, action) => ({...state, action})
Declare all states group in an object state: const initialState = { someState: '' }
Use useReducer with the state variables and dispatch encapsulated with it: const [{ someState }, dispatch ] = useReducer(reducer, initialState)
Use dispatch() method to set new values of states: dispatch({ someState: newValue }) where newValue is any value you want to assign to someState.

Yes it is possible. Whatever that can be done with useState can also be done by useReducer. It is the context that makes us decide on which to use.
Suppose I just want to update my loading state, then useState is enough
const [isLoading, setIsLoading] = useState(true);
But let's suppose I have a input form,
if I decide to useState, then it will be kind of like this
const [name, setName] = useState('')
const [age, setAge] = useState(0);
const [address, setAddress] = useState('');
instead of using all these useState, I could directly using useReducer for this specific application
const initialFormValues = {
name: '',
age: 0,
address: '',
};
const reducer = (state, action) =>{
switch(action.type) {
case "NAME": return { ...state, name: action.value};
case "AGE": return { ...state, age: action.value};
case "ADDRESS": return { ...state, address: action.value};
}
}
const [formValues, dispatch] = useReducer(reducer, initialFormValues);
Now whenever you want to update your input fields in the form, just use dispatch.
Both can do the task. Depending upon the use-case, we can choose the more appropriate one.

Related

How to use just 1 useState() for serveral states

I have a react component that uses several states which are initialized in the same way useState(false), is there a way to combine all these states into a single useState(false)
const [loading, setLoading] = useState(false);
const [fields, setFields] = useState(false);
const [wrongImageType, setWrongImageType] = useState(false);
const [aboutError, setAboutError] = useState(false);
const [destinationError, setDestinationError] = useState(false)
You can do like this
const [states, setStates] = useState({ loading:false, fields:false, wrongImageType:false, aboutError:false, destinationError:false })
Update state like this
setStates((prevState) => ({ ...prevState, loading: true }))
I usually use the useReducer hook. That way, I can just slam properties to some state object and the reducer tidies it all up for me. It also makes it easy when I have to submit an object to an API, I just fling the entire state object to the server. So it's basically, "fetch data from the server, use form inputs to let the user update the values, fling the data back to the server when they click the save button."
import React, {
useReducer,
useEffect
} from "react";
const initialState = {
loading: false,
fields: false,
wrongImageType: false,
aboutError: false,
destinationError: false
};
const reducer = (prev, cur) => ({ ...prev, ...cur });
const MyComponent = () => {
const [state, setState] = useReducer(reducer, initialState);
useEffect(() => {
setState({
loading: true
});
// fetch something or do something...
setState({
loading: false
});
}, []);
return ({
state.loading ? <> Loading </> : <>{state.something}</>
});
}

Update State Provider from Function

I have this periodic useEffect hook:
const [state, dispatch] = useContext(NTTrackerContext);
useEffect(() => {
const interval = setInterval(() => {
dispatch({type: "REFRESH_APIS"}); // function that I'd like to use to update
console.log(state.raceapi.racedata)
}, 60000);
return () => clearInterval(interval);
}, [])
So, I have made a context provider, and I'd like to update it through the dispatch function. I want to use dispatch because I've written most of my code this way, but if there are better methods, please comment or add an answer.
In case you are wondering, yes, the function above is wrapped in NTTrackerContextProvider, code as provided below.
Context:
export const NTTrackerContext = React.createContext([{}]);
const initialState = {
user: {username: "", authenticated: false, email: "",},
raceapi: {
racedata: {'data': null}, racerlog: {}, racerdata: {}, teamdata: {},
}
};
const nttrackerReducer = (state, action) => {
switch (action.type) {
case 'LOGGED_IN': { // example of how I've written some parts of my reducer
let {user, raceapi, ...etc} = state;
let {userdata} = action;
return {
...etc, user: {...user, ...userdata,}, raceapi: {...raceapi},
}
}
default: return state;
}
};
const NTTrackerContextProvider = props => {
const initState = props.initState || initialState;
const [state, dispatch] = useReducer(nttrackerReducer, initState);
return (
<NTTrackerContext.Provider value={[state, dispatch]}>
{props.children}
</NTTrackerContext.Provider>
);
};
export default NTTrackerContextProvider;
The APIs that I want to refresh is stored in the raceapi object. racedata, racerlog, etc. all have their own individual URLs; for instance, http://127.0.0.1:8000/data/racedata_json/ for racedata.
I have my initial values fetched using fetch("http://127.0.0.1:8000/data/racedata_json/"), by this point, but I can't find a way to update the state using a function in my body functional component.
Can anyone please help? Thanks in advance.

Why my react app rendered twice and give me an empty array in first render when I'm trying fetching API?

The second render was success but the side effect is my child component's that using context value from its parent as an initialState (using useState hooks) set its initial state to empty array. I'm using React Hooks ( useState, useContext, useReducer, useEffect)
App.js:
...
export const MainContext = React.createContext();
const initialState = {
data: [],
};
const reducer = (state, action) => {
switch (action.type) {
case "ADD_LIST":
return { ...state, data: action.payload };
default:
return state;
}
};
function App() {
const [dataState, dispatch] = useReducer(reducer, initialState);
const fetchData = () => {
axios
.get("http://localhost:3005/data")
.then((response) => {
const allData = response.data;
if (allData !== null) {
dispatch({ type: "ADD_LIST", payload: allData });
}
})
.catch((error) => console.error(`Error: ${error}`));
};
useEffect(() => {
fetchData();
}, []);
console.log("useReducer", dataState); //LOG 1
return (
<div>
<MainContext.Provider
value={{ dataState: dataState, dataDispatch: dispatch }}
>
<MainPage />
</MainContext.Provider>
</div>
);
}
export default App;
My child component
MainPage.jsx
...
function MainPage() {
const mainContext = useContext(MainContext);
const data = mainContext.dataState.data;
const uniqueList = [...new Set(data.map((list) => list.type))];
console.log("uniqueList", uniqueList); // LOG 2
const [uniqueType, setUniqueType] = useState(uniqueList);
console.log("uniqueType State", uniqueType); // LOG 3
const catAlias = {
account: "Account",
commandLine: "Command",
note: "Note",
bookmark: "Bookmark",
};
return (
// jsx code, not necessary with the issue
)
};
The result :
result image
As you can see, uniqueType state is still empty eventhough the uniqueList is already with filled array. My goal is to make uniqueType initial state into the uniqueList from the first render.
That's the expected behavior. The value used for state initialization is only used in first render. In subsequent renders, the component needs to use useEffect to keep track of value changes.
UseEffect(()=>{
// unique list update.
}, [data]);

React useReducer don't update state in React context

React useReducer don't update state in React context. But in return section state data render correctly. Here is sample:
context.js
const globalContext = React.createContext();
const initialState = {
statuses: null,
};
const globalReducer = (state, action) => {
switch (action.type) {
case 'SET_STATUSES':
return { ...state, statuses: action.payload };
default:
return state;
}
};
export const GlobalState = ({ children }) => {
const [state, dispatch] = React.useReducer(globalReducer, initialState);
return <globalContext.Provider value={{ state, dispatch }}>{children}</globalContext.Provider>;
};
export const useGlobalState = () => {
const context = React.useContext(globalContext);
return context;
};
comeChild.js
const { state, dispatch } = useGlobalState();
const testFn = () => {
console.log(state); // -> {statuses: null} :here is issue
};
React.useEffect(() => {
console.log(state); // -> {statuses: null} :as expected
dispatch({ type: 'SET_STATUSES', payload: 'test str' });
console.log(state); // -> {statuses: null} :here is issue
testFn();
setTimeout(() => {
console.log(state); // -> {statuses: null} :here is issue
}, 3000);
}, []);
return <div>
{state.statuses && <div>{state.statuses}</div>}// -> 'test str'
</div>;
What could be the issue?
Im fairly new to contexts and the useReducer hook my self, but my guess is the good ol' state "logic" in React. React update states asynchronous and not synchronous which can result in these behaviours.
Your reducer and context obviously works, since it outputs the correct state in your return statement. This is because of your state.statuses && condition, indicating that you want to return the div WHEN state.statuses "exist" so to say.
So to me it doesn't look like any problem, just that React being React with the state updates.
You could console.log(action.payload) in your reducer to see when 'test str' enters the reducer action.

Unable to read state updated by useReducer hook in context provider

I am using useReducer hook to manage my state, but it seems like I have a problem with reading updated state in my context provider.
My context provider is responsible to fetch some remote data and update the state based on responses:
import React, { useEffect } from 'react';
import useAppState from './useAppState';
export const AppContext = React.createContext();
const AppContextProvider = props => {
const [state, dispatch] = useAppState();
const initialFunction = () => {
fetch('/some_path')
.then(res => {
dispatch({ type: 'UPDATE_STATE', res });
});
};
const otherFunction = () => {
fetch('/other_path')
.then(res => {
// why is `state.stateUpdated` here still 'false'????
dispatch({ type: 'DO_SOMETHING_ELSE', res });
});
}
};
const actions = { initialFunction, otherFunction };
useEffect(() => {
initialFunction();
setInterval(otherFunction, 30000);
}, []);
return (
<AppContext.Provider value={{ state, actions }}>
{props.children}
</AppContext.Provider>
)
};
export default AppContextProvider;
and useAppState.js is very simple as:
import { useReducer } from 'react';
const useAppState = () => {
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE_STATE':
return {
...state,
stateUpdated: true,
};
case 'DO_SOMETHING_ELSE':
return {
...state,
// whatever else
};
default:
throw new Error();
}
};
const initialState = { stateUpdated: false };
return useReducer(reducer, initialState);
};
export default useAppState;
The question is, as stated in the comment above, why is state.stateUpdated in context provider's otherFunction still false and how could I access state with latest changes in the same function?
state will never change in that function
The reason state will never change in that function is that state is only updated on re-render. Therefore, if you want to access state you have two options:
useRef to see a future value of state (you'll have to modify your reducer to make this work)
const updatedState = useRef(initialState);
const reducer = (state, action) => {
let result;
// Do your switch but don't return, just modify result
updatedState.current = result;
return result;
};
return [...useReducer(reducer, initialState), updatedState];
You could reset your setInterval after every state change so that it would see the most up-to-date state. However, this means that your interval could get interrupted a lot.
const otherFunction = useCallback(() => {
fetch('/other_path')
.then(res => {
// why is `state.stateUpdated` here still 'false'????
dispatch({ type: 'DO_SOMETHING_ELSE', res });
});
}
}, [state.stateUpdated]);
useEffect(() => {
const id = setInterval(otherFunction, 30000);
return () => clearInterval(id);
}, [otherFunction]);

Categories

Resources