React / redux - Onclick react to reducer - javascript

I have a react/redux application and I have the following challenge. When I click on a button I want to save the form data and if a validation error occures in the backend I want to display and message and otherwise redirect.
In my component I have this function
doSubmit = (data) => {
const {actions, linkUtils, myObject} = this.props
return actions.checkAuthentication()
.then(actions.sendMyObject(myObject, data) )
.then(linkUtils.openRoute('nextUrl'))
}
Now the action uses dispatch to invoke the reducer and I can verify the reducer is invoked. I use action types and I use a switch case on the action type:
case types.SAVE_SUCCESS:
return {
...state,
save: true,
}
case types.SAVE_ERROR:
return {
..state,
saved: false,
fieldErrors: action.payload.errors
}
Now in the doSubmit I would like something like this:
return actions.checkAuthentication()
.then(actions.sendMyObject(myObject, data) )
.then( if (saved) { linkUtils.openRoute('nextUrl') } )
So that only when saving of the object is successful a redirect is done.
How can I do this?

You could just switch on the data sendMyObject returns:
return actions.checkAuthentication()
.then(actions.sendMyObject(myObject, data) )
.then((saved) => {
if (saved) {
return linkUtils.openRoute('nextUrl');
} else {
// something else
}
});
For this to work, your promise sendMyObject needs to resolve saved or some other information telling the next promise in the chain whether or not it was successful.

Related

Show a loading page when data is fetching - React Redux

I have 2 parts of the page, left side is the collection of Ids and second part is displaying the data.
First time when the page is loading all the data (of all the Ids
) is displayed. But user can select only 1 id and can see the data of that particular id but it takes time to fetch the data. So between the time of clicking the id and displaying the data - the page shows the previous data (which is fairly confusing) and suddenly the data is changed after the fetch is completed. I want to show the loading page when the id is clicked till the data appears.
How to do that.
On Id select below function is executed and then it goes to the reducer for dispatch action.
onIdSelection(key, obj) {
if (key && key.id) {
this.props.dispatch(actions.fetchDataActionCreator(key.id));
}
}
In the reducer:
export const fetchDataActionCreator = (siteId) => {
return (dispatch) => {
return dispatch({
type: START_FETCH_DEVICES,
payload: api.get({
url: '/api/ndp/v1/collector/telemetry/sites/' + siteId + '/devices',
config: {
apiPrefix: 'none'
}
}).then(payload => dispatch({
type: FINISH_FETCH_DEVICES,
meta: {siteId},
payload})
)
});
};
};
In the reducer:
case START_FETCH_DEVICES:
return {...state, dataLoading: true};
case FINISH_FETCH_DEVICES:
return {...state, dataLoading: false, payload: action.payload};
On the data side:
componentWillReceiveProps(nextProps) {
const {dataUpdated} = nextProps;
if (dataUpdated) {
this.props.dispatch(actions.fetchDataActionCreator(this.props.id));
}
}
Here when I get the data in the nextProps - there is the whole data not on the Id selected.
How to just show the "loading" div till the data is ready for display.
You should add split FetchData action into 2 actions:
1. First type: START_FETCH_DEVICES
2. Second type: FINISH_FETCH_DEVICES
To orchestrate dispatching these two action use action creators.
For example
export const fetchDataActionCreator = (id) => (dispatch) => {
dispatch ({type: START_FETCH_DEVICES});
api.get({
url: '/api/ndp/v1/id/' + id,
config: {
apiPrefix: 'none'
}
}).then(payload => dispatch({
type: FINISH_FETCH_DEVICES,
meta: {id},
payload})
// I'm not sure that api.get can return Promise, but most async fetch api do so
)
}
Now you have fetchDataActionCreator as action creator which returns function tacking dispatch as first argument. When connecting your component to Redux use bindActionCreators to properly wrap fetchDataActionCreator.
Reducer should set some variable in state to notify app that data is loading.
For example
function reducer(state, action) {
switch (action.type) {
case START_FETCH_DEVICES:
return {...state, dataLoading: true}
case FINISH_FETCH_DEVICES:
return {...state, dataLoading: false, payload: action.payload}
default:
return state;
}
}
And your component can display Loading message when state.dataLoading is true

How to handle vuex store to fetch data from rest api?

