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.
Related
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.
I’m working on a little proof of concept project, using React and Redux, and useSelector and useDispatch hooks. I’m trying to fetch some data asynchronously and I use thunks for that. I think I'm conceptually missing something. Even though my state works as expected, I can not get my data from api using useSelector.
Here is the code. My action:
import axios from "axios";
export const API_FETCH_POSTS = 'API_FETCH_POSTS';
export const fetchPosts = (postId) => { // to simulate post request
return (dispatch) => {
let baseUrl = 'https://jsonplaceholder.typicode.com/';
let postFix = 'comments?postId=';
let url = `${baseUrl}${postFix}${postId}`;
axios.get(url)
.then(response => {
const data = response.data;
console.log(JSON.stringify(data)); // work!
dispatch(fetchPostsSuccess(data));
});
}
};
const fetchPostsSuccess = posts => {
return {
type: API_FETCH_POSTS,
payload: posts
}
};
My reducer:
import {API_FETCH_POSTS} from "./apiActions";
const initialState = {
getPostsReq : {
posts: [],
}
};
const apiReducer = (state = initialState, action) => {
let getPostsReq;
switch (action.type) {
case API_FETCH_POSTS:
getPostsReq = {
posts: [...state.getPostsReq.posts]
};
return {
...state,
getPostsReq
};
default: return state;
}
};
export default apiReducer;
And rootReducer:
import {combineReducers} from 'redux';
import apiReducer from "./api/apiReducer";
export default combineReducers({
api: apiReducer
})
And store:
const initialState = {};
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(
applyMiddleware(thunk)
)
);
export default store;
I have a problem with my React component:
function PostContainer(props) {
const posts = useSelector(state => state.api.getPostsReq.posts);
const dispatch = useDispatch();
const logPosts = () => {
{/*why doesn't this line work???*/}
console.log(JSON.stringify(posts));
}
return (
<div>
<button onClick={() => {
dispatch(fetchPosts(1));
logPosts();
}}>Fetch Posts</button>
<div>
{/*why doesn't this line work???*/}
{posts.map(post => <p>{post.body}</p>)}
</div>
</div>
);
}
export default PostContainer;
I expect that after I press the button, the function fetchPosts gets dispatched and because I use thunk I shouldn’t have any problems with asynchronicity. But by some reason I can’t get my state, using useSelector() hook. I can neither render the state, nor log it in the console.
What am I missing here?
Here is the whole code if it is more convenient - https://github.com/JavavaJ/use-select-problem
Problem: Not Storing Posts
Your selector is fine, it's your reducer that's the problem! You dispatch an action which has an array of posts in the payload:
const fetchPostsSuccess = posts => {
return {
type: API_FETCH_POSTS,
payload: posts
}
};
But when you respond to this action in the reducer, you completely ignore the payload and instead just return the same posts that you already had:
const apiReducer = (state = initialState, action) => {
let getPostsReq;
switch (action.type) {
case API_FETCH_POSTS:
getPostsReq = {
posts: [...state.getPostsReq.posts]
};
return {
...state,
getPostsReq
};
default: return state;
}
};
Solution: Add Posts from Action
You can rewrite your reducer like this to append the posts using Redux immutable update patterns.
const apiReducer = (state = initialState, action) => {
switch (action.type) {
case API_FETCH_POSTS:
return {
...state,
getPostsReq: {
...state.getPostsReq,
posts: [...state.getPostsReq.posts, ...action.payload]
}
};
default:
return state;
}
};
It's a lot easier if you use Redux Toolkit! With the toolkit you can "mutate" the draft state in your reducers, so we don't need to copy everything.
const apiReducer = createReducer(initialState, {
[API_FETCH_POSTS]: (state, action) => {
// use ... to push individual items separately
state.getPostsReq.posts.push(...action.payload);
}
});
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.
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.
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]);