React-redux: actions with variables - reactjs

so i've this action:
export const getMovieDettails = (id) => (dispatch) => {
dispatch({type: GET_MOVIE_DETTAIL_LOADING})
fetch(`api/movieId/${id}`)
.then(response => response.json())
.then(movie => dispatch({type: GET_MOVIE_DETTAIL_LOADED, payload: movie}))
.catch(err => dispatch({type: GET_MOVIE_DETTAIL_FAIL, payload: "Error with API"}));
}
on App.js i do this:
const mapDispatchToProps = (dispatch) => {
return {
onGetShows: () => dispatch(getHomeShows()),
onGetMovies: () => dispatch(getMoviesShows()),
onGetSeries: () => dispatch(getSeriesShows()),
onGetMovieDettail: (id) => dispatch(getMovieDettails(id))
}
}
and then pass the function to
<Route path="/movie/:id" render={(routeProps) => <MovieDettailsRouter key={routeProps.match.params.id} data={this.props.movieDettail} movieDettail={this.props.onGetMovieDettail} {...routeProps}/>}/>
so on MovieDettailRouter i do
componentDidMount() {
console.log(this.props.match.params.id);
this.props.movieDettail(this.props.match.params.id);
}
but not work, how can pass the function with paramaters, for good experience i call all state and actions on App.js and then pass data on the component, for call without paramaters i don't have problems, everything it's fine, but here where i need to get the params for the actions 've problem, on v1 of App i don't use actions for this page, i use only for page without actions with params, but on this update i decide to use redux for every call on server and i need to understan how can pass an actions with params on components
Edit: Add the state on MovieDettailRoute

You don't seem to be passing the state to the MovieDetailsRouter component.
After the data is fetched, dispatched and the store changes all the components that are listening for that part of the store will update.
The component needs to have access to the state to get the data.
You can pass the data to the component by getting the part of the state that your action will update with mapStateToProps in the App component, and then pass it to the MovieDetailRouter

Related

Dispatching Redux-Toolkit Actions in other Actions

so i'm new to redux-toolkit and this is my final goal: to dispatch an action which updates loading state, and then call the login function, after this is done successfully dispatch another action which again updates the loading state, how can i achieve this considering i need to update (dispatch) the loading action for almost every reducer i have?
i know that in redux i can simply use dispatch in the function as my second argument and then use it but im not familiar how this works in redux-toolkit.
this is my code in redux:
export const login = (model) => (dispatch) => {
dispatch(isRequesting());
return (
authService
.ssoLogin(model)
.then((result) => {
dispatch(ssoIsAuthenticated());
localStorageService.setKey(storage_key.is_auth, true);
dispatch(isRequested());
history.push(routes.home);
})
.catch((error) => {
utilService.handleError(error);
dispatch(errorOccurred());
})
);
};

Redux action dispatch

In my redux action, I have one action will be called by another two actions, code is below:
export const addParticipantFromPopupRequest = (participant, project_id, currentStep) => async (dispatch) => {
const result = await addParticipant(participant)
dispatch({ type: PARTICIPANT_ADD, payload: result })
dispatch(updateProjectStep(project_id, currentStep))
}
export const handleFinalStep = (projectId, currentStep) => async (dispatch) => {
dispatch(updateProjectStep(projectId, currentStep))
}
const updateProjectStep = (projectId, currentStep) => async (dispatch, getState) => {
dispatch({ type: MODAL_STATUS_CHANGE, payload: { projectId, currentStep } })
dispatch({ type: PROJECT_PROCESS_LIST_UPDATE, payload: { project_id: projectId, currentStep } })
const { projectsProcessListsReducer } = getState()
localStorage.setItem("projectsProcessLists", JSON.stringify(projectsProcessListsReducer))
}
If I dont' use dispatch when call updateProjectStep, the addParticipantFromPopupRequest and handleFinalStep cannot run correct.
My question is can I call dispatch actions in this way and is it correct? Why I need the "dispatch" when I call updateProjectStep in another actions rather than call function name directly?
My question is can I call dispatch actions in this way and is it correct?
Yes. You should always call with the dispatch.
Why I need the "dispatch" when I call updateProjectStep in another actions rather than call function name directly?
If you call updateProjectStep directly without dispatch, it will become a normal js function call and your store won't be aware of it. Dispatch is the only way to trigger a state change in store.
In redux the store is single source of truth, the dispatch you are using is actually comes from store (store.dispatch).
If you call a function normally then it won't be aware by the store. That action won't pass through the middlewares (thunk/saga) that store is aware of and won't do the store update via reducers.
If store is not updated, your components won't receive any updates. Eventually your UI won't re-render.
You can find more about dispatch here.