I'm using Vuex to handle my application state.
I need to make an Ajax Get request to a rest api and then show some objects list.
I'm dispatching an action that loads this data from the server but then I don't know how to handle it on the component.
Now I have this:
//component.js
created(){
this.$store.dispatch("fetch").then(() => {
this.objs = this.$store.state.objs;
})
}
But I don't think that the assignment of the incoming data to the local property is the correct way to handle store data.
Is there a way to handle this better? Maybe using mapState?
Thanks!
There are many ways you can do it, you must experiment and find the one that fits your approach by yourself. This is what I suggest
{ // the store
state: {
something: ''
},
mutations: {
setSomething (state, something) {
// example of modifying before storing
state.something = String(something)
}
},
actions: {
fetchSomething (store) {
return fetch('/api/something')
.then(data => {
store.commit('setSomething', data.something)
return store.state.something
})
})
}
}
}
{ // your component
created () {
this.$store
.dispatch('fetchSomething')
.then(something => {
this.something = something
})
.catch(error => {
// you got an error!
})
}
}
For better explanations: https://vuex.vuejs.org/en/actions.html
Now, if you're handling the error in the action itself, you can simply call the action and use a computed property referencing the value in the store
{
computed: {
something () { // gets updated automatically
return this.$store.state.something
}
},
created () {
this.$store.dispatch('loadSomething')
}
}

how to render returned data on prop change in react

