Unable to read state updated by useReducer hook in context provider - javascript

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]);

Related

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.

Async redux action to fetch data is causing component to reload and cause react to react max depth in reload

I am trying to create a component that allows detecting changes in the redux store. Once the shouldUpdateData flag is set in the store, the component responsible for updating should fetch the data by using an async action creator. In my case, either the error "Maximum updates have reached" occurs or the update never happens.
Depending on the dispatch function stopFetching() (turns off the shouldUpdateData flag), the error or outcome changes. If I do the dispatch inside the action creator there are endless updates. If the code is used as it is below, no update occurs.
The reason I used the useSelector() hook from 'react-redux' is to detect a change in the store for the loading attribute.
Thank you in advance.
Here is the action creator:
export function updateDataAsync(id) {
return function (dispatch) {
// dispatch(fetchDataRequest());
return fetch(`/api/user/${id}/data`, {
method: "GET",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(
(result) => {
let {projects, notes} = result;
// New data and dispatch function
dispatch(fetchDataSuccess({projects, notes}));
},
(error) => { dispatch(fetchDataFailure(error)) }
)
}
}
Here is the reducer for this action creator:
export function savedData(state = DATA_INITIAL_STATE, action) {
switch(action.type) {
case FETCH_STATES.FETCH_DATA_REQUEST:
return {
...state,
loading: true
}
case FETCH_STATES.FETCH_DATA_SUCCESS:
return {
loading: false,
data: action.data,
error: ''
}
case FETCH_STATES.FETCH_DATA_FAILURE:
return {
loading: false,
data: {},
error: action.error.message
}
default:
return state;
}
}
The React component that is doing the update:
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
let reqSent = useRef(false);
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
})
return loading ? <LoadingAnimation /> : children;
}
const mapStateToProps = (state) => {
return {
userId: state.user.id,
shouldUpdate: state.shouldUpdateData // The flag that should trigger the update
}
}
const mapDispatchToProps = (dispatch) => {
return {
stopFetch: () => { dispatch(setShouldFetchData(false)) },
update: (id) => { dispatch(updateDataAsync(id)) },
startFetch: () => dispatch(fetchDataRequest()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(StoreUpdater);
You dint pass any dependency to useEffect so it will be called on every render which is causing infinite renders
change useEffect to
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
},[])
For complete information regarding useEffect refer this link
The reference I created inside the component responsible of the updates, was causing the problem. The reference was preventing the update dispatch to occur due to the if statement being false.
mapStateToProps and mapDispatchToProps were react-redux higher order functions to connect classes components into the store. there equalants at functional components are useSelector and useDispatch. re-write your HOC redux adaption into hooks, and add [ dependency ] at useEffect usage
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
const userId = useSelector(state => state.user.id);
const shouldUpdate = useSelector(state => state.shouldUpdateData);
let reqSent = useRef(false);
const dispatch = useDispatch() // import from 'react-redux'
useEffect(()=>{
if(!reqSent && shouldUpdate) {
dispatch(startFetch());
dispatch(update(userId));
reqSent.context = true;
}
}, [reqSent, shouldUpdate, startFetch, dispatch, update, userId])
return loading ? <LoadingAnimation /> : children;
}
export default StoreUpdater ;

Access the state of my redux app using redux hooks

I am migrating my component from a class component to a functional component using hooks. I need to access the states with useSelector by triggering an action when the state mounts. Below is what I have thus far. What am I doing wrong? Also when I log users to the console I get the whole initial state ie { isUpdated: false, users: {}}; instead of just users
reducers.js
const initialState = {
isUpdated: false,
users: {},
};
const generateUsersObject = array => array.reduce((obj, item) => {
const { id } = item;
obj[id] = item;
return obj;
}, {});
export default (state = { ...initialState }, action) => {
switch (action.type) {
case UPDATE_USERS_LIST: {
return {
...state,
users: generateUsersObject(dataSource),
};
}
//...
default:
return state;
}
};
action.js
export const updateUsersList = () => ({
type: UPDATE_USERS_LIST,
});
the component hooks I am using
const users = useSelector(state => state.users);
const isUpdated = useSelector(state => state.isUpdated);
const dispatch = useDispatch();
useEffect(() => {
const { updateUsersList } = actions;
dispatch(updateUsersList());
}, []);
first, it will be easier to help if the index/store etc will be copied as well. (did u used thunk?)
second, your action miss "dispatch" magic word -
export const updateUsersList = () =>
return (dispatch, getState) => dispatch({
type: UPDATE_USERS_LIST
});
it is highly suggested to wrap this code with { try } syntax and be able to catch an error if happened
third, and it might help with the console.log(users) error -
there is no need in { ... } at the reducer,
state = intialState
should be enough. this line it is just for the first run of the store.
and I don't understand where { dataSource } comes from.

Categories

Resources