Hi recently I encountered the useDispatch hook that supposed to give me an alternative to mapDispatchToProps, and I found very repetitive to do () => dispatch(action(args)) in each onPress so I started to think about something generic. My goal was to make a hook that uses useDispatch() and wraps the functions that it gets and retuens () => dispatch(theWrappedAction(theActionArgs))
for example if I have an action upCounterActionCreator that is as following:
export const upCounterActionCreator = (count: number = 1): AppActions => {
const action: UpCounterAction = {
type: 'UP_COUNTER',
count
};
return action;
};
My goal is to do something like this:
const [upAfterDispatch] = useActions(upCounterActionCreator);
and then I can do:
<Button onPress={upAfterDispatch(1)} title='+' />
What I tried to do is as following:
export const useActions = (...actions: ((...args: any) => AppActions)[]) => {
const dispatch = useDispatch<Dispatch<AppActions>>();
const actionsWithDispach: ((...args: any) => () => (...args: any) => AppActions)[] = [];
actions.forEach(action => {
actionsWithDispach.push((...args: any) => () => (...args: any) => dispatch(action(...args)));
});
return actionsWithDispach;
};
to put that wrapped function on onPress I need to do
<Button onPress={upAfterDispatch(1)()} title='+' /> - to invoke it, it is not so good option.
Then when I do call it the action indeed is being dispatched however when I debug on my payload I have an object insted of count that is as following:
it is a class-
What am I doing wrong? What do I need to to in order to:
get the number 1(the count parameter sent in the action payload) instead of the class
invoke the returned functions from useActions and not call it like this onPress={upAfterDispatch(1)()
** I think that the object received in the args is the react native onPress event, how to avoid it overriding my count argument?
Thanks ahead!
I think this is what you wanted to do:
export const useActions = (...actions: ((...args: any) => AppActions)[]) => {
const dispatch = useDispatch<Dispatch<AppActions>>();
const actionsWithDispach: ((...args: any) => () => (...args: any) => AppActions)[] = [];
actions.forEach(action => {
actionsWithDispach.push((...args: any) => () => dispatch(action(...args)));
});
return actionsWithDispach;
};
You added an extra (...args: any) => but with the code above you can do onClick={theAction(1)}
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore } = Redux;
const initialState = {
count: 0,
};
const reducer = (state, { type, payload }) => {
if (type === 'UP') {
return { count: state.count + payload };
}
return state;
};
const store = createStore(
reducer,
{ ...initialState },
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
//action
const add = (howMuch) => ({ type: 'UP', payload: howMuch });
const useAction = (action) => {
const dispatch = useDispatch();
return React.useMemo(
() => (...args) => () => dispatch(action(...args)),
[action, dispatch]
);
};
const Button = React.memo(function Button({ up, howMuch }) {
const rendered = React.useRef(0);
rendered.current++;
return (
<button onClick={up(howMuch)}>
Rendered: {rendered.current} times, add {howMuch}
</button>
);
});
const App = () => {
const up = useAction(add);
const count = useSelector((state) => state.count);
return (
<div>
<h2>count:{count}</h2>
<Button up={up} howMuch={1} />
<Button up={up} howMuch={1} />
<Button up={up} howMuch={1} />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
type ActionCreator = (...args: any) => AppActions
export const useActions = (...actions: ActionCreator[]) => {
const dispatch = useDispatch<Dispatch<AppActions>>();
return actions.map(action => (...args: any) => () => dispatch(action(...args)))
}
Related
I have a child filter input componenet inside kendo grid and my goa is stoping that components from rendering again and saving "input text" inside input field
<GridColumn
field="name"
title="Document Name"
headerCell={HeaderCell}
className="tableCell"
cell={LinkCell}
filterCell={() => <SearchInput className={filtersToggler} /> }
footerCell={(props) => <FooterCell {...props} colSpan={4} total={total}></FooterCell>}
/>
Now when I add some input inside that component, it passes value to the state called word and after 500 ms it triggers debounce and parses it to the redux state called "term"
const SearchInput = React.memo(() => {
const [word, setWord] = useState('');
const dispatch = useDispatch();
const deb = useCallback(
debounce((text) => dispatch({ type: 'SET_TERM', term: text }), 1000),
[]
);
const handleText = (text) => {
deb(text);
};
return (
<input
className="searchInput"
value={word}
type="search"
placeholder="Search.."
onChange={(e) => {
handleText(e.target.value)
setWord(e.target.value);
}}></input>
);
});
export default SearchInput;
Now, whenever redux state changes, it triggers useEffect inside of a kendo grid and gets new data from API.
const searchWord = useSelector((state) => state.search.term);
const classifications = useSelector((state) => state.search.classifications);
const date = useSelector((state) => state.search.date);
useEffect(() => {
const data = searchDocsByName(searchWord, date, classifications);
data.then((i) => {
setDocuments(i.data.data);
setTotal(i.data.data.length);
});
}, [searchWord, date, classifications]);
So what's the problem? SearchInput Componenet rerenders even if its inside React.memo() and from the profiler I am getting that SearchInput rendered because "hook change".
I am totally stuck, I have no idea how to procced.
You needlessly set local state with const [word, setWord] = useState('');, setWord will re render your component because local state changed. You can make the input an uncontrolled component
Here is an example of what you can do:
const { Provider, useDispatch } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { useCallback } = React;
const initialState = {};
const reducer = (state, { type, term }) => {
console.log('in reducer', type, term);
return state;
};
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
() => (next) => (action) => next(action)
)
)
);
const debounce = (fn, time) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), time);
};
};
const SearchInput = React.memo(function SearchInput() {
console.log('rendering SearchInput');
const dispatch = useDispatch();
const deb = useCallback(
debounce(
(text) => dispatch({ type: 'SET_TERM', term: text }),
1000
),
[]
);
return (
<input
className="searchInput"
type="search"
placeholder="Search.."
onChange={(e) => {
deb(e.target.value);
}}
></input>
);
});
const App = () => {
return <SearchInput />;
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
I've been looking for a solution to "promisify" useReducer do something once I'm sure that the state has been changed as per my dispatched action. I found some promising stuff, such as this feature request and a few solutions similar to this one that's based on combining useReducer with useEffect. So instead of using a promise, I've tried to use a callback instead and I want to note here that this implementation works. But I'm unsure if there are any drawbacks to this.
**Note, the use case here isn't to call a function per every time the state changes, but rather the option to do something when the reducer finishes processing an action.
As per Redux rules, my reducer does not mutate state.
const emptyState: IState = {
str: '',
obj: {
propA: 0,
propB: 0,
}
}
interface ReducerActions {
type: 'changeStr' | 'changeObj';
callback?: (newState: IState) => any;
}
const reducer = (state: IState, action: ReducerActions): IState => {
let newState = {...state};
switch(action.type) {
case 'changeStr':
newState.str = action.newStr;
break;
case 'changeObj':
newState.obj = action.newObj;
break;
if (action.callback) {
action.callback(newState);
}
return newState;
}
I did notice that this works in reverse of the traditional flow, where the callback or promise is executed after the state has changed, but should it matter when the callback is called using the value of the new state anyways?
And, are there any drawbacks or side-effects of using this method (whether here or in a Redux implementation)?
the action changeStr replaces the string of state.str with a new string, using 2 buttons, one makes it longer, the other shorter. If I use useEffect, I can of course check the new value of the string and get the length, but I would not be able to get the length of the previous string without storing the previous value. If I pass a callback to the action implemented in the buttons, I know which button makes it longer and which one makes it shorter.
Still not sure what your needs are but if you need the previous and current value to perform some logic you can write a custom hook:
const { useReducer, useState, useRef } = React;
const init = { value: 'A' };
const TOGGLE = 'TOGGLE';
const toggle = () => ({ type: TOGGLE });
const reducer = (state, { type }) => {
//toggle state.value between A and B
if (type === TOGGLE) {
return { value: state.value === 'A' ? 'B' : 'A' };
}
return state;
};
const selectValue = (state) => state.value;
const NONE = {};
//custom hook to detect changes between renders
const useChange = (value, callback) => {
const ref = useRef(NONE);
if (ref.current !== value) {
if (ref.current !== NONE) {
callback(ref.current, value);
}
ref.current = value;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, init);
const [message, setMessage] = useState('');
const value = selectValue(state);
useChange(value, (pref, current) =>
setMessage(`value changed from ${pref} to ${current}`)
);
return (
<div>
<button onClick={() => dispatch(toggle())}>
toggle
</button>
<div>{value}</div>
<div>{message}</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
UPDATE
Example of using thunk with useReducer:
const { useReducer, useRef } = React;
const init = { value: 'A' };
const TOGGLE = 'TOGGLE';
const thunkToggle = () => (dispatch, getState) => {
const value = getState().value;
//you can do async dispatch
setTimeout(() => {
dispatch({ type: TOGGLE });
console.log(
`action dispatched value was ${value} is now ${
getState().value
}`
);
}, 10);
console.log(`value is now (nothing dispatched) ${value}`);
};
const reducer = (state, { type }) => {
console.log(`in reducer action type: ${type}`);
//toggle state.value between A and B
if (type === TOGGLE) {
return { value: state.value === 'A' ? 'B' : 'A' };
}
return state;
};
const SET_STATE = Date.now();
//custom hook to detect changes between renders
const useThunkReducer = (reducer, initialState) => {
const state = useRef(initialState);
const thunkReducer = (state, action) => {
if (action.type === SET_STATE) {
return action.payload;
}
return reducer(state, action);
};
const [rState, dispatch] = useReducer(thunkReducer, init);
const thunkDispatch = (action) => {
if (typeof action === 'function') {
return action(thunkDispatch, () => state.current);
}
state.current = thunkReducer(state.current, action);
dispatch({ type: SET_STATE, payload: state.current });
};
return [rState, thunkDispatch];
};
const App = () => {
const [state, dispatch] = useThunkReducer(reducer, init);
return (
<div>
<button onClick={() => dispatch(thunkToggle())}>
toggle
</button>
<pre>{JSON.stringify(state, undefined, 2)}</pre>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
For completion; here is an example using middleware so you can add several middleware functions and not only thunk:
const { useRef, useState } = React;
const compose = (...fns) =>
fns.reduce((result, fn) => (...args) =>
fn(result(...args))
);
const mw = () => (next) => (action) => next(action);
const createMiddleware = (...middlewareFunctions) => (
store
) =>
compose(
...middlewareFunctions
.concat(mw)
.reverse()
.map((fn) => fn(store))
);
const useMiddlewareReducer = (
reducer,
initialState,
middleware = () => (b) => (c) => b(c)
) => {
const stateContainer = useRef(initialState);
const [state, setState] = useState(initialState);
const dispatch = (action) => {
const next = (action) => {
stateContainer.current = reducer(
stateContainer.current,
action
);
return setState(stateContainer.current);
};
const store = {
dispatch,
getState: () => stateContainer.current,
};
return middleware(store)(next)(action);
};
return [state, dispatch];
};
//middleware
const thunkMiddleWare = ({ getState, dispatch }) => (
next
) => (action) =>
typeof action === 'function'
? action(dispatch, getState)
: next(action);
const logMiddleware = ({ getState }) => (next) => (
action
) => {
console.log('in log middleware', action, getState());
Promise.resolve().then(() =>
console.log('after action:', action.type, getState())
);
return next(action);
};
const init = { value: 'A' };
const TOGGLE = 'TOGGLE';
const thunkToggle = () => (dispatch) => {
setTimeout(() => {
dispatch({ type: TOGGLE });
}, 500);
};
const reducer = (state, { type }) => {
console.log(`in reducer action type: ${type}`);
//toggle state.value between A and B
if (type === TOGGLE) {
return { value: state.value === 'A' ? 'B' : 'A' };
}
return state;
};
const middleware = createMiddleware(
thunkMiddleWare,
logMiddleware
);
const App = () => {
const [state, dispatch] = useMiddlewareReducer(
reducer,
init,
middleware
);
return (
<div>
<button onClick={() => dispatch(thunkToggle())}>
toggle
</button>
<pre>{JSON.stringify(state, undefined, 2)}</pre>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I have an index.js which calls an action. Once it calls an action I want to trigger multiple dispatch actions inside that action. Index.js calls handLoadCustomers which will dispatch loadCustomers function which calls an API and dispatches another function to store customers in the state.
Once that is done, call comes back to handleLoadCustomers where I want to use customers from the first
call and then dispatch another action of handleExtraCustomerLoads with those customers which will
call another functions/actions. How can I do that in React Redux?
export function handleLoadCustomers() {
return function (dispatch) {
dispatch(loadCustomers())
.then((customers) => {
dispatch(handleExtraCustomerLoads(customers));
})
.catch((error) => {
throw error;
})
.then((newCustomers) => {
dispatch(handlePageLoadSuccess(newCustomers));
});
};
export function loadCustomers() {
return function (dispatch) {
return getCustomers()
.then((customers) => {
dispatch(loadCustomerSuccess(customers));
})
.catch((error) => {
throw error;
});
};
}
customers after the loadCustomers is empty and it does not dispatch handleExtraCustomerLoads function at all
You should not use the return value of your action but you can wait for it to finish and select the result from the state (I assume the action will write data you need to the store).
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const initialState = {
customers: [],
extra: [],
};
//api
const getCustomers = () => Promise.resolve([1, 2, 3]);
//action types
const CUSTOMERS_SUCCESS = 'CUSTOMERS_SUCCESS';
const EXTRA = 'EXTRA';
//action creators
const loadCustomerSuccess = (customers) => ({
type: CUSTOMERS_SUCCESS,
payload: customers,
});
const handleExtraCustomerLoads = (extra) => ({
type: EXTRA,
payload: extra,
});
function handleLoadCustomers() {
return function (dispatch, getState) {
dispatch(loadCustomers()).then(() => {
const customers = selectCustomers(getState());
dispatch(handleExtraCustomerLoads(customers));
});
};
}
function loadCustomers() {
return function (dispatch) {
return getCustomers()
.then((customers) => {
//you need to return the value here
return dispatch(loadCustomerSuccess(customers));
})
.catch((error) => {
throw error;
});
};
}
const reducer = (state, { type, payload }) => {
if (type === CUSTOMERS_SUCCESS) {
return { ...state, customers: payload };
}
if (type === EXTRA) {
return { ...state, extra: payload };
}
return state;
};
//selectors
const selectCustomers = (state) => state.customers;
const selectExtra = (state) => state.extra;
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware((store) => (next) => (action) =>
//diy thunk
typeof action === 'function'
? action(store.dispatch, store.getState)
: next(action)
)
)
);
const App = () => {
const dispatch = useDispatch();
const customers = useSelector(selectCustomers);
const extra = useSelector(selectExtra);
return (
<div>
<button
onClick={() => dispatch(handleLoadCustomers())}
>
load customers
</button>
<pre>
{JSON.stringify({ customers, extra }, undefined, 2)}
</pre>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
I have state that is managed inside a component.
And I'm using data from other component by storing them into the redux store.
However, at the end, i'm trying to collect all the data to make an api call.
const [projectData, setProjectData] = useState({})
const { userData } = useSelector((state) => state.userManage);
useEffect(() => {
let newUsers = userData.map((x,i)=>({user_id:x.user_id, sub_user_id:x.sub_user_id}))
setProjectState((state) =>({...state, users:newUsers}))
}, [userData ]);
// this is handler
const handleClick = () =>{
dispatch(action.createProject(projectData));
}
I'm trying to update local state whenever the data i'm subscribing is changing.
Somehow it doesn't update correctly. When I don't use 'map' function and just
setProjectState((state) =>({...state, users: userData})) <= This works.
But it copys everything inside of userData,
so I'm using map function to pick specific data, not all data. but it is not working.
for example:
//redux store state
userData={user_id: 0, sub_id: [1,2,3], useless:"sda", useless2:"asd" }
I want to update and add local state only with user_id and sub_id
The code you posted works just fine, maybe you can provide an example that demonstrates your problem assuming you meant setProjectData instead of setProjectState:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { useState, useEffect } = React;
const initialState = {
userData: [],
};
//action types
const ADD = 'ADD';
//action creators
const userid = ((id) => () => id++)(1);
const sub = ((id) => () => id++)(1);
const add = () => ({
type: ADD,
payload: {
user_id: userid(),
sub_user_id: sub(),
other: 'not used',
},
});
const reducer = (state, { type, payload }) => {
if (type === ADD) {
return {
...state,
userData: [payload, ...state.userData],
};
}
return state;
};
//selectors
const selectUserData = (state) => state.userData;
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
const UserList = () => {
const [projectData, setProjectData] = useState({});
const userData = useSelector(selectUserData);
useEffect(() => {
let newUsers = userData.map((x, i) => ({
user_id: x.user_id,
sub_user_id: x.sub_user_id,
}));
setProjectData((state) => ({
...state,
users: newUsers,
}));
}, [userData]);
return (
<div>
userData:
<pre>{JSON.stringify(userData, undefined, 2)}</pre>
projectData:
<pre>{JSON.stringify(projectData, undefined, 2)}</pre>
</div>
);
};
const App = () => {
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(add())}>
add data
</button>
<UserList />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
However; instead of copying the data it would be best to just write a selector to get the data the way you want it:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
const initialState = {
userData: [],
};
//action types
const ADD = 'ADD';
//action creators
const userid = ((id) => () => id++)(1);
const sub = ((id) => () => id++)(1);
const add = () => ({
type: ADD,
payload: {
user_id: userid(),
sub_user_id: sub(),
other: 'not used',
},
});
const reducer = (state, { type, payload }) => {
if (type === ADD) {
return {
...state,
userData: [payload, ...state.userData],
};
}
return state;
};
//selectors
const selectUserData = (state) => state.userData;
const selectProjectData = createSelector(
[selectUserData], //re use selectUserData
(userData) =>
//modify user data to project data
userData.map((x, i) => ({
user_id: x.user_id,
sub_user_id: x.sub_user_id,
}))
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
const UserList = () => {
//no state, no effect, just select the way you want it
const projectData = useSelector(selectProjectData);
//if you want userData as well you can select that too
// const userData = useSelector(selectUserData)
return (
<div>
<pre>{JSON.stringify(projectData, undefined, 2)}</pre>
</div>
);
};
const App = () => {
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(add())}>
add data
</button>
<UserList />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
To clearify I'm pretty newbie with the concept of react-redux. I try to dispatch an async action in the presentational comp. but this does not seem to work out.
Container Component
const store = configureStore();
const Root: React.FC = () => (
<Provider store={store}>
<App />
</Provider>
);
render(<Root/>, document.getElementById('root'));
Presentational Component
interface AppProps {
system: SystemState,
updateSession: typeof updateSession,
getLanguageThunk: any
}
const App: React.FC<AppProps> = ({system, updateSession, getLanguageThunk}) => {
useEffect(() => {
getLanguageThunk().then((res: any) => {
console.log(res);
i18n.init().then(
() => i18n.changeLanguage(res.language));
});
}, []
);
return (
<div>
<div className="app">
<TabBar/>
</div>
</div>
);
};
const mapStateToProps = (state: AppState) => ({
system: state.system
});
export default connect(mapStateToProps, { updateSession, getLanguageThunk })(App);
But the console everytime logs undefined. So I am doint something wrong here. Maybe some of u can help me out on here.
Redux middleware
export const getLanguageThunk = (): ThunkAction<void, AppState, null, Action<string>> => async dispatch => {
const language = await getLanguage();
dispatch(
updateSession({
disableSwipe: false,
language
})
)
};
async function getLanguage() {
try {
const response = await fetch('http://localhost:3000/language');
return response.json();
} catch {
return { language: 'en_GB' }
}
}
You need to return the language from getLanguageThunk, to be able to use it from promise in the useEffect method
export const getLanguageThunk = (): ThunkAction<void, AppState, null, Action<string>> => async dispatch => {
const language = await getLanguage();
dispatch(
updateSession({
disableSwipe: false,
language
})
)
return language;
};