I'm trying to fetch props into my componentDidUpdate method.
Sadly second action from .then block doesn't dispatch.
I have the following:
My AddPerson.js which is a form to add Person. There I have following states:
constructor(props) {
super(props);
this.state = {
loading: false,
firstName: '',
secondName: '',
email: '',
date: '',
};
}
Whole is connected to redux:
function mapDispatchToProps(dispatch) {
return {
addPerson: data => dispatch(addPerson(data))
};
}
const mapStateToProps = state => {
return { data: state.data };
};
const Person = connect(
mapStateToProps,
mapDispatchToProps
)(AddPerson);
export default Person;
Then I have action dispatcher like that:
export const addPerson = (payload) => {
return dispatch => {
dispatch(addTodoStarted());
axios
.post(PATH + '/attendant', {
payload,
})
.then(res => {
dispatch(addTodoSuccess(res));
})
.catch(err => {
dispatch(addTodoFailure(err));
});
};
};
const addTodoSuccess = payload => ({
type: ADD_PERSON,
data: {
payload
}
});
const addTodoStarted = () => ({
type: ADD_PERSON,
data:
"loading"
});
const addTodoFailure = error => ({
type: ADD_PERSON,
data: {
error
}
});
And my reducer:
function reducer(state = {} , action) {
switch (action.type) {
case ADD_PERSON:
return Object.assign({}, state, {
data: action.data,
})
default:
return state;
}
}
export default reducer;
When fetch happens in the action, there is firstly dispatched type of action loading then after promise solves I want to dispatch action that is in .then block. What am I missing?
EDIT:
My componentDidUpdate looks like that:
componentDidUpdate(prevProps) {
console.log(prevProps)
if (prevProps.data !== this.state.data) {
console.log(prevProps.data)
}
}
Related
I am having trouble to dispatch component state value to redux store .My idea is that i will dispatch to redux store only API response data and after can access it from different screens:
When i try it , i am getting error: ReferenceError: action is not defined
at loginSuccessful (http://packager.yz-9ze.anonymous.easy…}
Here is my component code:
import { loginSuccessful } from "../../redux/actions/loginacction";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
user: {
login: "",
password: ""
},
activity: false,
response: {},
showpassword: true
};
}
_login = async () => {
if (this.state.user.login !== "" && this.state.user.password !== "") {
console.log(this.state);
this.setState({ activity: true });
try {
await Axios.post(LoginAPI, this.state, {
headers: { appversion: 1.4 }
})
.then(response => {
console.log(response);
const status = response.status;
if (status === 200) {
this.setState({ activity: false });
this.setState({ response: response.data });
const userData = this.state.response;
this.props.dispatch(loginSuccessful(userData));
//this.props.login_api_call;
//this.props.navigation.navigate("CreateMove");
console.log(this.state);
}
})
.catch(error => {
console.log({ error });
this.setState({ activity: false });
Alert.alert("Error", error.response.data.error);
});
} catch (error) {}
} else {
Alert.alert("Support", "Enter Email and Password");
}
};
const mapStateToProps = state => {
return {
userData: state.loginCredentialsReducer
};
};
// const mapDispatchToProps = dispatch => {
// return {
// login_api_call: userData => dispatch(loginSuccessful(userData))
// };
// };
export default connect(mapStateToProps)(Login);
Here is my action type and creator:
export const LOGINSUCCESSFUL = "LOGINSUCCESSFUL";
export const loginSuccessful = userData => {
console.log(action);
return {
type: LOGINSUCCESSFUL,
payload: userData
};
};
And here is my reducer
import {LOGINSUCCESSFUL} from "../actions/actiontypes";
const initialState = {
userData: {}
};
const loginCredentialsReducer = (state = initialState, action) => {
switch (action.type) {
case LOGINSUCCESSFUL:
return {
...state,
userData: action.payload
};
default:
return state;
}
};
export default loginCredentialsReducer;
You're calling console.log(action) here, but action isn't declared anywhere. Hence the error.
export const LOGINSUCCESSFUL = "LOGINSUCCESSFUL";
export const loginSuccessful = userData => {
console.log(action); // <-- action is not declared anywhere
return {
type: LOGINSUCCESSFUL,
payload: userData
};
};
In this:
export const loginSuccessful = userData => {
console.log(action);
return {
type: LOGINSUCCESSFUL,
payload: userData
};
};
you never defined action hence the error.
I am learning React Redux. My Action is like below
import Axios from 'axios';
export const getAddress = valueModal => dispatch => {
return Axios.get('/api/address')
.then(response => {
var addressData = response.data;
dispatch({
type: 'getAddresses',
payload: { addressData, valueModal }
});
})
.catch(function(error) {
console.log(error);
});
};
export const uploadImage = (formData, id, config) => dispatch => {
return Axios.post('/api/address/upload', formData, config)
.then(response => {
dispatch({
type: 'uploadImage',
payload: response.data
});
})
.catch(function(error) {
console.log(error);
});
};
export default { getAddress, addAddress, uploadImage };
My Reducer is like below
const initialState = {
address: {}
};
const addressReducer = (state = initialState, action) => {
switch (action.type) {
case 'getAddresses': {
return {
controlModal: action.payload.valueModal,
address: action.payload.addressData
};
}
case 'uploadImage': {
return {
uploadImage: action.payload
};
}
default:
return state;
}
};
export default addressReducer;
I would like to call getAddresses and uploadImage jointly. How can I do that ?
const initialState = {
address: {}
};
const addressReducer = (state = initialState, action) => {
switch (action.type) {
case 'getAddresses': {
return {
...state,
controlModal: action.payload.valueModal,
address: action.payload.addressData
};
}
case 'uploadImage': {
return {
...state,
uploadImage: action.payload
};
}
default:
return state;
}
};
export default addressReducer;
You need to spread the object state out otherwise there is never a reference to the state before the update.
The object spread syntax lets you use the spread ... operator to copy enumerable properties from one object to another in a more succinct way.
Having a strange bug/issue with redux. I have a component in an app that displays data in a table. this table is used across numerous routes and i'm passing in a url for the end point.
When i click between the routes they work fine but some fields in the table have a button to open a slide out menu. when i do the redux actions is dispatched and it fires it for all routes i have been to and not the one i'm on.
Action
export const clearTableData = () => dispatch => {
dispatch({
type: TYPES.CLEAR_TABLE_DATA,
});
};
export const getTableData = (url, limit, skip, where, sort, current) => async dispatch => {
try {
dispatch({ type: TYPES.FETCH_TABLE_DATA_LOADING });
const response = await axios.post(url, {
limit,
skip,
where,
sort
});
await dispatch({
type: TYPES.FETCH_TABLE_DATA,
payload: {
url: url,
data: response.data,
limit: limit,
skip: skip,
where: where,
sort: sort,
pagination: {
total: response.data.meta.total,
current: current,
pageSizeOptions: ["10", "20", "50", "100"],
showSizeChanger: true,
showQuickJumper: true,
position: "both"
}
}
});
dispatch({ type: TYPES.FETCH_TABLE_DATA_FINISHED });
} catch (err) {
dispatch({ type: TYPES.INSERT_ERROR, payload: err.response });
}
};
Reducer
import * as TYPES from '../actions/types';
export default (state = { loading: true, data: [], pagination: [] }, action) => {
switch (action.type) {
case TYPES.FETCH_TABLE_DATA:
return { ...state, ...action.payload };
case TYPES.FETCH_TABLE_DATA_LOADING:
return { ...state, loading: true };
case TYPES.FETCH_TABLE_DATA_FINISHED:
return { ...state, loading: false };
case TYPES.CLEAR_TABLE_DATA:
return {};
default:
return state;
}
};
component
componentDidMount() {
this.fetch();
websocket(this.props.websocketRoute, this.props.websocketEvent, this.fetch);
}
fetch = () => {
// Fetch from redux store
this.props.getTableData(
this.props.apiUrl,
this.state.limit,
this.state.skip,
{ ...this.filters, ...this.props.defaultWhere },
`${this.state.sortField} ${this.state.sortOrder}`,
this.state.current)
}
const mapStateToProps = ({ tableData }) => ({
tableData,
});
const mapDispatchToProps = dispatch => (
bindActionCreators({ getTableData }, dispatch)
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(SearchableTable);
Websocket
import socketIOClient from 'socket.io-client';
import sailsIOClient from 'sails.io.js';
export const websocket = (websocketRoute, websocketEvent, callback) => {
if (websocketRoute) {
let io;
if (socketIOClient.sails) {
io = socketIOClient;
} else {
io = sailsIOClient(socketIOClient);
}
io.sails.transports = ['websocket'];
io.sails.reconnection = true;
io.sails.url = process.env.REACT_APP_WEBSOCKECTS_URL
io.socket.on('connect', () => {
io.socket.get(websocketRoute, (data, jwres) => {
console.log("connect data sss", data)
console.log("connect jwres sss", jwres)
});
});
io.socket.on(websocketEvent, (data, jwres) => {
console.log("websocket", callback)
callback();
})
io.socket.on('disconnect', () => {
io.socket._raw.io._reconnection = true;
});
}
}
So for e.g if i'm on a route for cars i'll pass in api/cars as url, and for trucks api/trucks. if i've been to both these pages they get fired.
should i be doing something to unmount and reset state to blank?
edit to add render
render() {
const { filters, columns, expandedRowRender, rowClassName, style } = this.props;
return (
<Table
bordered
columns={columns}
rowKey={record => record.id}
dataSource={this.props.tableData.data.items}
pagination={this.props.tableData.pagination}
loading={this.props.tableData.loading}
onChange={this.handleTableChange}
expandedRowRender={expandedRowRender}
rowClassName={rowClassName} />
);
Basic idea is, define a new action type in reducer file to clear the table data, and before unmount dispatch that action.
In Component:
componentDidMount() {
this.fetch();
}
componentWillUnmount() {
this.props.clearTableData();
}
const mapDispatchToProps = dispatch => (
bindActionCreators({ getTableData, clearTableData }, dispatch)
)
Action:
export const clearTableData = () => {
return { type: TYPES.CLEAR_TABLE_DATA };
};
Reducer:
case TYPES.CLEAR_TABLE_DATA: {
// reset the table data here, and return
}
I've been trying to retrieve the new state from my vitaminReducer() reducer function, and connect it through mapStateToProps. But when I console.log the state, I get back "the state is {vitamin: undefined}".
This is the Vitamins component where I'm calling mapStateToProps()
(Vitamins.js)
componentDidMount() {
this.props.fetchVitamins();
}
function mapStateToProps(state) {
return {
vitamin: state,
}
};
console.log('the state is', mapStateToProps());
export default connect(mapStateToProps, { fetchVitamins })(Vitamins);
(reducers.js)
function vitaminReducer(state = [], action) {
switch(action.type) {
case FETCH_VITAMINS_SUCCESS:
return [
...state,
action.payload.vitamins
];
default:
return state;
}
}
const reducers = combineReducers({
vitamin: vitaminReducer,
});
I have the data coming through an Express server. I've console logged "vitamins" here and I get the data back, so I know that's not the issue.
(actions.js)
export function fetchVitamins() {
return dispatch => {
return fetch("/users")
.then(handleErrors)
.then(res => res.json())
.then(micros => {
dispatch(fetchVitaminsSuccess(micros));
const vitamins = micros.vitamins;
}
)};
};
export const FETCH_VITAMINS_SUCCESS = 'FETCH_VITAMINS_SUCCESS';
export const fetchVitaminsSuccess = vitamins => ({
type: FETCH_VITAMINS_SUCCESS,
payload: vitamins
});
If I do: "return { vitamin: state.vitamin, }" instead of "return { vitamin: state, }", I get back "TypeError: Cannot read property 'vitamin' of undefined". But that's what I called vitaminReducer in my combineReducers() function at the bottom of reducers.js, so I thought that was the right way to do it.
Thank you everyone for your input! I was able to get it working.
I ditched the mapStateToProps() and instead did this
(Vitamins.js)
componentDidMount() {
this.props.fetchVitamins();
}
renderData() {
const { vitamins } = this.props.vitamins;
return vitamins.map((micro, index) => {
return (
<option value={micro.value} key={index}>{micro.name}</option>
)
})
}
export default connect(
state => ({
vitamins: state.vitamins
}),
{
fetchVitamins
},
)(Vitamins);
I set the dispatch action inside of the fetchVitamins() function
(actions.js)
export function fetchVitamins() {
return dispatch => {
return fetch("/users")
.then(handleErrors)
.then(res => res.json())
.then(micros => {
dispatch({
type: "RECEIVE_VITAMINS",
payload: micros.vitamins
});
}
)};
};
export const RECEIVE_VITAMINS = 'RECEIVE_VITAMINS';
In reducers I set the initialState to the vitamins array, and passed the new state of micros.vitamins from my RECEIVE_VITAMINS action
(reducers.js)
const initialState = {
vitamins: [],
}
function vitaminReducer(state = initialState, action) {
switch(action.type) {
case RECEIVE_VITAMINS:
return {
...state,
vitamins: action.payload
};
default:
return state;
}
}
const reducers = combineReducers({
vitamins: vitaminReducer,
});
Thanks everyone for your help! Let me know if you have any other suggestions :D
I have been working on authentication with my project. I have a REST api backend that serves JWT tokens. My front end stack is ReactJS, Redux, Axios and Redux Thunk.
My question is why when I submit my form it does not send any credentials?
But when I console log the action and payload on credChange it seems to be correct. Am I not setting the state somewhere?
Also, axios does not catch the 400 Bad Request error.
Here is my code:
AuthActions.js
export const credChange = ({ prop, value }) => {
return {
type: CRED_CHANGE,
payload: { prop, value },
};
};
export const logoutUser = () => {
return (dispatch) => {
dispatch({ type: LOGOUT_USER });
};
};
const loginSuccess = (dispatch, response) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: response.data.token,
});
};
const loginError = (dispatch, error) => {
dispatch({
type: LOGIN_USER_ERROR,
payload: error.response.data,
});
};
export const loginUser = ({ empNum, password }) => {
return (dispatch) => {
dispatch({ type: LOGIN_USER });
axios({
method: 'post',
url: 'http://127.0.0.1:8000/profiles_api/jwt/authTK/',
data: {
emp_number: empNum,
password,
},
})
.then(response => loginSuccess(dispatch, response))
.catch(error => loginError(dispatch, error));
};
};
AuthReducer.js
const INITIAL_STATE = {
empNum: '',
password: '',
empNumErr: null,
passwordErr: null,
authTK: null,
loading: false,
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CRED_CHANGE:
return { ...state, [action.payload.prop]: action.payload.value };
case LOGIN_USER:
return {
...state,
...INITIAL_STATE,
loading: true,
};
case LOGOUT_USER:
return {
...state,
INITIAL_STATE,
};
case LOGIN_USER_SUCCESS:
return {
...state,
...INITIAL_STATE,
authTK: action.payload,
};
case LOGIN_USER_ERROR:
return {
...state,
...INITIAL_STATE,
empNumErr: action.payload.emp_number,
passwordErr: action.payload.password,
};
default:
return state;
}
};
LoginForm.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
credChange,
loginUser,
logoutUser,
} from '../Actions';
class LoginForm extends Component {
constructor() {
super();
this.onFormSubmit = this.onFormSubmit.bind(this);
this.renderEmpNumErr = this.renderEmpNumErr.bind(this);
this.empNumChange = this.empNumChange.bind(this);
this.passwordChange = this.passwordChange.bind(this);
}
onFormSubmit() {
const { empNum, password } = this.props;
this.props.loginUser({ empNum, password });
}
empNumChange(text) {
this.props.credChange({ prop: 'empNum', value: text.target.value });
}
passwordChange(text) {
this.props.credChange({ prop: 'password', value: text.target.value });
}
renderEmpNumErr() {
if (this.props.empNumErr) {
return (
<p>
{this.props.empNumErr}
</p>
);
}
return null;
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<label htmlFor="numberLabel">Employee Number</label>
<input
id="numberLabel"
type="password"
value={this.props.empNum}
onChange={this.empNumChange}
/>
<label htmlFor="passLabel">Password</label>
<input
id="passLabel"
type="password"
value={this.props.password}
onChange={this.passwordChange}
/>
<button type="submit">Login</button>
</form>
{this.renderEmpNumErr()}
</div>
);
}
}
const mapStateToProps = ({ counter }) => {
const {
empNum,
password,
loading,
empNumErr,
passwordErr,
authTK,
} = counter;
return {
empNum,
password,
loading,
empNumErr,
passwordErr,
authTK,
};
};
export default connect(mapStateToProps, { credChange, loginUser, logoutUser })(LoginForm);
After Submitting form with credentials
The console says:
POST XHR http://127.0.0.1:8000/profiles_api/jwt/authTK/ [HTTP/1.0 400 Bad Request 5ms]
And the POST request Raw Data is blank, therefore no credentials were sent.
{"emp_number":["This field is required."],"password":["This field is required."]}
EDIT
If there is any other information I can provide please say so but I think this should be sufficient.
Looks like empNum and password aren't getting set in the state. This is because the action object returned by credChange doesn't get dispatched, so the reducer never get called:
// dispatch calls the reducer which updates the state
dispatch(actionCreator())
// returns an action object, doesn't call reducer
actionCreator()
You can dispatch actions automatically by calling a bound action creator:
// calls the reducer, updates the state
const boundActionCreator = () => {dispatch(actionCreator())}
// call boundActionCreator in your component
boundActionCreator()
mapDispatchToProps can be used to define bound action creators (to be passed as props):
const mapDispatchToProps = (dispatch) => {
return {
credChange: ({ prop, value }) => {dispatch(credChange({prop, value})},
loginUser: ({ empNum, password }) => {dispatch(loginUser({empNum, password})},
logoutUser: () => {dispatch(logoutUser()},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
This should solve the state update issue, allowing props that read from state (empNumber, password, etc.) to update as well.