React not responding to (redux) state change? - reactjs

For some reason react is not re-rendering when the (redux) state changed. Might it be because it is nested 1 level? Also in the redux inspector I can see that the state has changed correctly and as expected. On manually refreshing the page, I can also see that it worked. But I am confused on why it is not re-rendering automatically.
Simplified Class Component
class Users extends Component {
render() {
const something = this.props.users.users;
return (
<div>
<div className='MAIN_SECTION'>
<SearchBar handleChange={(value) => this.setState({searchQuery: value})} />
<div className='LIST'>
{something.mapObject((user) => <div onClick={() => this.props.deleteUser(user.id)}>{user.name}</div>)}
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
users: state.users
};
}
export default connect(mapStateToProps, { deleteUser })(Users);
Action Creator
export function deleteUser(userhash) {
return function (dispatch, getState) {
return new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
const data = {
status: 200,
data: {
status: 200
}
};
if (data.status === 200) {
const newState = getState().users.users;
delete newState[userhash];
dispatch({
type: DELETE_USER,
payload: {
data: newState
}
});
}
});
};
}
Reducer
const INITIAL_STATE = {
isFetching: true,
users: {}
};
case DELETE_USER:
return {
...state,
users: action.payload.data
};

Once you dispatch action DELETE_USER,
const something = this.props.users.users;
doesn't look right.
You are doing this in action which means something must be this.props.user.
const newState = getState().users.users;
delete newState[userhash];
dispatch({
type: DELETE_USER,
payload: {
data: newState
}
});
In your render, this.props.users itself is the array of users.
To have a dirty fix, changing mapStateToProps to:
function mapStateToProps(state) {
return {
users: state.users.users || state.users
};
}
Change in render:
const something = this.props.users;