next.js mapStateToProps, mapDispatchToProps and getInitialProps

i am currently still trying to wrap my head around redux when using next.js and i am not sure what is the best way to use redux with next. I am used to using mapDispatchToProps for my actions and mapStateToProps for my props. After some research i am now using next-redux-wrapper in my _app.js like recommended but now i am fighting with how to best get my props and dispatch my actions. I had look at a few examples and practices and now have a counter component based on one of these examples.
class Counter extends Component {
increment = () => {
const {dispatch} = this.props
dispatch(incrementCount())
}
decrement = () => {
const {dispatch} = this.props
dispatch(decrementCount())
}
reset = () => {
const {dispatch} = this.props
dispatch(resetCount())
}
render () {
const { count } = this.props
return (
<div>
<h1>Count: <span>{count}</span></h1>
<button onClick={this.increment}>+1</button>
<button onClick={this.decrement}>-1</button>
<button onClick={this.reset}>Reset</button>
</div>
)
}
}
function mapStateToProps (state) {
const {count} = state.counter;
return {count};
}
export default connect(mapStateToProps)(Counter)
Most examples i have seen so far do something similar to this or only dispatch actions in getInitialProps. Is there a reason to do it this way and not use mapDispatchToProps?
Cause this work perfectly fine as well:
export default connect(null, {authenticate})(Signin);
Dispatching actions in getIntialProps seems to have some drawback (or i made some mistakes), cause they do not get executed again when the props change. In my user-profile component i get the current user based on a token from the redux store like this:
const Whoami = ({isAuthenticated, user}) => (
<Layout title="Who Am I">
{(isAuthenticated && user && <h3 className="title is-3">You are logged in as <strong className="is-size-2 has-text-primary">{user}</strong>.</h3>) ||
<h3 className="title is-3 has-text-danger ">You are not authenticated.</h3>}
</Layout>
);
Whoami.getInitialProps = async function (ctx) {
initialize(ctx);
const token = ctx.store.getState().auth.token;
if(token) {
const response = await axios.get(`${API}/user`, {headers: {
authorization: token
}});
const user = response.data.user;
return {
user
};
}
}
const mapStateToProps = (state) => (
{isAuthenticated: !!state.auth.token}
);
export default connect(mapStateToProps)(Whoami);
This works perfectly fine for the initial page-load or when navigating there one the client, but when the token expires or i logout the page does not reflect that without reload or navigating there again without my mapStateToProps. But it seems super clunky to split the concern over 2 seperate functions. But i cant find a cleaner way to do it.
Thanks in advance
About mapDispatchToProps:
It is better to use mapDispatchToProps at least because it is easier to test: you can just pass a mock function to your component. With using this.props.dispatch to dispatch some imported actions it can be much harder.
About getInitialProps:
This answer may be helpful:
GetInitialProps: is provided by Next.js and it is NOT always triggered, so be careful with that, it happen when you wrap 1 component inside another. If the parent Component has GetInitialProps, the child's GetInitialProps will never be triggered, see this thread for more info.
I found some answers to my questions after playing around with next a bit more. For pages where the data does not change after intial load, i could get rid of mapStateToProps by rewriting my thunks a bit to return the dispatches and only use getInitialProps like this:
export function fetchShow(id) {
return (dispatch) => {
dispatch({ type: actionTypes.FETCH_SHOW_REQUESTED,id});
// we need to return the fetch so we can await it
return fetch(`http://api.tvmaze.com/shows/${id}`)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
//dispatch(itemsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((data) => dispatch({type: actionTypes.FETCH_SHOW_SUCEEDED,id, show: data, time: Date.now() }))
.catch(() => dispatch({ type: actionTypes.FETCH_SHOW_ERROR,id }));
};
}
Post.getInitialProps = async function ({store, isServer, pathname, query}) {
const { id } = query;
const {show} = await store.dispatch(fetchShow(id));
return {show};
}
For pages where the data should update upon store changes i am not sure yet. My current idea is to try and write a helper function that will be called from both getInitialProps and mapStateToProps to reduce code duplication but i am not sure yet.

How to pass argument to React Redux middleware inside mapDispatchToProps