I am having trouble figuring out how to get an API call to re-render to the screen. I have an apiCall function that passes this.state and changes the state via passed ref but it does not trigger a rerender on the props change.
searchBody.js
class SearchBody extends Component {
constructor(props) {
super(props)
const queryString = require('query-string');
const queryTerm = queryString.parse(this.props.location.search);
this.state = { urlSearchTerm: queryTerm.search,
searchTerm: '',
loaded: false,
buttonClicked: null,
apiData: [],
tableHeaders: [],
tableRows: []
}
// check if URL has search term if so pass term for apiCall
if (this.state.urlSearchTerm) {
this.state.searchTerm = this.state.urlSearchTerm
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
// capture input text field to state variable
handleChange = searchTerm => event => {
this.setState({ searchTerm: event.target.value })
//console.log(this.state.searchTerm)
}
// handle form submission
handleSubmit = (event) => {
console.log('Inside HandleSubmit')
console.log('button clicked update url to /?search=' + this.state.searchTerm)
this.props.history.push('/?search=' + this.state.searchTerm);
this.setState({buttonClicked: true})
event.preventDefault();
}
// load search from API if search term is in URL
componentDidMount() {
console.log('Inside compDidMount')
if (this.state.urlSearchTerm){
this.setState({apiData: apiCall(this.state)})
}
}
render() {
const { classes } = this.props;
let table = ''
//check if API has loaded data and show results if true
if (this.state.loaded){
if (this.state.apiData.length === 0 && this.state.buttonClicked){
table = 'No Results Found'
//reset search button State
this.setState({buttonClicked: false})
} else {
table = <TableData tableHead={this.state.tableHeaders} tableData={this.state.tableRows} />
//reset search button State
this.setState({buttonClicked: false})
}
}
return (
<Fragment>
<hr/>
<form /*className={classes.container}*/ noValidate autoComplete="off" onSubmit={this.handleSubmit} >
<TextField
id="search"
label="Search field"
type="search"
/* className={classes.textField}*/
margin="normal"
onChange={this.handleChange('search')}
/>
<Button color='primary' letiant="outlined" type="submit" >Search DB</Button>
</form>
<h1>Results: </h1>
{table}
</Fragment>
)
}
}
export default SearchBody
methods.js
// break API data into arry of data for table component rows.
export const parseTableHeaders = input => {
// console.log(input)
if (input !== undefined && input.length !== 0) {
let head = []
for(let key in input[0]){ head.push(key);}
//console.log(head)
return head
}
}
///break API data into array of headers for table component
export const parseTableRows = (input) => {
let rows = [];
for(let o in input) {
rows.push(Object.values(input[o]));
}
//console.log(head)
return rows
}
//get api data from AWS
export function apiCall(props) {
const searchTerm = props.searchTerm
let apigClientFactory = require('aws-api-gateway-client').default;
const config = {
//apiKey: 'xxxx',
invokeUrl:'https://xxxx.execute-api.us-east-2.amazonaws.com'
}
let apigClient = apigClientFactory.newClient(config);
let params = {
//This is where any header, path, or querystring request params go. The key is the parameter named as defined in the API
// userId: '1234',
search_keyword: searchTerm
};
// Template syntax follows url-template https://www.npmjs.com/package/url-template
let pathTemplate = '/beta/testDB'
let method = 'GET';
let additionalParams = {
//If there are any unmodeled query parameters or headers that need to be sent with the request you can add them here
headers: { },
queryParams: {
search_keyword: searchTerm
}
}
apigClient.invokeApi(params, pathTemplate, method, additionalParams)
.then(function(result){
//This is where you would put a success callback
console.log('apiCall Returned. searchTerm; ', searchTerm)
console.log(result)
props.loaded = true
props.tableHeaders = parseTableHeaders(JSON.parse(result.data))
props.tableRows = parseTableRows(JSON.parse(result.data))
return JSON.parse(result.data)
}).catch( function(result){
//This is where you would put an error callback
})
}
Am I structuring the code wrong? My understanding is that when a prop changes it will force a re-render. Should I pass the "this.state.apiData" into apiCall instead of the entire state like this?
apiCall(this.state.apiData)
This is running within componentDidMount() I believe this is the correct location to call an API, but it's not re-rendering upon callback. I can see in the debugger the state variables are updating as expected.
Should I set a return variable in apiCall() and have the return value update the state within the componentDidMount()? Would this force a re-render once the data is returned?
something like this?
this.setState({apiData: apiCall()})
If I return this.state.apiData from apiCall() and have it parse the table headers and rows inside apiCall, when the state variable is returned will this force an update?
You are running an Async Call to get some rest api Data. Async by definition means you have no idea when your code will finish. This means you will need some type of callback to run after your apiCall has finished.
What you have here is a rest api call that returns a promise object. The promise object is basically an interface for adding callbacks to asynchronous code. I recommend you take one of these options for running a callback after your restApi call.
1.) You can pass a callback function into restApi() as a second parameter. You would invoke this callback as such:
let that = this;
apiCall(props, function(result) {
that.setState({apiData: result});
});
export function apiCall(props, callback) {
...
apigClient.invokeApi(params, pathTemplate, method, additionalParams)
.then(function(result){
...
callback(result);
...
});
...
}
2.) Your other option would be to handle the resolution of your apiCall by latching onto the promise that is created by the api call. When you execute async code the call to the async method immediately returns the promise object which you can return to the calling function to allow the caller to attach callbacks. this may sound a bit confusing, I am not the greatest at explaining things but see the following:
let that = this;
apiCall(props).then(function(result) {
that.setState({apiData: result});
});
export function apiCall(props) {
...
return apigClient.invokeApi(params, pathTemplate, method, additionalParams)
.then(function(result){
...
});
}
The key difference here is that you are returning the actual async call to apigClient.invokeApi. this allows whoever calls apiCall() to attach any callback functionality in the .then() method.
Ultimately you want to make sure you are calling setState when the restApi data actually gets back to the caller. .then() is the easiest way to trigger that call and reliably get the returned result.
Note: you should also look into promises in JS as the .then method can accept 2 parameters, One function that handles the successful return of data and one that handles error reporting.
Eric Hasselbring helped me get to this answer.
I was calling apiCall() and it was not returning anything inside of componentDidMount. I added return before the methods.js apigClient.invokeAPI() so that it would return the result of the function
return apigClient.invokeApi(params, pathTemplate, method, additionalParams)
.then(function(result){
//This is where you would put a success callback
console.log('apiCall Returned. searchTerm; ', searchTerm)
console.log(result)
debugger
props.tableHeaders = parseTableHeaders(JSON.parse(result.data))
props.tableRows = parseTableRows(JSON.parse(result.data))
return JSON.parse(result.data)
}).catch( function(result){
//This is where you would put an error callback
})
then I updated my function call from within SearchBody.js to
componentDidMount() {
console.log('Inside compDidMount')
if (this.state.urlSearchTerm){
apiCall(this.state).then(apiResponse => this.setState({
loaded: true,
apiData: apiResponse
})
)
}
}
So now apiCall returns a result from a promise and the apiCall is now a promise because of the .then function added. this allows the state to be changed inside of componentDidMount so that react sees there was a prop change and re-renders.

Why Redux returns old state when dispatch action before?

