I'm new to react. I tried to separate component and action function. but I cannot get return value from separate action function. Is it possible to return a value (e.g Object {}) from dispatch function
I put the brief code as below :
LoginComponent.js
class Login extends React.Component {
constructor(props){
super(props)
this.state = {
username : '',
password : ''
}
}
submit = (e) => {
/* console.logging "Some response"*/
console.log(this.props.doLogin(this.state))
}
render(){
return (
<form onSubmit={this.submit}>/* some login element */</form>
)
}
}
export default connect(null, {LoginAction})(Login);
LoginAction.js
export function doLogin(state){
return dispatch => {
return axios.post('login', state).then(res =>{
return "Some response";
})
}
}
but It doesn't return any value
Thankyou.
Contrary the the above answer, you actually can return whatever you want from a thunk. Redux-thunk will pass it through.
In your case, where your thunk is returning a Promise<string>, that means that in your component this.props.doLogin(this.state) will also evaluate to a Promise<string>.
So instead of trying to log the Promise, instead try switching that log code over to this.props.doLogin(this.state).then(result => console.log(result);
You can use callback function
this.props.doLogin((this.state),(result)=>{
console.log(result)
})
export function doLogin(state,callback){
return dispatch => {
return axios.post('login', state).then(res =>{
callback(res);
})
}
}
Returning the function is not an option when you are using redux-thunk. it will run the callback and dispatch whatever you pass as an action object.
So as you want to make the api call ans see whether it is a success or failure . You need to dispatch and action on success. save it in redux state. and access the data in your component
export function doLogin(state){
return dispatch => {
axios.post('login', state).then(res =>{
dispatch({
data: "Some response",
type: "API_SUCCESS"
})
})
.catch(err) {
dispatch({
data: err,
type: "API_FAILURE"
})
}
}
And then access those values in your component like this
mapStateToProps = (state) => ({
data: state.yourreducer.data,
})
define mapDispatchToProps if you need dispatcch binded functions
export default(mapStateToProps, mapDispatchToProps)(YourComponent)
Related
I have a simple form in react-redux meant to try to add a user to the database, if it is successful, display a success message. However I am not sure of the best approach to do this. I have the following:
onSubmit = e => {
...
const newUser = { user object here }
this.props.registerUser(newUser);
}
in componentWillReceiveProps(nextProps):
if (nextProps.success === true) {
this.setState({ success: nextProps.success });
}
in the render():
Meant to display a success component giving further information. There is also a conditional check to hide the form if success is true
{ this.state.success === true &&
(<SuccessComponent name={this.state.name} />)
}
in mapStateToProps:
const mapStateToProps = state => ({
success: state.success
});
in my action:
.then(res => {
dispatch({
type: REGISTRATION_SUCCESS,
payload: true
});
})
in the reducer:
const initialState = false;
export default function(state = initialState, action) {
switch (action.type) {
case REGISTRATION_SUCCESS:
return action.payload;
default:
return state;
}
}
in combineReducers:
export default combineReducers({
success: successReducer
});
In this, I am basically using the response from the server to dispatch a success prop to the component, update the state, which forces react to render and go through the conditional statement again, hiding the form and displaying the new success block.
However, when I go into redux dev tools, I see that the state from the store is now true, and remains so should users navigate away. Is there a better way to go about this objective? I find that maybe this should be isolated to component state itself, but not sure how to do it since the action and hence the server response is through redux.
Redux is a state machine, not a message bus, so try to make your state values represent the current state of the application, not to send one-time messages. Those can by the return value of the action creator. Success or failure can simply be the existence/lack of an error from the action creator.
If you actually do want to store the user info, you can derive your "was successful" state by virtue of having a registered user, and clear out any existing registered user on component mount.
// actions.js
export const clearRegisteredUser = () => ({
type: SET_REGISTERED_USER,
payload: null,
})
export const register = (userData) => async (dispatch) => {
// async functions implicitly return a promise, but
// you could return it at the end if you use the .then syntax
const registeredUser = await api.registerUser(userData)
dispatch({
type: SET_REGISTERED_USER,
payload: registeredUser,
})
}
// reducer.js
const initialState = { registeredUser: null }
const reducer = (state = initialState, { type, payload }) => {
switch(type) {
case SET_REGISTERED_USER: {
return {
...state,
registeredUser: payload,
}
}
default: {
return state
}
}
}
// TestComponent.js
class ExampleComponent extends Component {
state = {
registrationError: null,
}
componentWillMount() {
this.props.clearRegistered()
}
handleSubmit = async (formData) => {
try {
this.props.register(formData)
} catch(error) {
// doesn't really change application state, only
// temporary form state, so local state is fine
this.setState({ registrationError: error.message })
}
}
render() {
const { registrationError } = this.state
const { registeredUser } = this.props
if (registrationError) {
return <FancyError>{registrationError}</FancyError>
} else if (registeredUser) {
return <SuccessComponent name={this.props.registeredUser.name} />
} else {
return <UserForm onSubmit={this.handleSubmit} />
}
}
}
If you really don't need the user info after you register, you can just perform the api call directly and leave Redux out of it.
class ExampleComponent extends Component {
state = {
registered: false,
registrationError: null,
}
handleSubmit = async (formData) => {
try {
await api.registerUser(formData)
this.setState({ registered: true })
} catch(error) {
this.setState({ registrationError: error.message })
}
}
render() {
const { registered, registrationError } = this.state
if (registrationError) {
return <FancyError>{registrationError}</FancyError>
} else if (registered) {
return <SuccessComponent name={this.props.registeredUser.name} />
} else {
return <UserForm onSubmit={this.handleSubmit} />
}
}
}
Finally, avoid keeping copies of redux state in your component state whenever possible. It can easily lead to sync issues. Here's a good article on avoiding derived state: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
First off, I don't think that you should be using your Redux store for saving what is essentially local state.
If it was me I would probably try to make the api call directly from the component and then write to the redux store if it is successful. That way you could avoid having your derived state in the component.
That said, if you want to do it this way I would suggest componentWillUnmount. That would allow you have another Redux call that would turn your registration boolean back to false when you leave the page.
What is the best way to trigger an action inside componentDidMount () using a redux props? ex:
import { fetchUser } from '../actions'
class Example extends Component {
ComponentDidMount(){
this.props.fetchUser(this.props.id)
} ...
mapDispatchToProps = dispatch => ({
fetchUser: (payload) => dispatch(fetchUser(payload))
})
mapStateToProps = state => ({
id: state.user.id
})
The problem is that ComponentDidMount () is mounted before the class even receives props from the store. That way my this.props.id is = 'undefined' inside the method.
One solution I found was to run as follows but I do not know if it's the best way:
import { fetchUser } from '../actions'
class Example extends Component {
fetchUser = () => {
this.props.fetchUser(this.props.id)
}
render(){
if(this.props.id !== undefined) this.fetchUser()
} ...
}
mapDispatchToProps = dispatch => ({
fetchUser: (payload) => dispatch(fetchUser(payload))
})
mapStateToProps = state => ({
id: state.user.id
})
That way I get the requisition, but I do not think it's the best way. Any suggestion?
Have you tried using async/await?
async ComponentDidMount(){
await this.props.fetchUser(this.props.id)
} ...
You have to understand the lifecycle of react components. When the component gets mounted, it can fetch data, but your component at that point needs something to render. If the data hasn't been loaded yet, you should either return null to tell react that it's not rendering anything at that point, or perhaps a loading indicator to show that it's fetching data?
import { fetchUser } from '../actions'
class Example extends Component {
componentDidMount() {
this.props.fetchUser();
}
render(){
const { loading, error, user } = this.props;
if (loading) {
return <LoadingIndicator />;
}
if (error) {
return <div>Oh noes, we have an error: {error}</div>;
}
// Render your component normally
return <div>{user.name}</div>;
}
}
Your reducer should have loading set to true by default, and when your fetch completes, set loading to false, and either set the user or error depending on if the fetch fails/completes.
I can't lie, i'm a bit confused about react-redux. I think lot of the actions require parameters (for an example deleting items from the store), but even if i'm still reading about how to dispatch from component in that way to pass a parameter, about 2 hours now, i didn't get any answers. I was tried with this.props.dispatch and with mapDispatchToProps, i always get the this.props... is not a function message. Here's what i'm trying to do:
const getTheArray = (array) => {
return {
type: 'GET_ARRAY',
array
}
}
class Example extends......
componentDidUpdate(){
//i have a button, and when it clicked, turns the status: 'deleted'
if (this.state.status === 'deleted'){
fetch('http://localhost:3001/deleteitem', {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: this.props.id
})
})
.then(res => res.json())
.then(data => this.props.deleteFromArray(data))
.catch(err => console.log(err))
}
}
function mapStateToProps(state) {
return {
array: state.array
};
}
function mapDispatchToProps(dispatch) {
return({
deleteFromArray: (array) => {dispatch(getTheArray(array))}
})
}
export default connect(mapStateToProps, mapDispatchToProps)(Example);
It isn't the only place where i need to dispatch with an action, where the action's object's properties depending on another property passed to the function, so i really want to do, what's the best way to pass property to action, and dispatch it within a react component.
import { bindActionCreators } from "redux"
function mapDispatchToProps(dispatch) {
return(bindActionCreators({
deleteFromArray: (array) => {getTheArray(array)}
}, dispatch))
}
In your dumbComponents, just call e.g this.props.deleteFromArray([1,2,3,4])
This should solve the issue. you are not binding with dispatch
use of react-redux
you need to import action first
import { storeProfiles } from 'actions/profiles';
define props
const {
storeProfiles
} = props;
get data use of userEffect
useEffect(() => {
fetchProfiles().then(storeProfiles);
}, [ fetchProfiles, storeProfiles ]);
map to props
const mapDispatchToProps = dispatch => ({
storeProfiles: x => dispatch(storeProfiles(x))
});
export default connect(mapStateToProps, mapDispatchToProps)(Component);
Read official documentation
Using es6:
const mapDispatchToProps = (dispatch) => ({ addToCart: (params) => dispatch(actions.addToCart(params)), });
Actually, you don't have to pass params to actions like that, in my opinion.
I'm using React Hooks with TypeScript and Redux and thunk. The way I delete a post using an action creator looks like this:
PostDetails.tsx component:
import {deletePost, getPost, LanguageActionTypeEnum} from "../../../../app/store/aspian-core/actions";
interface IPostDetailsProps {
getPost: Function;
// action for deleting a post
deletePost: Function;
loadingInitial: boolean;
submitting: boolean;
post: IPost | undefined;
lang: LanguageActionTypeEnum;
}
const PostDetails: FC<PostDetailsProps> = ({
match, getPost,
lang, loadingInitial,
submitting, deletePost, post
}) => {
}
// To delete a post
const onDeleteBtnClick = async (id: string) => {
try {
/// using deletePost action and passing id to it
await deletePost(id);
message.success(t('messages.post-deleting-success'));
} catch (error) {
message.error(t('messages.post-deleting-error'));
}
};
return (
// omitted for brevity
<Popconfirm
key={uuidv4()}
title={t('popconfirm.title')}
/// calling onDeleteBtnClick() here
onConfirm={() => onDeleteBtnClick(post!.id)}
okText={t('popconfirm.ok-text')}
cancelText={t('popconfirm.cancel-text')}
placement={lang === LanguageActionTypeEnum.en ? 'left' : 'right'}
okButtonProps={{
danger: true,
}}
>
<Button
loading={submitting}
size="small"
type="primary"
icon={<DeleteFilled/>}
danger
>
{t('buttons.delete')}
</Button>
</Popconfirm>
// omitted for brevity
)
}
// Redux State To Map
const mapStateToProps = ({post, locale}: IStoreState): { post: IPost | undefined, submitting: boolean, loadingInitial: boolean, lang: LanguageActionTypeEnum } => {
return {
post: post.post,
submitting: post.submitting,
loadingInitial: post.loadingInitial,
lang: locale.lang
}
}
// Redux Dispatch To Map
const mapDispatchToProps = {
getPost,
// mapped deletePost action to props here
// and I will be able to pass id to its argument later
deletePost
}
export default connect(mapStateToProps, mapDispatchToProps)(PostDetails);
and my the action creator looks like this:
postActions.ts:
export const deletePost = (id: string) => async (dispatch: Dispatch) => {
try {
dispatch(setPostSubmitting(true));
await agent.Posts.delete([id]);
dispatch<IDeletePostAction>({
type: PostActionTypes.DELETE_SINGLE_POST,
payload: {
id,
submitting: false
}
})
} catch (error) {
console.log(error);
dispatch(setPostSubmitting(false));
throw error;
}
}
And it works fine. Hopefully it could help you with your situation.
If you use actions as arrow function so there would be no need to bindActionsCreators, let me do it with a very simple way.
your action should be of arrow function like so:
export const formHandler=({key,value})=>{
/*for checking that you get the values on function call or not*/
console.log(key,value)
return {
type:'formHandler',
payload:{key,value},
}
}
the below action function takes to arguments so you have to pass likewise
const mapDispatchToProps = dispatch=> {
/*this will add the functions as a props to the components
which can be trigger on order to change the state*/
return {
inputHandler: ({key,value})=>dispatch(formHandler({key,value}))
}
}
at the end you can use connect on yourComponent
export default connect(mapStatesToProps,mapDispatchToProps)(yourComponent)
then in your component
you can call the function using
class component
this.props.inputHandler({key:'k',value:2})
functional component
props.inputHandler({key:'k',value:2})
I have an action creator that I'm calling in componentWillMount, the return of that action payload is being assigned to state using setState. However, in componentDidMount I cannot access that property as the async call hasn't completed yet. What is the correct way to access this data in compoentDidMount?
//component
class Dashboard extends Component {
componentWillMount() {
this.setState(this.props.getUser());
}
componentDidMount() {
// this.state.user isn't available yet
}
render(){
return(...);
}
}
//action
export function getUser() {
return async function (dispatch) {
const user = await axios.get(`${API_URL}user?token=${token}`);
return dispatch({
type: USER,
payload: user,
});
}
};
}
Axios returns a promise and you have to wait until it resolves. Then dispatch the success action like this,
export function getUser() {
return function (dispatch) {
axios.get(`${API_URL}user?token=${token}`)
.then(user => {
return dispatch(getUserSuccess(user));
}).catch(error => {
throw error;
});
}
};
export function getUserSuccess(user) {
return {type: USER, payload: user};
}
Also note that you need to have mapStateToProps so it brings the user to your component. Then you can access it using this.props.user within your component. It should be like this.
UserPage.propTypes = {
user: PropTypes.object.isRequired
};
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({getUser}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(UserPage);
Finally you may access the user like this.
render() {
const {user} = this.props;
return(
<div>
<div>user.name</div>
</div>
);
}
You need to use componentWillReceiveProps to do that, for example:
componentWillReceiveProps(nextProps) {
if (nextProps.user !== this.state.user) {
this.setState({
user: nextProps.user
});
}
}
now you can use user inside your component.
Here you can find more information.
I'm new to Redux Saga, coming from Redux Thunk. In some situations, I need to know whether an API call fails or succeeds from inside the view from which I called the action. With Redux Thunk, I would do something like the following.
My component and action creator would look like this:
class MyComponent extends Component {
componentDidMount() {
this.props.actions.fetchItems()
.then(result => {
if (result.status === 'OK') {
console.log('Request succeeded, take particular action in this view')
}
else {
console.log('Request failed, take other action in this view')
}
})
}
render() {
return (
<div>My view</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
return {
items: state.items,
}
}
const mapDispatchToProps = dispatch => {
return {
actions: bindActionCreators({
...actions,
}, dispatch),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(MyComponent)
import * as t from './actionTypes'
import {CALL_API} from '../api'
const xhrGetItems = (params) => (dispatch, getState) => {
const action = {
[CALL_API]: {
type: t.XHR_ITEMS_FETCH,
endpoint: `http://www.example.com/myendpoint`,
method: 'get',
}
}
return dispatch(action)
}
My API middleware catches all actions with a CALL_API property, uses an ajax library to make the appropriate call, then returns a fulfilled promise. My reducer is set up to handle each of the possible states of the api call (success, failure, pending). All the while, I can still check the result of the call in my view where it originated.
So my question is, how can this be accomplished with Redux Saga? Right now my saga api middleware is doing everything it should be doing, but when I call fetchItems() in my view, the result is a plain JS object, so I can't check whether or not it succeeded.
It's possible that I'm going about this completely wrong, too. Any suggestions are very much appreciated.
A common pattern with redux and redux-saga is to create 3 Actions for an API call. In your case I would create:
LIST_ITEMS_START
LIST_ITEMS_SUCCEEDED
LIST_ITEMS_FAILED
Your saga would look something like this:
function* watchListItemsStart() {
yield takeLatest(LIST_ITEMS_START, listItemsStartFlow)
}
function* listItemsStartFlow() {
try {
const result = yield call(api.getItems)
if (result.status === 'OK') {
yield put(LIST_ITEMS_SUCCEEDED, items)
} else {
throw new Error(result.error)
}
} catch (error) {
yield put(LIST_ITEMS_FAILED, error)
}
}
The sagas have cleanly abstracted away the API side-effect. The reducer can concentrate on state management:
switch (action.type) {
case LIST_ITEMS_START:
return {
...state,
isLoading: true,
error: null,
items: [],
}
case LIST_ITEMS_SUCCEEDED:
return {
...state,
isLoading: false,
items: action.payload.items,
}
case LIST_ITEMS_FAILED:
return {
...state,
isLoading: false,
error: action.payload.error.getMessage(),
}
}
Now you have everything you need in your store that can be selected and reacted on in the component.
class MyComponent extends Component {
componentDidMount() {
this.props.fetchItems()
}
render() {
const { isLoading, items, error } = this.props
// Here you can react to the different states.
return (
<div>My view</div>
)
}
}
connect(state => ({
isLoading: itemsSelectors.isLoading(state),
items: itemsSelectors.getItems(state),
error: itemsSelectors.getError(state),
}), {
fetchItems: actions.fetchItems
})(MyComponent)
An important point here is, that your component gets very dumb. It just gets props and does not handle the result of the API call (no promise.then here).
I hope this answer will help you.