Unable to edit form input using redux - reactjs

I've created a container called siteEdit.js. It handles creating & editing "sites".
I've setup actionCreators that handles taking the form data and submitting it to the API. This works perfectly.
But when you visit the container using a route that contains an ID it will run an actionCreator that will fetch for the "Site" data based on the id param.
This all works as expected, but since I'm using redux, I'm setting the Input value with the props. for example, this.props.title
I'm trying to stay away from using the redux-form package for now.
Container:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {createSite, getSite} from '../../actions/siteActions';
class SiteEdit extends Component {
constructor(props) {
super(props)
this.state = {
title: '',
url: '',
description: '',
approvedUsers: []
}
this.handleSubmit = this.handleSubmit.bind(this)
this.handleInputChange = this.handleInputChange.bind(this)
}
componentWillMount() {
if(this.props.params.id) {
this.props.dispatch(getSite(this.props.params.id))
}
}
handleInputChange(e) {
const target = e.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
}
handleSubmit(e) {
e.preventDefault()
this.props.dispatch(createSite(this.state))
}
render() {
const {title, url, description, approvedUsers} = this.props
return (
<div className="SiteEdit">
<h1>NEW SITE</h1>
<form onSubmit={this.handleSubmit}>
<div className="block">
<label>Site Name</label>
<input
className="input"
type="text"
value={title ? title : this.state.title}
onChange={this.handleInputChange}
name="title" />
</div>
<div className="block">
<label>Site URL</label>
<input
className="input"
type="text"
value={this.state.url}
onChange={this.handleInputChange}
name="url" />
</div>
<div className="block">
<label>Description</label>
<input
className="textarea"
type="textarea"
value={this.state.description}
onChange={this.handleInputChange}
name="description" />
</div>
<div className="block">
<label>Approved Users</label>
<input
className="input"
type="text"
value={this.state.approvedUsers}
onChange={this.handleInputChange}
name="approvedUsers" />
</div>
<button className="button--action">Create</button>
</form>
</div>
)
}
}
const mapStateToProps = (state) => ({
title: state.sites.showSite.title,
url: state.sites.showSite.url,
description: state.sites.showSite.description,
approvedUsers: state.sites.showSite.approvedUsers
})
SiteEdit = connect(mapStateToProps)(SiteEdit)
export default SiteEdit
ActionCreators:
import config from '../config'
import { push } from 'react-router-redux'
const apiUrl = config.api.url
// List all sites
export const LIST_SITES_START = 'LIST_SITES_START'
export const LIST_SITES_SUCCESS = 'LIST_SITES_SUCCES'
export const LIST_SITES_ERROR = 'LIST_SITES_ERROR'
export function sitesListStart(data) {
return { type: LIST_SITES_START, data }
}
export function sitesListSuccess(data) {
return { type: LIST_SITES_SUCCESS, data }
}
export function sitesListError(data) {
return { type: LIST_SITES_ERROR, data }
}
export function listSites() {
return (dispatch) => {
dispatch(sitesListStart())
fetch(`${apiUrl}/listSites`)
.then(res => res.json())
.then(json => {
dispatch(sitesListSuccess(json))
})
.catch(error => {
dispatch(sitesListError)
})
}
}
// Create & Edit Sites
export const CREATE_SITE_START = 'CREATE_SITE_START'
export const CREATE_SITE_SUCESS = 'CREATE_SITE_SUCCESS'
export const CREATE_SITE_ERROR = 'CREATE_SITE_ERROR'
export function siteCreateStart(data) {
return { type: CREATE_SITE_START, data}
}
export function siteCreateSuccess(data) {
return { type: CREATE_SITE_SUCCESS, data}
}
export function siteCreateError(error) {
return { type: CREATE_SITE_ERROR, error}
}
export function createSite(data) {
return (dispatch) => {
dispatch(siteCreateStart())
fetch(`${apiUrl}/createSite`, {
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(res => res.json())
.then(json => {
dispatch(push('/'))
dispatch(siteCreateSuccess())
})
.catch(error => {
dispatch(siteCreateError())
})
}
}
// Get Single Site
export const GET_SITE_START = 'GET_SITE_START'
export const GET_SITE_SUCCESS = 'GET_SITE_SUCCESS'
export const GET_SITE_ERROR = 'GET_SITE_ERROR'
export function getSiteStart(data) {
return { type: GET_SITE_START, data}
}
export function getSiteSuccess(data) {
return { type: GET_SITE_SUCCESS, data}
}
export function getSiteError(error) {
return { type: GET_SITE_ERROR, error}
}
export function getSite(id) {
return (dispatch) => {
dispatch(getSiteStart())
fetch(`${apiUrl}/getSite/${id}`)
.then(res => res.json())
.then(json => {
dispatch(getSiteSuccess(json))
})
.catch(error => {
dispatch(getSiteError())
})
}
}
Reducers:
import {push} from 'react-router-redux'
import {
LIST_SITES_START,
LIST_SITES_SUCCESS,
LIST_SITES_ERROR,
GET_SITE_START,
GET_SITE_SUCCESS,
GET_SITE_ERROR
} from '../actions/siteActions'
const initialState = {
sitesList: {
sites: [],
error: null,
loading: true
},
showSite: {
title: '',
url: '',
description: '',
approvedUsers: [],
loading: true
}
}
export default function (state = initialState, action) {
switch (action.type) {
// List Sites
case LIST_SITES_START:
return Object.assign({}, state, {
sitesList: Object.assign({}, state.sitesList, {
loading: true
})
})
case LIST_SITES_SUCCESS:
return Object.assign({}, state, {
sitesList: Object.assign({}, state.sitesList, {
sites: action.data,
loading: false
})
})
case LIST_SITES_ERROR:
return Object.assign({}, state, {
error: action.error,
loading: false
})
case GET_SITE_START:
return Object.assign({}, state, {
showSite: Object.assign({}, state.showSite, {
loading: true
})
})
case GET_SITE_SUCCESS:
return Object.assign({}, state, {
showSite: Object.assign({}, state.showSite, {
...action.data,
loading: false
})
})
case GET_SITE_ERROR:
return Object.assign({}, state, {
showSite: Object.assign({}, state.showSite, {
error: action.error,
loading: false
})
})
default:
return state
}
}

You are setting the ternary for the value with the props.title taking precedent, like so just to re-iterate -
const { title } = this.props;
...
value={title ? title : this.state.title}
Your onChange logic is correct and is probably updating your components local state correctly, however you still have this.props.title, so it will take precedent in that ternary.
There are a bunch of ways you could handle this, it will depend on order of operations really (that is when props.title will be truthy or not). Assuming you have the title when the component mounts you can do something in the constructor like:
constructor(props) {
super(props)
this.state = {
title: props.title, << set default here
url: '',
description: '',
approvedUsers: []
}
then in the input you only need to set the value to the state title
value={this.state.title}
This will depend on when the props.title value comes into your component of course, if it is not there for mount, this will not work as intended.
You could also pass a function to evaluate all of this for the value of the input as well - inside of which you would more verbosely check the props.title vs the state.title and decide which one to return as your value.
<input value={this.returnTitleValue} .. << something like so
Hope this helps!

Related

Redux: My state is receiving '##redux/INITi.8.g.w.a.m' Redux state not updating

I've been debugging why my state hasn't been changing and noticed this being logged in my reducer:
{ type: '##redux/INITi.8.g.w.a.m' }
This is the store which includes state, action types, reducer, actions:
/* initial state */
import axios from 'axios';
export var usersStartState = {
accountNotVerified: null,
isLoggedIn: false,
error: true,
userAvatar: 'uploads/avatar/placeholder.jpg'
};
/* action types */
export const actionTypes = {
RESET_USER_ACCOUNT_IS_VERIFIED: 'RESET_USER_ACCOUNT_IS_VERIFIED',
USER_ACCOUNT_IS_VERIFIED: 'USER_ACCOUNT_IS_VERIFIED',
USER_ACCOUNT_NOT_VERIFIED: 'USER_ACCOUNT_NOT_VERIFIED',
IS_LOGGED_IN: 'IS_LOGGED_IN',
IS_LOGGED_OUT: 'IS_LOGGED_OUT',
LOAD_USER_AVATAR: 'LOAD_USER_AVATAR',
ERROR_LOADING: 'ERROR_LOADING' // LOAD_MULTER_IMAGE: "LOAD_MULTER_IMAGE"
};
/* reducer(s) */
export default function users(state = usersStartState, action) {
console.log('In users reducer! ', action);
switch (action.type) {
case actionTypes.RESET_USER_ACCOUNT_IS_VERIFIED:
return Object.assign({}, state, { accountNotVerified: null });
case actionTypes.USER_ACCOUNT_IS_VERIFIED:
return Object.assign({}, state, { accountNotVerified: false });
case actionTypes.USER_ACCOUNT_NOT_VERIFIED:
return Object.assign({}, state, { accountNotVerified: true });
case actionTypes.IS_LOGGED_IN:
return Object.assign({}, state, { isLoggedIn: true });
case actionTypes.IS_LOGGED_OUT:
return Object.assign({}, state, { isLoggedIn: false });
case actionTypes.LOAD_USER_AVATAR:
return { ...state, userAvatar: action.data };
case actionTypes.ERROR_LOADING:
return Object.assign({}, state, { error: true });
default:
return state;
}
}
/* actions */
export const resetUserAcoountVerified = () => {
return { type: actionTypes.RESET_USER_ACCOUNT_IS_VERIFIED };
};
export const userHasBeenVerified = () => {
return { type: actionTypes.USER_ACCOUNT_IS_VERIFIED };
};
export const userHasNotBeenVerified = () => {
return { type: actionTypes.USER_ACCOUNT_NOT_VERIFIED };
};
export const logInUser = () => {
return { type: actionTypes.IS_LOGGED_IN };
};
export const logOutUser = () => {
axios
.get('/users/logout')
.then(response => {
if (response.status === 200) {
console.log('You have been logged out!');
}
})
.catch(function(error) {
if (error.response.status === 500) {
console.log('An error has occured');
}
});
return { type: actionTypes.IS_LOGGED_OUT };
};
export const loadAvatar = data => {
console.log('in load avatar ', data);
return { type: actionTypes.LOAD_USER_AVATAR, data: data };
};
export const errorLoading = () => {
return { type: actionTypes.ERROR_LOADING };
};
And this is my component:
import { useState } from 'react';
import { Card, Icon, Image, Segment, Form } from 'semantic-ui-react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { loadAvatar } from '../../store/reducers/users/index';
import axios from 'axios';
function ImageUploader({ userAvatar }) {
var [localUserAvatar, setLocalUserAvatar] = useState(userAvatar);
function fileUploader(e) {
e.persist();
var imageFormObj = new FormData();
imageFormObj.append('imageName', 'multer-image-' + Date.now());
imageFormObj.append('imageData', e.target.files[0]);
loadAvatar('foo');
axios({
method: 'post',
url: `/users/uploadmulter`,
data: imageFormObj,
config: { headers: { 'Content-Type': 'multipart/form-data' } }
})
.then(data => {
if (data.status === 200) {
console.log('data ', data);
console.log('path typeof ', typeof data.data.path);
loadAvatar('foo');
setLocalUserAvatar('../../' + data.data.path);
}
})
.catch(err => {
alert('Error while uploading image using multer');
});
}
Here are
console.log('userAvatar in imageUploader ', userAvatar);
console.log('Date.now() line 44 in imageUploader ', Date.now());
console.log('localUserAvatar in imageUploader ', localUserAvatar);
console.log('Date.now() line 46 in imageUploader ', Date.now());
console.log("loadAvatar('barbar') ", loadAvatar('barbar'));
return (
<>
<Segment>
<Card fluid>
<Image src={localUserAvatar} alt="upload-image" />
<Segment>
<Form encType="multipart/form-data">
<Form.Field>
<input
placeholder="Name of image"
className="process__upload-btn"
type="file"
content="Edit your Avatar!"
onChange={e => fileUploader(e)}
/>
</Form.Field>
</Form>
</Segment>
<Card.Content>
<Card.Header>Charly</Card.Header>
<Card.Meta>
<span className="date">Joined in 2015</span>
</Card.Meta>
<Card.Description>Charly</Card.Description>
</Card.Content>
<Card.Content extra>
<a>
<Icon name="user" />
22 Friends
</a>
</Card.Content>
</Card>
</Segment>
</>
);
}
function mapStateToProps(state) {
const { users } = state;
const { userAvatar } = users;
return { userAvatar };
}
const mapDispatchToProps = dispatch => bindActionCreators({ loadAvatar }, dispatch);
export default connect(
mapStateToProps,
mapDispatchToProps
)(ImageUploader);
From the logs you can see loadAvatar the dispatcher, gets fired in the component and in the store...
But the state in the store never changes....
Also other states do change correctly...Like for example I have a Modal and that updates nicely.
Any help would be appreciated as what { type: '##redux/INITi.8.g.w.a.m' } and why my state is not updating?
Redux dispatches that action as an internal initialization step:
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })
It's specifically so that your reducers will see the action, not recognize the action type, and return their default state, thus defining the initial overall app state contents.

In React-Redux app, trying to pre-fill the default value in Edit Component with current api calls value

In my reactredux app, There is a peculiar situaton where I am currently trying to pre-fill my input field in Edit component but the thing is ,Its getting filled but not with current api calls but with last api calls that happens inside componentDidMount().I tried to clear the object too but all in vain. Kindly suggest
ProfileEdit.js component
import React, { Component } from 'react';
import '../App.css';
import {connect} from 'react-redux';
import {profileFetchDetail} from '../actions/profile';
import { withRouter } from 'react-router-dom';
class ProfileEdit extends Component {
constructor(props){
super(props);
this.state = {
firstName: '',
lastName: '',
emailId: '',
}
}
componentDidMount(){
const id = this.props.match.params.id;
this.props.profileFetchDetail(id);
this.setState({
firstName: this.props.profile.firstName,
lastName: this.props.profile.lastName,
emailId: this.props.profile.emailId
})
}
render() {
const {firstName,lastName,emailId} = this.state;
console.log(this.props.profile);
return (
<form name="profileCreate" className="profile-form">
<div className="form-control">
<label htmlFor="firstName">First Name</label><br/>
<input type="text" id="firstName" defaultValue={firstName}
name="firstName" placeholder="First Name"
/>
</div>
<div className="form-control">
<label htmlFor="LastName">Last Name</label><br/>
<input type="text" id="LastName" defaultValue={lastName}
name="lastName" placeholder="Last Name"
/>
</div>
<div className="form-control">
<label htmlFor="email">Email</label><br/>
<input type="email" id="email" defaultValue={emailId}
/>
</div>
<div className="form-action">
<input type="submit" value="Click here" />
</div>
</form>
)
}
}
const mapStateToProps = state => ({
profile: state.profile.profile
})
export default connect(mapStateToProps, {profileFetchDetail})(withRouter(ProfileEdit));
Action creators, here profileFetchDetail() is of our interest
import api from '../api';
// profile create
export const profileAdd = (formData, history) => async dispatch => {
console.log(formData);
const config = {
headers : { 'Content-Type': 'application/json' }
}
try {
await api.post('/api/profile/create', formData, config);
dispatch({ type: 'CREATE_PROFILE', payload: formData });
history.push('/list');
} catch (error) {
console.log(error);
}
}
// profile get all list
export const profileFetch = () => async dispatch => {
try {
const res = await api.get('/api/profile/list');
dispatch({ type: 'GET_PROFILE', payload: res.data });
} catch (error) {
console.log(error);
}
}
// profile get single list item corresponding to id
export const profileFetchDetail = (id) => async dispatch => {
dispatch({ type: 'CLEAR_PROFILE' });
try {
const res = await api.get(`/api/profile/${id}`);
dispatch({ type: 'GET_PROFILE_SINGLE', payload: res.data });
} catch (error) {
console.log(error);
}
}
// profile delete
export const profileDelete = (id) => async dispatch => {
dispatch({ type: 'CLEAR_PROFILE' });
try {
const res = await api.delete(`/api/profile/${id}/delete`);
dispatch({ type: 'DELETE_PROFILE', payload: res.data });
dispatch(profileFetch());
} catch (error) {
console.log(error);
}
}
ProfileReducers
const initialState = {
profiles:[],
profile:{}
};
export default (state = initialState, action) => {
switch (action.type) {
case 'CREATE_PROFILE':
return {...state, profiles: [...state.profiles, action.payload]};
case 'GET_PROFILE':
return {...state, profiles: action.payload};
case 'GET_PROFILE_SINGLE':
return {...state, profile: action.payload};
case 'CLEAR_PROFILE':
return {...state, profile: {}};
case 'DELETE_PROFILE':
return {...state, profiles: state.profiles.filter( item => item._id !== action.payload) };
default:
return state;
}
};
First time it loads perfectly on clicking edit button then the issue happens on clicking any other edit button.Pasting the example of 2 api calls inside componentDidMount().
In the attached image, the last api request in sequence displayed is the currently made request.Api made detail
Note: Till now I am not trying to edit it just prefilling data,where issue happening.

Why is the state change not making it's way to the reducer?

I have a file called Login.js in the file I call this.props.onUserLogin and pass the users auth token. I can verify that I am getting the token from the api call via the console log. When I get to the reducer I loose the value from the state somehow. I can verify that with the console log as well. I am trying to figure out why my state.loginToken is empty in the reducer.
Login.js
import React, {Component} from 'react';
import * as actionTypes from '../Store/actions';
import UserInfoButton from '../UserInfoButton/UserInfoButton';
import { connect } from 'react-redux';
const axios = require('axios');
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
};
}
handleChange = (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
})
}
handleLogin = (event) => {
axios.post('http://127.0.0.1:8000/api/user/token/', {
email: this.state.email,
password: this.state.password,
}).then( (response) => {
console.log('response: ' + response.data.token);
this.props.onUserLogin(response.data.token);
})
.catch( (error) => {
console.log(error)
})
event.preventDefault();
}
render() {
const isLoggedIn = this.props.loginToken;
let userButton;
if(isLoggedIn) {
userButton = <UserInfoButton/>
} else {
userButton = "";
}
return (
<div>
<form onSubmit={this.handleLogin}>
Email:<br/>
<input type="text" value={this.state.email} onChange={this.handleChange} name="email"/><br/>
Password:<br/>
<input type="password" value={this.state.password} onChange={this.handleChange} name="password"/><br/>
<br/>
<input type="submit" value="Submit"></input>
</form>
<div>{this.props.loginToken}</div>
<br/>
<div>{userButton}</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
loginToken: state.loginToken
};
}
const mapDispatchToProps = dispatch => {
return {
onUserLogin: (userToken) => dispatch( { type: actionTypes.USER_LOGIN_ACTION, loginToken: userToken } ),
};
}
export default connect(mapStateToProps,mapDispatchToProps)(Login);
reducer.js
import * as actionTypes from './actions';
const initialState = {
loginToken: '',
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case actionTypes.USER_LOGIN_ACTION:
console.log('action: ' + state.loginToken);
return {
...state,
loginToken: state.loginToken,
}
default:
return state;
}
};
export default reducer;
Basically a typo inside of your reducer - You're looking for the loginToken inside of state instead of the action.
case actionTypes.USER_LOGIN_ACTION:
console.log('action: ' + action.loginToken); // not state.loginToken
return {
...state,
loginToken: action.loginToken, // not state.loginToken
}

