DRY redux actions - reactjs

I wonder what is the best approach to dry out common redux actions ('m using redux-thunk)
for example I have these two components:
const Main = ({list, onLike }) => (
<ul>
{list.map(item => (
<Comment item={item} onLike={onLike} />
))}
</ul>
)
const mapStateToProps = (state) => ({
list: state.main.comments
})
const mapStateToProps = (dispatch) => ({
onLike: (commentId) => dispatch(actions.mainOnLike(commentId))
})
export default connect(mapStateToProps, mapDispachToProps)(Main)
const Profile = ({ list, onLike }) => (
<ul>
{list.map(item => (
<Comment item={item} onLike={onLike} />
))}
</ul>
)
const mapStateToProps = (state) => ({
list: state.profile.comments
})
const mapStateToProps = (dispatch) => ({
onLike: (commentId) => dispatch(actions.profileOnLike(commentId))
})
export default connect(mapStateToProps, mapDispachToProps)(Profile)
actions.js
export const mainOnLike = commentId => dispatch => {
commentsService.like(commentId).then(() => {
dispatch(mainReloadComments())
})
}
export const profileOnLike = commentId => dispatch => {
commentsService.like(commentId).then(() => {
dispatch(profileReloadComments())
})
}
This is an example to show the problem. The point is that I'm calling commentsService.like multiple times and I will like to dry that out. The reason for that is that I need to call profileReloadComments and mainReloadComments separated to reload the comments in store for those containers/reducers.
I'm tempted to call commentsService.like inside the <Comment> component, so whenever I use it I have that functionality, but that does not seems the redux way.
any ideas how to dry this out?
Thanks!

Probably something like this:
const likeCommentWithAction = actionCreator => commentId => dispatch => {
commentsService.like(commentId).then(() => {
dispatch(actionCreator())
})
}
// Then
export const mainOnLike = likeCommentWithAction(mainReloadComments)
export const profileOnLike = likeCommentWithAction(profileReloadComments)

Related

Trying to wrap dispatch function in react redux

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)))
}

history.push() not working when passed to other component fron functional component

I am quite used to class components. There I can pass a function that is bound to this to other components without problems. I thought the same would hold true for functional components.
Yet, the following code simply does not work:
const Dropdown: React.FC<{onNewPost: any}> = (props) => {
return(
<div onClick={props.onNewPost}></div>
)
}
function AddMessage(props: IProps) {
const { conversationUUID } = props.match.params;
const navigateToNewPost = (postUUID: string) => {
props.history.push(`/app/messages/new/${postUUID}/`)
}
const onNewPost = () => {
// props.history.push('/example/') -> this works without problems
props.createPost(
conversationUUID,
navigateToNewPost
)
}
return(
<Dropdown onNewPost={onNewPost}/>}
)
}
const mapStateToProps = (state: AppState) => ({
})
const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({
createPost
}, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(AddMessage));
I think the problem arises because props.history.push is not bound to anything here but the global object? I think I am having trouble understanding how functions are bound in functional components ... Maybe some helpful soul can come up with an explanation. Thanks!
EDIT:
export const createPost = (conversationUUID: string, callback: any) => async (dispatch: Dispatch) => {
try {
const res: any = authAxios.post('/posts/', {
conversation: conversationUUID,
})
if (callback) {callback(res.data.uuid)}
} catch(e) {
}
}

React-Redux not dispatching action type

Here is react/redux application.
This a basic stripped down version of what I am trying to accomplish. showFolder() produces a list of folders and a button to click where it calls the removeFolder action from FolderActions.js. The button works and will call the function in FolderActions.js however will not dispatch the type. The functions works as I can see the console.log message but will not dispatch the type using redux..
I have a strong feeling it's the way I'm calling the function however I am lost at the moment
import {
addFolder,
getFolder,
removeFolder,
} from "../../../actions/FolderActions";
class Folders extends Component {
onRemoveFolder = (e,id) => {
e.preventDefault();
console.log(id);
this.props.removeFolder(id);
};
showFolders = () => {
return (
<ul>
{this.props.folder.map((key, index) => (
<form onSubmit={(e) => this.onRemoveFolder(e,key._id)}>
<input type="submit"></input>
</form>
))}
</ul>
);
};
render() {
let { isShown } = this.state;
return (
<div>
<div className="folder_names">{this.showFolders()}</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
userId: state.auth.user._id,
folder: state.folder.data
};
};
export default connect(mapStateToProps, {removeFolder,addFolder,getFolder})(
Folders
);
FolderActions.js
export const removeFolder = id => dispatch => {
console.log("called")
axios
.delete(`api/folders/${id}`)
.then(res =>
dispatch({
type: DELETE_FOLDER,
payload: id
})
)
.catch(err => {
console.log(err);
});
};
Your function call looks strange to me...
Can you try defining a proper mapDispatchToProps and calling dispatch within that instead of within your function?
const mapDispatchToProps = dispatch => ({
removeFolder: (id) => dispatch( removeFolder(id) ),
addFolder: (id) => dispatch( addFolder(id) ),
getFolder: (id) => dispatch( getFolder(id) ),
})
export default connect(mapStateToProps, mapDispatchToProps)(
Folders
);
export const removeFolder = id => {
// code block
};
I know that's more a rework that you probably were hoping for, but does it work?
Correct me if I'm wrong but my server never sent a response.
Because the server never sent a response, when I made a request, res is not true so cannot dispatch the type and payload.
Sorry for wasting peoples time!

React Redux - dispatch action with react hooks

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;
};

What is reliable way to dispatch action for dynamic components?

Let's say that I have state with elements that represent different data types of objects.
Each element can have a different action to dispatch
export default connect(
(state) => {
return {
events: getRegisteredEventsList(state).map(item => {
return {
title: item.get('name'),
actionButton: <a onClick={dispatch(someActionForThisSpecifiedItem(item))}>Custom Action</a>
}
})
},
(dispatch) => {
return {
}
}
)(Dashboard)
What is reliable way to achieve this kind of pattern ?
Should I put dispatch method to my container's props?
How do I achieve that at this point is:
export default connect(
(state) => {
return {
events: getRegisteredEventsList(state).map(item => {
return {
title: item.get('name'),
actionButton: ({dispatch}) => <a
className={"btn btn-success"}
onClick={() => dispatch(someActionForThisSpecifiedItem(item))}>Go To</a>
}
})
}
)(Dashboard)
adding method:
renderActionButtons() {
const { actionButtons, dispatch } = this.props
return actionButtons.map(button => renderComponent(button, {
dispatch
}));
}
into my dummy component - which is violation of separation of concerns because my view components now need to know and maintain dispatch property
I feel like that could be redux a feature request as well.
I would go for something like this, lets say for simplicity your state is something like this:
const items = [{
name: 'abc',
}, {
name: 'def',
}];
The link component which simply dispatches an action when it's clicked
const Link = ({name, onClick}) => <a onClick={() => onClick(name)}>{name}</a>;
The render links component which accepts the following props: a list of items and the onClick function which is capable of dispatching actions
const RenderLinks = ({ items, onClick }) => (
<div>
{items.map(a =>
<Link
name={a.name}
onClick={onClick}
/>)}
</div>
);
const mapStateToProps = (state) => ({
items,
});
The onClick function has the ability to dispatch the actions
const mapDispatchToProps = (dispatch) => ({
onClick: (name) => dispatch({type: `${name.toUpperCase()}_CLICKED`}),
});
export default connect(mapStateToProps, mapDispatchToProps)(RenderLinks);

Resources