The code doesn't really jive to me. In one spot you are you are using .map() on users.users and in another spot you are using delete[newHash] on it. So is it an array, or an object?
But hypothetically, let's say it did work. You say that that the state has changed correctly.
Let's assume the initial state is
{
isFetching: true,
users: { users: ['12345', '67890'] }
}
Let say user '12345' gets deleted, in the action, we have these sequence of events
const newState = getState().users.users; // ['12345', '67890']
delete newState[userhash]; // ['67890'] if you use your imagination
dispatch({
type: DELETE_USER,
payload: {
data: newState // ['67890']
})
Finally, in the reducer
case DELETE_USER:
return {
...state,
users: action.payload.data // ['67890']
};
So the final state is
{
isFetching: true,
users: ['67890']
}
It's not the same shape as the original state - users no longer has a users.users.

Related

React useEffect doesn't dispatch the redux action after page refresh

I'm trying to render the data from the following object of data which is coming from an API.
{
"code": 0,
"c": "verified",
"d": "verified",
"leaseInfo": {
"infoId": 6
},
"cpfPrice": "500.00",
"carCurrentLocation": {
"id": 1,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
},
"n": "verified",
"p": "false",
"ownerCarInfo": {
"brand": "Ferrari",
"model": "0"
},
"serviceFeeRate": 0.10,
"depositPrice": "100.00",
"pics": [
{
"picid": 49,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
},
],
"items": {
"itemid": 5,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
}
}
I'm using react-redux to dispatch an action, where I will be provided with the data under a state named 'carDetails'.
However, when I try to access the data, if my component is refreshed, carDetails becomes undefined and hence gives "Cannot read property ownerCarInfo of undefined."
I'm obtaining and de-structuring the data of carDetails like this in my React component:
import React, {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
const CarInfo = ({ match }) => {
const dispatch = useDispatch();
const details = useSelector((state) => state.carDetails);
const { loading, carDetails } = details;
const {pics, carCurrentLocation, items, ownerCarInfo} = carDetails;
useEffect(() => {
dispatch(getCarDetails(match.params.id));
}, [dispatch, match]);
return (
<div>
{loading ? (
<Loader></Loader>
) : (
<>
<p>{d.depositPrice}</p>
<p>{ownerCarInfo.brand}</p>
</>
)}
</div>
);
)
}
As long as the component or the React application is not refreshed, it retrieves data and displays it correctly. The carDetails becomes an empty array as soon as the page is refreshed.
This is the getCarDetails() action:
export const getCarDetails = (id) => async (dispatch, getState) => {
try {
dispatch({
type: CAR_DETAILS_REQUEST,
});
const { userLogin } = getState();
const { userInfo } = userLogin;
const config = {
headers: {
Authorization: userInfo.token,
'Content-Type': 'application/json',
},
};
const { data } = await axios.get(
`${BASE_API}/car/info/getDetails/${id}/${userInfo.bscId}`,
config
);
dispatch({
type: CAR_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: CAR_DETAILS_FAIL,
payload:
error.response && error.response.data.msg
? error.response.data.msg
: error.msg,
});
}
};
This is my reducer:
export const carsDetailsReducer = (state = { carDetails: [] }, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return { loading: true };
case CAR_DETAILS_SUCCESS:
return { loading: false, carDetails: action.payload };
case CAR_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
This is how I declare carDetails in the redux store.
const reducer = combineReducers({
carDetails: carsDetailsReducer,
});
What is the cause for carDetails becoming undefined and the useEffect not running on page refresh?
If you are using axios your action should look like this with async function and await while you are calling API.
If you are passing API car id in the api link then pass the id in the parameters:
import axios from "axios";
export const loadData = (id) => async (dispatch) => {
dispatch({
type: "CAR_DETAILS_REQUEST",
});
const detailData = await axios.get("http:\\****/id");
dispatch({
type: "CAR_DETAILS_SUCCESS",
payload: {
success: detailData.data,
},
});
};
Reducer:
const initailState = { carDetails: [], loading: true };
export const carsDetailsReducer = (state = initailState, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return { ...state,
loading: true
};
case CAR_DETAILS_SUCCESS:
return {...state,
loading: false,
carDetails: action.payload
};
case CAR_DETAILS_FAIL:
return { ...state,
loading: false,
error: action.payload };
default:
return ...state;
}
};
Your useEffect should only work when data is fetched:
import React, {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
const CarInfo = ({ match }) => {
const dispatch = useDispatch();
const details = useSelector((state) => state.carDetails);
const { loading, carDetails } = details;
const {pics, carCurrentLocation, items, ownerCarInfo} = carDetails;
useEffect(() => {
dispatch(getCarDetails(id));
}, [dispatch]);
return (
<div>
{loading ? (
<Loader></Loader>
) : (
<>
<p>{d.depositPrice}</p>
<p>{ownerCarInfo.brand}</p>
</>
)}
</div>
You can also use it without a useEffect by making an onclick() function like this:
const loadDetailHandler = () => {
dispatch(getCarDetails(id));
};
return (
<div onClick={loadDetailHandler} >
</div>
If carDetails initial state is an array, then why are you destructuring object properties from it in your UI? Question for another time...
If after reloading the page the state reverts back to the initial state, an empty array is still a defined object. You need to track down what is causing your state.carDetails.carDetails to become undefined. If you examine your reducer notice that your CAR_DETAILS_REQUEST case wipes the carDetails state out and it becomes undefined. Honestly I'm surprised you aren't seeing this issue when your code runs normally without a page reload.
You need to hold on to that state. For good measure, you should always shallow copy the existing state when computing the next state object unless you've good reason to omit parts of state.
export const carsDetailsReducer = (state = { carDetails: [] }, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return {
...state, // <-- shallow copy existing state
loading: true,
};
case CAR_DETAILS_SUCCESS:
return {
...state, // <-- shallow copy existing state
loading: false,
carDetails: action.payload
};
case CAR_DETAILS_FAIL:
return {
...state, // <-- shallow copy existing state
loading: false,
error: action.payload,
};
default:
return state;
}
};
for me, I think you should save the state in the
`case CAR_DETAILS_REQUEST:
return {
...state, // <-- shallow copy existing state
loading: true,
};
`
to be able to use it before o when you want to use a reducer you should each case
have the old state the reducer return the same sharp of initial state that put it you also used is loading and that not found in the initial state
so try to make the shape of the state
state={
isloading:false,
carDetails: []
}
also try each time to same the state by {...state ,is loading:true}
The problem is in CAR_DETAILS_REQUEST. You only return { loading: true }; so carDetails will be lost and become undefined.
Just update your reducer like this:
case CAR_DETAILS_REQUEST:
return { ...state, loading: true };

React Redux - Loading state too slow - how to solve it

I'm trying to create a loading state for my Redux but it looks to "slow" to get updated.
First action fetchDB => setLoading: true => once over setLoading: false
Second action fetchCat => doesn't have the time to fire it that crashes
Really simple:
set loading action:
export const setLoading = () => {
return async (dispatch) => {
await dispatch({ type: SET_LOADING }); // no payload by default goes to true
};
};
set loading reducer:
import {
FETCH_DB,
SET_LOADING,
} from "../types"
const initalState = {
db: [],
loading: false,
}
export default (state = initalState, action) => {
switch (action.type) {
// this like the other cases sets loading to FALSE
case FETCH_DB:
return {
...state,
db: action.payload,
current: null,
loading: false,
}
case FETCH_CAT_FOOD:
return {
...state,
food: action.payload,
loading: false,
}
case FETCH_CAT_DESIGN:
return {
...state,
design: action.payload,
loading: false,
}
case SET_LOADING:
return {
...state,
loading: true,
}
default:
return state
}
}
then action I use that creates the problem:
export const fetchCat = kindof => {
return async dispatch => {
dispatch(setLoading()) // looks like that it doesn't get fired
const response = await axios
.get(`http://localhost:5000/api/categories/${kindof}`)
.then(results => results.data)
try {
await dispatch({ type: `FETCH_CAT_${kindof}`, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
and then the file (a custom component) that creates the problem.
It crashes cause categories.map is undefined.
It doesn't find loading: true so the loader doesn't stop.
import React, { useState, useEffect, Fragment } from "react"
import { Spinner } from "react-bootstrap"
import { connect, useDispatch, useSelector } from "react-redux"
import CatItem from "./CatItem" // custom component
import { fetchCat, setLoading } from "../../../store/actions/appActions"
const MapCat = ({ kindof, loading, categories }) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchCat(kindof)) // gives the category I want to fetch
// eslint-disable-next-line
}, [categories])
if (!loading) {
return (
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
)
} else {
return (
<Fragment>
<div>
{categories.map(item => (
<CatItem item={item} />
))}
</div>
</Fragment>
)
}
}
const mapStateToProps = (state, kindof) =>
({
loading: state.appDb.loading,
categories: state.appDb[kindof],
})
export default connect(mapStateToProps, { fetchCat, setLoading })(MapCat)
I think that it is supposed to work like this:
loading: false (by default) => true => time to fetch => false
But doesn't look like working. Any idea?
Firstly setLoading needs to return a plain object with type and payload
export const setLoading = () => ({ type: SET_LOADING });
In fetchCat the then is not required. Also async await for dispatch is not required.
export const fetchCat = (kindof) => {
return (dispatch) => {
dispatch(setLoading()); //<---this should now be ok.
const response = await axios.get(`http://localhost:5000/api/categories/${kindof}`)
// .then((results) => results.data); //<----- not required as you are using await
try {
dispatch({ type: `FETCH_CAT_${kindof}`, payload: response.data }); //<--- use response.data ...also async/await for dispatch is not rquired.
} catch (error) {
console.log("await error", error);
}
};
};
The 2nd arg of mapStateToProps is ownProps which is an object
const mapStateToProps = (state, ownProps) =>
({
loading: state.appDb.loading,
categories: state.appDb[ownProps.kindof],
})
You have quite a bit different way of calling dispatch. Let me list them out
dispatch(fetchCat(kindof)) // gives the category I want to fetch
await dispatch({ type: `FETCH_CAT_${kindof}`, payload: response })
You can see, await or not basically is the way you use async operation. However dispatch takes type and payload to function, which means you have to make sure what you send to dispatch is with the right object. Of course Redux does accept custom format via plugins, so maybe if you throw it a async as input, the reducer might understand it as well?
Please double check each dispatch first, for example, write a function that only dispatch one type of action. Only after you make each call working, don't move to assemble them together into a bundled call.

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.

State is undefined in mapStateToProps

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

Component structure to handle Async Action with Redux-thunk ?

After a bit of trial and error I finally manage to get my action creator working properly and passing the data I wanted into my redux store. Until now I've been dispatching it "manually" like this store.dispatch(fetchTest()); but It would be great if could use these data into a component.
So here is my action creator :
export const fetchTest = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
return axios.get('http://localhost:3000/authors')
.then(data => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching:false,
data: data
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
Here is my reducer :
const initialState = {data:null,isFetching: false,error:null};
export const ThunkData = (state = initialState, action)=>{
switch (action.type) {
case 'FETCH_DATA_REQUEST':
case 'FETCH_DATA_FAILURE':
return { ...state, isFetching: action.isFetching, error: action.error };
case 'FETCH_DATA_SUCCESS':
return Object.assign({}, state, {data: action.data, isFetching: action.isFetching,
error: null });
default:return state;
}
};
So far everything is working properly when using store.dispatch(fetchTest());.
Based on this example I tried to build the following component :
class asyncL extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchTest(this.props.thunkData)
// got an error here : "fetchTest is not a function"
}
render() {
if (this.props.isFetching) {
return console.log("fetching!")
}else if (this.props.error) {
return <div>ERROR {this.props.error}</div>
}else {
return <p>{ this.props.data }</p>
}
}
}
const mapStateToProps = (state) => {
return {
isFetching: state.ThunkData.isFetching,
data: state.ThunkData.data.data,
error: state.ThunkData.error,
};
};
const AsyncList = connect(mapStateToProps)(asyncL);
export default AsyncList
It doesn't work, I have an error on the componentWillMount() and probably somewhere else.
Also my data structure is kind of weird. To actually get to the data array I have to do state.ThunkData.data.data. The first data object is full of useless stuff like request, headers, etc...
So how should I write this component so I can at least passed the Async data into a console.log.
Thanks.
You need to mapDispatchToProps as well.
import { fetchTest } from './myFetchActionFileHere';
import { bindActionCreators } from 'redux';
function mapDispatchToProps(dispatch) {
return {
fetchTest: bindActionCreators(fetchTest, dispatch)
};
}
const AsyncList = connect(mapStateToProps, mapDispatchToProps)(asyncL);
export default AsyncList
documentation link: http://redux.js.org/docs/api/bindActionCreators.html

Resources