I want to know why when I dispatch action before my console log prints old state.
if I do next:
reducer.js
let initialState = { display: false };
const MyReducer = (state = initialState,action) => {
...
case 'SET_DISPLAY':
return { update(state,{ display : {$set: action.display } }) }
break;
default:
return state;
break;
}
ActionCreator.js
let ActionCreator = {
setDisplay(value) {
return(dispatch,getState) {
dispatch({ type: 'SET_DISPLAY',display: value})
}
}
};
app.js
componentDidMount(){
this.props.dispatch(ActionCreator.setDisplay(true))
// expected : true
console.log(this.props.display)
// prints : false.
}
const mapStateToProps = (state) => {
display : state.display
}
but I can see changes in my redux dev-tools console.
PD I use redux-thunk as Middleware.its just example,all my code seems good and works great,but,its a question.
Why console logs old state instead a new state (its ilogic, if I dispatched an action before call logs) I will apreciate your answers,thanks.
This is because you are using redux-thunk and your dispatch happens aynchronously.
this.props.dispatch(ActionCreator.setDisplay(true)) will not set display true immediately.
Since you are not making a network request or anything async in that action why dont you change the action creator to
let ActionCreator = {
setDisplay(value) {
return { type: 'SET_DISPLAY',display: value};
}
};
Now it will happen synchronously. Also dont put console log immediately after dispatching. As redux updates state, old state is not modified. Instead it creates a new state instance with updated value. This new value will be passed as props to your component via connect of react-redux.
Try printing display in render() method, you will see that it is called twice and second one will display true.
First, I would recommend not to rely on the fact that dispatching an action may be synchronous; design as if everything was asynchronous. When eventually you dispatch an async actions, you will be pleased to have your mindset ready for that.
Second, your action creator return a function (you must be using the thunk middleware), which is why you get this behaviour.
componentDidMount(){
startSomethingAsync();
}
componentDidUpdate(){
if (!this.props.asyncCompleted) return;
if(this.props.asyncResultFn) {
this.props.dispatch({ type: ... value: VALUE_CONDITIONAL_TRUE})
}
else{
this.props.dispatch({ type: ... value: VALUE_CONDITIONAL_FALSE})
}
}

Complex state modifications on a reducer

I'm sending from the view to an action this.props.appClasses This is the view:
<div key={key} className='SDWanAppClassTreeItem' onClick={this.props.actions.handleToggleAppClass.bind(this,this.props.appClasses, 0, key)}>
In the action I modify appClasses that I get from the view, I want to send appClasses modified to the reducer to update the appClasses state. But it gives me an error before reach the reducer.
A state mutation was detected
This is the action:
export function handleToggleAppClass(appClasses, parentAppClassId, appClassId) {
// console.log('handleToggleAppClass', appClassId, this.appClasses[appClassId]);
if (appClass.parentAppClassId == 0) {
// Flip the appClass Show Property
appClasses[appClass.appClassId].show = appClasses[appClass.appClassId].show ? false : true;
if (Object.keys(appClasses[appClass.appClassId].children).length !== 0) {
// Regardless if we enable or disabled the parent, all children should be disabled
for (var childKey in appClasses[appClass.appClassId].children) {
appClasses[appClass.appClassId].children[childKey].show = false;
}
}
} else {
// If we are enabling a child, make sure parent is disabled
if (!appClasses[appClass.parentAppClassId].children[appClass.appClassId].show) {
appClasses[appClass.parentAppClassId].show = false;
}
appClasses[appClass.parentAppClassId].children[appClass.appClassId].show = appClasses[appClass.parentAppClassId].children[appClass.appClassId].show ? false : true;
}
dispatch(handleUpdateInitialSourceFetch(appClasses));
return { type: types.TOGGLE_APP_CLASS, appClasses };
}
I already have appClasses in the state, what I wanted was to send the modified appClasses and update the state in the reducer. I want to to it this way because I read that reducers should be simple, and the modification as you can see in the action are complex. Can I do this kind of complex modifications in the reducer? Because here is giving me an error because I'm sending this.props.appClasses. If I do this on the reducer I don't have to send appClasses because is on the state of the reducer.
So in one line, can I make this complex modifications on a reducer instead of make them on the action?
You can create a function that computes the new appClasses state and dispatch two actions sending this new state inside each action.
Then you dispatch this function itself using redux-thunk middleware.
It will look like this:
function handleSomeUserInteraction(appClasses, parentAppClassId, appClassId) {
return function(dispatch) {
//make a copy of appClasses, so you wont change the state directly
let newAppClasses = Object.assign({}, appClasses)
//do all logic work to compute new appClasses
//...
//...
//handleUpdateInitialSourceFetch
dispatch({ type: types.UPDATE_INITIAL_SOURCE_FETCH, appClasses: newAppClasses }
//handleToggleAppClass
dispatch({ type: types.TOGGLE_APP_CLASS, appClasses: newAppClasses })
}
}
You can learn more about this here:
http://jamesknelson.com/can-i-dispatch-multiple-actions-from-redux-action-creators/

Categories