The situation is I am creating a single board which will hold a collection of note cards (each note has an id, title and body), and each note card will have a button to delete it. Also the application will be syncing with firebase, so my main question is how to pass arguments to middlewares AND do it inside of mapDispatchToProps. The following is my code to point out where my success with middleware and where I am currently blocked.
To hydrate the app on startup, I dispatch a middleware function that gets the data from firebase, and then dispatches actions handled by reducers and finally gets updated by the container/presentation component.
Middleware function:
export function hydrateApp(dispatch) {
dispatch({type: 'PENDING'});
fireBaseDBRef.once('value').then(snapshot => {
let firebaseNotes = snapshot.val()
let notes = [];
// populate notes using firebaseNotes, nothing exciting
dispatch({ type: 'DONE', notes: notes });
// the 'DONE' action.type is handled by the reducer and passes data
// to the container component successfully
}).catch(e => {
dispatch({type: 'ERROR', error: e});
});
}
Container component:
const mapStateToProps = state => {
return {
notes: state.boardReducer.notes
};
};
const mapDispatchToProps = dispatch => {
return {
addNote: () => {
dispatch(boardMiddleware.createNote);
}
};
};
const BoardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(BoardPresentation);
So far so good, and this is what I added to the same middleware and container component files to handle delete scenarios.
Middleware function:
export function deleteNote(id) {
return (dispatch) => {
dispatch({type: 'PENDING'});
//firebase stuff happening here
dispatch((type: 'DONE'});
}
}
Container component:
const mapDispatchToProps = dispatch => {
return {
addNote: () => {
dispatch(boardMiddleware.createNote);
},
removeNote: (id) => {
dispatch(boardMiddleware.deleteNote(id));
}
};
};
The problem is that deleteNote gets called non-stop on startup, I don't even need to click the button.
I know the code presented may not make a whole bunch of sense, but the crux of my problem is that I need to some how pass an id to the middleware function when the user clicks on the button, and because I'm passing the function as a prop, it for some reasons decides to just call it a million times.
I could call boardMiddleware.deleteNote function inside the presentation component just like the examples in the official redux page do, but I'm wondering if there is a way of doing it the way I'm trying to do.
I also thought about binding the argument into the middleware function, but that also doesn't feel right, something like this
removeNote: (id) => {
dispatch(boardMiddleware.deleteNote.bind(id));
}
Thanks for any help in advance!

redirect from component level after specific dispatch - redux thunk

I have a fairly simple use case, but having a hard to find the appropriate answer. I'm using React,Redux,React Router & redux thunk middleware.
Lets say, I have two module food-tags & food. These modules have individual create,list,edit page/component. In practical use case, food-tags have no special value. Whenever a food object is created, separated tags are inserted into the food object's tags property.
General use case is that, after any item is created successfully, react router redirects it to the list page.
whenever i'm calling the createTag action from food-tag module, I can do it in a hacky way. like just after the success dispatch, i can call
browserHistory.push('/dashboard/tags')
this leads me to a problem where i can create food-tag inline from the food create component. Codes are given below
actions.js
export function createTag(tag) {
return function (dispatch) {
axios.post(API_URL + 'api/tags', tag)
.then((response) => {
// I CAN DO REDIRECT HERE,BUT THIS CAUSES THE PROBLEM
dispatch({type: 'TAG_CREATE_RESOLVED', payload:response});
toastr.success('Tag created Successfully.......!');
})
.catch((err) => {
dispatch({type: 'TAG_CREATE_REJECTED', payload: err});
toastr.warning(err.message);
})
}
}
component/container.js
createTag () {
//validatation & others....
this.props.createTag(tag)
}
react-redux connection
function mapDispatchToProps (dispatch) {
return bindActionCreators({
createTag: createTag
}, dispatch)
}
Almost same pattern in food/create.js
$('#food-tags').select2(select2settings).on('select2:selecting', function (event) {
let isNewTagCreated = event.params.args.data.newOption,
name = event.params.args.data.text;
if (isNewTagCreated && name !== '') {
reactDOM.props.createTag({name}); // reactDOM = this context here
}
});
What I want basically that, I want to get access in the component level which action type is dispatching so that i can redirect from component & show notifications as well instead of action thunk. May be i'm not thinking in the proper way. there could be a dead simple work around.
It's good to know that redux-thunk passed out return value from the function. So you can return the promise from the action creator and wait until it will be finished in you component code
export function createTag(tag) {
return function (dispatch) {
return axios.post(API_URL + 'api/tags', tag) // return value is important here
.then((response) => dispatch({type: 'TAG_CREATE_RESOLVED', payload:response}))
.catch((err) => {
dispatch({type: 'TAG_CREATE_REJECTED', payload: err})
throw err; // you need to throw again to make it possible add more error handlers in component
})
}
}
Then in your component code
createTag () {
this.props.createTag(tag)
.then(() => {
toastr.success('Tag created Successfully.......!');
this.props.router.push() // I assume that you have wrapped into `withRouter`
})
.catch(err => {
toastr.warning(err.message);
});
}
Now you have proper split up between action logic and user interface.

Resources