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>
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>
Consider the below code made in react ( also including redux)
store = createStore(todoApp) ;
store.subscribe(App);
export default function App(){
.....
}
which means for every dispatch action happening in my App functional component the App must render since subscribe executes the enclosed function . However what I have observed is the that though the function executes the HTML components do not get updated and remain the same from the first render even after multiple dispatch actions . Can anyone explain this behavior ?
That is not how you connect your React application to the redux store. Here is an example application using Provider and the react-redux hooks:
//you would import these with
// import {Provider} from 'react-redux'
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
const { memo, useMemo, useCallback } = React;
const initialState = {
counters: [
{ id: 1, count: 1 },
{ id: 2, count: 1 },
{ id: 3, count: 1 },
],
};
//action types
const ADD = 'ADD';
//action creators
const add = (id) => ({
type: ADD,
payload: id,
});
const reducer = (state, { type, payload }) => {
if (type === ADD) {
return {
...state, //not needed in this case but usually is
counters: state.counters.map(
(counter) =>
counter.id === payload
? { ...counter, count: counter.count + 1 }
: counter //do not update this counter (not the right id)
),
};
}
return state;
};
//selectors
const selectCounters = (state) => state.counters;
const createSelectCounterById = (counterId) =>
createSelector(
[selectCounters], //re use select counters
(
counters //got the counters, find the right counter
) => counters.find(({ id }) => id === counterId)
);
//select sum of all counters
const selectSum = createSelector(
[selectCounters], //re use select counters
(counters) =>
//reduce counters array to a number
counters.reduce(
(result, counter) => result + counter.count,
0
)
);
//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 Counter = memo(function Counter({ id, addAction }) {
const selectCounter = useMemo(
() => createSelectCounterById(id),
[id]
);
const counter = useSelector(selectCounter);
return (
<li>
{counter.count}
<button onClick={() => addAction(id)}>+</button>
</li>
);
});
const Total = memo(function Total() {
const sum = useSelector(selectSum);
return <h3>{sum}</h3>;
});
const App = () => {
const counters = useSelector(selectCounters);
const dispatch = useDispatch();
const addAction = useCallback(
(id) => dispatch(add(id)),
//dispatch is not really a dependency but
// linter doesn't know that and adding
// it doesn't change behavior
[dispatch]
);
return (
<div>
<Total />
<ul>
{counters.map(({ id }) => (
//id property here is not html id element property
<Counter key={id} id={id} addAction={addAction} />
))}
</ul>
</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>
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 noticed that immer doesn't create a new array when doing draft.list.push()
Why this is a suggested approach when this doesn't work for redux? Is spread operator the only solution to this? The actual gain from this is that we are sure that array objects will not be mutated?
I am a little confused of the actual purpose of this action
Without seeing any code it is impossible to say what's wrong but there is nothing wrong with immer using push:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
const { produce } = immer;
const id = ((id) => () => id++)(4);
const initialState = {
data: {
counters: [
{ id: 1, count: 0 },
{ id: 2, count: 0 },
{ id: 3, count: 0 },
],
},
};
//action types
const INCREASE = 'INCREASE';
const ADD = 'ADD';
//action creators
const increase = (id) => ({
type: INCREASE,
payload: id,
});
const add = () => ({
type: ADD,
});
const reducer = (
state = initialState,
{ type, payload }
) => {
if (type === INCREASE) {
const index = state.data.counters.findIndex(
({ id }) => id === payload
);
//you sure you return here?
return produce(state, (draft) => {
++draft.data.counters[index].count;
});
}
if (type === ADD) {
//returning new state
return produce(state, (draft) => {
//using arry push on immer draft
draft.data.counters.push({ id: id(), count: 0 });
});
}
return state;
};
//selectors
const selectCounters = (state) => state.data.counters;
const createSelectCounterById = (counterId) =>
createSelector([selectCounters], (counters) =>
counters.find(({ id }) => id === counterId)
);
//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 Counter = React.memo(function Counter({ id }) {
const dispatch = useDispatch();
const selectCounter = React.useMemo(
() => createSelectCounterById(id),
[id]
);
const counter = useSelector(selectCounter);
console.log('rendering:', id);
return (
<li>
{id}:{' '}
<button onClick={() => dispatch(increase(id))}>
{counter.count}
</button>
</li>
);
});
const App = () => {
const counters = useSelector(selectCounters);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(add())}>
add counter
</button>
<ul>
{counters.map((counter) => (
<Counter key={counter.id} id={counter.id} />
))}
</ul>
</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>
<script src="https://unpkg.com/immer#7.0.5/dist/immer.umd.production.min.js"></script>
<div id="root"></div>
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)))
}