My react component is not re-rendering despite its props being updated and I don't understand why.
Here's my component
import { fetchLocations } from 'state/locations/actions';
class Event extends React.Component {
componentDidMount() {
this.props.fetchLocations();
}
render() {
const { locations } = this.props;
return <span>{locations.map((l) => {return <span>{l}</span>;})}</span>;
}
}
const mapStateToProps = (state) => ({
locations: state.locations
})
export default connect(
mapStateToProps,
{ fetchLocations },
)(Event);
Here is my locations action file
export const fetchLocations = () = (dispatch) => {
axios.get('/api/locations')
.then(response => {
const locations = response.data;
dispatch({ type: FETCH_LOCATIONS_SUCCESS, payload: locations });
});
}
And my entities reducer
function entities(state = { locations: {} }, action) {
switch (action.type) {
case FETCH_LOCATIONS_SUCCESS:
return Object.assign({}, state, {
locations: action.payload
})
default:
return state
}
}
After this, my Event component should re-render. It doesn't. Using the react dev tools chrome extension I see that the locations are indeed there as props, but they do not show on the UI.
If I unmount the component by going to a different page and re-mount it, the locations show up properly.
It looks like everything works fine except the re-render is not triggering. componentDidUpdate is never fired.
If I manually do a setTimeout to forceUpdate an arbitrary second later, they show up.
Why isn't my component re-rendering?
Please, try to add key prop to span element of the render method. locations.map((l,key)=> <span key={key} >{l} </span>
Related
I have a React component that maps state to props to get data via redux. Everything works fine with the action and the value being updated properly in the reducer. My only problem is that when the state value changes, I want my component to re render so that it is always displaying the most up to date value in the reducer. As of right now I have to call a separate function that refreshes the component, but I'd rather have it automatically re render every time that value changes in the reducer.
Action:
export const createPickup = (selected, pickups) => dispatch => {
let icon;
icon = check(selected);
pickups.icon = icon;
return API('/createPickUp/', {
...pickups,
})
.then(res => {
dispatch({type: types.CREATE_PICKUP, res});
})
.catch(err => console.log(err));
};
Reducer:
const initialState = {
pick: [],
};
export default function pickup(state = initialState, action) {
switch (action.type) {
case types.GET_PICK:
return {
pick: action.pickup,
};
case types.CREATE_PICKUP:
return {
pick: [action.res, ...state.pick],
};
case types.DEL_GAME:
return {
pick: state.pick.filter(p => p._id !== action.id),
};
case types.START_GAME:
return {
pick: state.pick.map(p =>
p._id === action.id ? {...p, start: true} : p,
),
};
case types.STOP_GAME:
return {
pick: state.pick.map(p =>
p._id === action.id ? {...p, stop: true} : p,
),
};
default:
return state;
}
}
Use useSelector hook in Functional Component as it automatically subscribes to the state and your component will re-render.
If you are using Class Component then use connect() from redux and mapStateinProps.
I am assuming you have passed the reducer to the global Store.
Now... make sure you have the up to date value in your component.. try consoling it like this...
import {useSelector} from 'react-redux';
const YourCmponent = () => {
const reduxState = useSelector(state => state);
console.log(reduxState);
return <div>Your Content</div>
}
That way you can get access to the redux store. And you don't need to make any other function for updating component You will always get updated value here.
I'm setting a new value in the store by this action triggered with a click on a button:
onClick={() => { this.handleChange(values)
The function is:
handleChange = (values) => {
const { setComponentSelected } = this.props;
//some code
setComponentSelected(values);
//some code
}
With that, I can see in the Redux Dev Tools that my state is changed:
and in the dom I can see this changes ok.
But, another component is re-rendering this component where I have the problem. In this componente, I'm rendering like this way:
render() {
const { getComponentSelected } = this.props;
console.log('getComponentSelected', getComponentSelected)
And this is how I use the store with redux:
const mapStateToProps = state => ({
getComponentSelected: state.displayReportRecordReducers.setComponentSelected_data
});
const mapDispatchToProps = {
setComponentSelected: displayReportRecordActions.setComponentSelected,
}
I can see the changes from the this.handleChange at first, but everytime that the component gets mounted again, the values for this console.logis the old one, and not the same as I have in the store. There's no actions made with the remounting, the action that changes the store is only triggered with the this.handleChange so I don't know why is changes the value, and not showing what I have in the store.
Any idea?
EDIT:
My action is:
const setComponentSelected = (value: any) => {
return async (dispatch: any) => {
try {
dispatch(success(value));
}
catch (err) {
console.log('error:', err);
};
}
function success(value) {
return { type: displayReportRecordConstants.SET_COMPONENT_SELECTED, payload: { value } };
}
};
My reducer is:
case displayReportRecordConstants.SET_COMPONENT_SELECTED:
return update(state, {
setComponentSelected_data: { $set: payload.value },
});
EDIT 2: this scrennshots show the consistence between the store and the data that I render, at first click:
But if the components get re-rendered by another one, I lost the data in the console logs, whichs it's supossed to be get from the store. But the Redux dev tools show no changes!
I don't know why but I have infinite loop when fetching data in Redux operations.
I have an app with Redux and ReactJS.
This is my React component
const CustomersTable = (props) => {
useEffect( () => {
props.getAllCustomers()
}, []);
return <Table ...props.customers />
}
const mapStateToProps = (state) => ({
customers: state.customers,
})
const mapDispatchToProps = dispatch => ({
getAllCustomers: () => dispatch(getAllCustomers()),
})
export default connect(
mapStateToProps, mapDispatchToProps
)(CustomersTable);
This is getAllInvoices()
const fetchCustomers = async() => {
/**
* I fetch only documents with flag delete==false
*/
const snapshot = await firestore.collection("customers").where('deleted', '==', false).get()
let data = []
snapshot.forEach(doc => {
let d = doc.data();
d.id_db = doc.id
//...other
data.push(d)
})
return data
}
export const getAllCustomers = () =>
async (dispatch) => {
const customers = await fetchCustomers()
// I reset state becouse I wont duplicate inovices in tables
dispatch(actions.reset())
customers.map(customer => dispatch(
actions.fetch(customer)
))
}
And reducers
const customerReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.FETCH_CUSTOMERS:
return {
...state, list: [...state.list, action.item]
}
case types.RESET_CUSTOMERS:
return {
...state, list: []
}
default:
return state
}
}
I expect that reducers RESET_CUSTOMERS and then FETCH_CUSTOMERS done job. But it still working in loop reset->customers.
I thought that is still rendered the component in useEffect but I think that hook is writing good.
I tested other reducers which are copy-pase reducers from Customers and they work well.
EDIT 1
#godsenal, thanks for your reply:
actions.js:
import types from './types'
const fetch = item => ({
type: types.FETCH_CUSTOMERS, item
})
const reset = item => ({
type: types.RESET_CUSTOMERS, item
})
export default {
fetch,
reset
}
As regards <Table /> it is AntDesign component (https://ant.design/components/table/). Without that, it looks the same.
EDIT 2
It is incredible. I copied all files from modules (customers) and paste into contracts directory. Then I changed all variables, functions, etc from customer to contract. Now it working (only contracts), but customers infinite loop. Maybe something disturbs in outside a structure.
EDIT 3
I found in app.js that in mapStateToProps I added customers to props. After remove (because I don't need it in root component) it began works fine. I suspect that fetch method in <CustomerTable /> affect the <App /> component and it render in a loop. I discovered that component isn't still updated in a loop, but its mounts and unmounts in a loop.
But still, I don't understand one thing. In <App />, I still have in mapStateToProps dispatching invoice from a store (the same case as customers) and in this case, everything works fine.
I have a component that makes an API call and then updates the state through a reducer. The problem is, this doesn't work so well cause the data don't get updated in the component, it's like the react didn't notice a state change a never re-rendered the component, but I'm not sure if that's the real issue here. So the component looks like this:
class MyComponent extends Component {
componentDidMount() {
// ajax call
this.props.loadData(1);
}
render() {
return (
<Grid>
<MySecondComponent
currentData={this.props.currentData}
/>
</Grid>
);
}
}
const mapStateToProps = state => ({
reducer state.myReducer,
currentData: state.myReducer.currentData
});
const mapDispatchToProps = dispatch => {
return {
loadData: () => {
HttpClient.getData(id, (data) => {
dispatch(
action_loadCurrentData(
data
)
);
});
},
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
I am doing 2 things here: issuing an API call as soon as component is mounted, and then after data is fetched, dispatching action_loadCurrentData
This action looks like this:
//Action
export function action_loadCurrentData(
data
) {
return {
type: 'LOAD_CURRENT_DATA',
payload: {
currentData: data,
}
};
}
and the reducer:
//Reducer
const defaultState = {
};
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
state = {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
export default myReducer;
So the issue here is that the this.props.currentData that I'm passing to MySecondComponent will end up empty, as if I didn't set the data at all. However, If I stop the execution in the debugger and give it a few seconds, the data will be populated correctly, so I'm not sure what I'm doing wrong here?
Don't reassign state, return the newly created object instead
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
return {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
Your reducer needs to return the new state object, which needs to be a different instance from the previous state to trigger components update.
According to redux documentation:
The reducer is a pure function that takes the previous state and an action, and returns the next state.
And
Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
I am building a form where in some instances form elements are injected from an AJAX call (Duplicate text input for example).
Everything is working great and updating my form however I can't seem to get any default values back into the initial form state in my redux store. Below is my custom reducer that keeps track of the form elements. Can I push my new values into the initial state again?
//Schema Reducer
case "UPDATE_SCHEMA_FULFILLED":{
let s = {...state.schema}
for (let key in action.payload){
if(s.hasOwnProperty(key)){
if(key == 'values'){
s[key] = {...s[key], ...action.payload[key]}
}else{
s[key] = [...s[key], ...action.payload[key]]
}
}
}
state = { ...state,
loaded: true,
schema: {...s},
}
break;
}
My form is adding the initial values on first load as per the docs:
CustomForm = connect(
state => ({
initialValues: state.schema.schema.values
}),
dispatch => ({
onSubmit: data => dispatch(saveForm(data))
})
)(CustomForm)
This is what is generating the action:
import React from 'react'
import { addSchema } from '../actions/schemaActions'
export default class VirtualButton extends React.Component {
constructor(){
super();
this.generateNewLayout = this.generateNewLayout.bind(this)
}
generateNewLayout(e){
e.preventDefault();
this.props.dispatch(addSchema(this.props.owner));
}
render(){
return <div className="cf__virtual-action"><a href="" onClick={this.generateNewLayout}>Create New</a></div>
}
}
This seems to be working but I'm not sure if it's performant? Adding the initialize values function to the actions via props:
//My button that dispatches the initial call:
this.props.dispatch(addSchema(this.props.owner, this.props.initialize, this.props.initialValues));
export function addSchema($id, initialize, initial){
return function(dispatch) {
axios.post(config.api+'forms/schema/virtual/'+$id)
.then((response) => {
dispatch({type: 'UPDATE_SCHEMA_FULFILLED', payload: response.data})
initialize({...initial, ...response.data.values});
})
.catch((error) => {
console.log(error);
})
}
}