How to use data from form to get data from api?

I have a form in react where I'm asking for the last 8 of the VIN of a car. Once I get that info, I want to use it to get all the locations of the car. How do I do this? I want to call the action and then display the results.
Added reducer and actions...
Here is what I have so far...
class TaglocaByVIN extends Component {
constructor(props){
super(props);
this.state={
searchvin: ''
}
this.handleFormSubmit=this.handleFormSubmit.bind(this);
this.changeText=this.changeText.bind(this);
}
handleFormSubmit(e){
e.preventDefault();
let searchvin=this.state.searchvin;
//I want to maybe call the action and then display results
}
changeText(e){
this.setState({
searchvin: e.target.value
})
}
render(){
return (
<div>
<form onSubmit={this.handleFormSubmit}>
<label>Please provide the last 8 characters of VIN: </label>
<input type="text" name="searchvin" value={this.state.searchvin}
onChange={this.changeText}/>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default TaglocaByVIN;
Here are my actions:
export function taglocationsHaveError(bool) {
return {
type: 'TAGLOCATIONS_HAVE_ERROR',
hasError: bool
};
}
export function taglocationsAreLoading(bool) {
return {
type: 'TAGLOCATIONS_ARE_LOADING',
isLoading: bool
};
}
export function taglocationsFetchDataSuccess(items) {
return {
type: 'TAGLOCATIONS_FETCH_DATA_SUCCESS',
items
};
}
export function tagformsubmit(data){
return(dispatch) =>{
axios.get(`http://***`+data)
.then((response) => {
dispatch(taglocationsFetchDataSuccess);
})
};
}
reducer:
export function tagformsubmit(state=[], action){
switch (action.type){
case 'GET_TAG_FORM_TYPE':
return action.taglocations;
default:
return state;
}
}
This is an easy fix but it will take a few steps:
Set up an action
Set up your reducer
Fetch and Render data in component
Creating the Action
The first thing, you need to set up an action for getting data based on a VIN. It looks like you have that with your tagformsubmit function. I would make a few adjustments here.
You should include a catch so you know if something went wrong, change your function param to include dispatch, add a type and a payload to your dispatch, and fix the string literal in your api address. Seems like a lot but its a quick fix.
Update your current code from this:
export function tagformsubmit(data){
return(dispatch) =>{
axios.get(`http://***`+data)
.then((response) => {
dispatch(taglocationsFetchDataSuccess);
})
};
}
to this here:
//Get Tag Form Submit
export const getTagFormSubmit = vin => dispatch => {
dispatch(loadingFunctionPossibly()); //optional
axios
.get(`/api/path/for/route/${vin}`) //notice the ${} here, that is how you use variable here
.then(res =>
dispatch({
type: GET_TAG_FORM_TYPE,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_TAG_FORM_TYPE,
payload: null
})
);
};
Creating the Reducer
Not sure if you have already created your reducer. If you have you can ignore this. Creating your reducer is also pretty simple. First you want to define your initial state then export your function.
Example
const initialState = {
tags: [],
tag: {},
loading: false
};
export default (state=initialState, action) => {
if(action.type === GET_TAG_FORM_TYPE){
return {
...state,
tags: action.payload,
loading: false //optional
}
}
if(action.type === GET_TAG_TYPE){
return {
...state,
tag: action.payload,
}
}
}
Now that you have your action and reducer let's set up your component.
Component
I'm going to assume you know all of the necessary imports. At the bottom of your component, you want to define your proptypes.
TaglocaByVIN.propTypes = {
getTagFormSubmit: PropTypes.func.isRequired,
tag: PropTypes.object.isRequired
};
mapStateToProps:
const mapStateToProps = state => ({
tag: state.tag
});
connect to component:
export default connect(mapStateToProps, { getTagFormSubmit })(TaglocaByVIN);
Update your submit to both pass data to your function and get the data that is returned.
handleFormSubmit = (e) => {
e.preventDefault();
const { searchvin } = this.state;
this.props.getTagFormSubmit(searchvin);
const { tags } = this.props;
tags.map(tag => {
//do something with that tag
}
Putting that all together your component should look like this (not including imports):
class TaglocaByVIN extends Component {
state = {
searchvin: ""
};
handleFormSubmit = e => {
e.preventDefault();
const { searchvin } = this.state;
this.props.getTagFormSubmit(searchvin);
const { tags } = this.props.tag;
if(tags === null){
//do nothing
} else{
tags.map(tag => {
//do something with that tag
});
};
}
changeText = e => {
this.setState({
searchvin: e.target.value
});
};
render() {
return (
<div>
<form onSubmit={this.handleFormSubmit}>
<label>Please provide the last 8 characters of VIN: </label>
<input
type="text"
name="searchvin"
value={this.state.searchvin}
onChange={this.changeText}
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
TaglocaByVIN.propTypes = {
getTagFormSubmit: PropTypes.func.isRequired,
tag: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
tag: state.tag
});
export default connect(
mapStateToProps,
{ getTagFormSubmit }
)(TaglocaByVIN);
That should be it. Hope this helps.

React/Redux: GET axios call causing infinite loop, onSubmit method is not causing visual output to the user

After clicking on submit button in form with addBook action nested inside, data is passed into DB, but not outputting imidiately to the screen (i have to refresh page each time to output newly added data from DB).
I tried to put my getBooks function into componentDidUpdate() lifecycle hook, but it causes infinite loop.
getBooks action
export const getBooks = () => dispatch => {
axios.get('https://damianlibrary.herokuapp.com/library')
.then(res => dispatch({
type: GET_BOOKS,
payload: res.data
}))
};
addBook action
export const addBook = book => dispatch => {
axios.post('https://damianlibrary.herokuapp.com/library', book)
.then(res => dispatch({
type: ADD_BOOK,
payload: res.data
}))
};
bookReducer
const initialState = {
books: []
}
export default function(state = initialState, action) {
switch(action.type) {
case GET_BOOKS:
return {
...state,
books: action.payload
};
case DELETE_BOOK:
return {
...state,
books: state.books.filter(book => book.book_id !== action.payload)
};
case ADD_BOOK:
return {
...state,
eventDetails: [action.payload, ...state.books]
};
default:
return state;
}
}
Form.js component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addBook, getBooks } from '../../actions/bookActions';
import './Form.css';
class Form extends Component {
state = {
name: '',
author: '',
isbn: ''
}
componentDidMount () {
this.props.getBooks();
}
onChangeHandler = (e) => {
this.setState({ [e.target.name]: e.target.value })
};
onSubmitHandler = (e) => {
const {name, author, isbn} = this.state
const newBook = {
name: name,
author: author,
isbn: isbn
}
this.props.addBook(newBook);
this.setState({
name: '',
author: '',
isbn: ''
})
e.preventDefault();
}
render() {
const { name, author, isbn } = this.state;
return (
<div className='formContainer'>
<div className='form'>
<form className='bookForm' onSubmit={this.onSubmitHandler.bind(this)}>
<div className='inputs'>
<input
type='text'
name='name'
placeholder='Book name'
onChange={this.onChangeHandler}
value={name}/>
<input
type='text'
name='author'
placeholder='Book author'
onChange={this.onChangeHandler}
value={author}/>
<input
type='text'
name='isbn'
placeholder='ISBN'
onChange={this.onChangeHandler}
value={isbn}/>
</div>
<div className='buttonSpace'>
<button>Add book</button>
</div>
</form>
</div>
</div>
)
}
}
const mapStateToProps = (state) => ({
book: state.book
});
export default connect(mapStateToProps, { addBook, getBooks })(Form);
In the reducer you should return an updated reducer object. In the ADD_BOOK you add new property eventDetails. Do you use it somewhere?
Your new reducer look that: { books: [ initial book list ], eventDetails: [initial book list and new book]}. When you change eventDetails to books in ADD_BOOK, your book list will update without additional requests.

Resources