I'm having trouble getting the Component to re-render after an action. I understand that I must return a new object in my reducer, so I'm returning an entirely new state, but its still not triggering a componentDidMount() or render()
My Component:
class AppTemplate extends React.Component {
constructor(props) {
super(props);
if(!this.props.settings.user){
this.props.dispatch(userActions.get());
}
}
componentDidUpdate(prevProps, prevState) {
console.log(prevProps, this.props);
//do some stuff
}
render() {
return (
<SomeComponent/>
);
}
}
function mapStateToProps(state) {
const { registration, role, settings } = state;
console.log(role);
return {
registration,
role,
settings
};
}
const connectedAppTemplate = connect(mapStateToProps)(AppTemplate);
export { connectedAppTemplate as AppTemplate };
My Reducer:
export function role(state = { role : null, loading: true }, action) {
switch (action.type) {
case 'USER_GET_PENDING':
return {
role:null,
loading: true
}
case 'USER_GET_FULFILLED':
const role = action.payload.data.roles[0];
const newState = {
role: role,
loading: false
}
console.log(state, newState);
console.log(state == newState);
return newState;
default:
return state
}
}
The action is getting fulfilled with no problem, and even the mapToState is logging with the new role, but the componentDidMount is never fireing. Any thoughts?
Thanks!
componentDidMount is only called ONCE. Which is when the component finishes loading for the first time. If you're updating the redux store, then you will get calls to the componentWillReceiveProps and IF you trigger a render there, you will see componentDidUpdate will fire.
EDIT: While the component will render again when new props are received via redux...componentDidMount will still only be called once. You are better off using componentWillReceiveProps and componentDidUpdate for what you're trying to accomplish.
Credit to Dan O in the comments below (that rhymed) for pointing out my error.
Related
I'm using react + redux + redux saga
I'm facing the issue that when I'm rendering the page (GET call)
The calls should be like:
constructor
render()
componentDidMount
render()
But I'm just reaching up to componentDidMount, mapDispatchToProps is dispatching the action, API call is working by which getting the response from the server and the data is updated into the state.
BUT somewhere it gets lost and my component is not even re rendering.
Up to the reducer, I'm getting the data where I'm returning action.items.
itemReducer.js
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_ALL_ITEMS_SUCCESS:
console.log("itemReducer-----", action.items); //getting the data over here
return action.items;
default:
return state;
}
};
itemPage.js (component)
class ItemsPage extends React.Component {
componentDidMount() {
this.props.loadItems();
}
render() {
const { items } = this.props; // not even it renders, so not getting data
...
return (<div>...</div>);
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
};
};
const mapDispatchToProps = (dispatch) => {
return {
loadItems: () => dispatch(loadAllItemsAction()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ItemsPage);
Please give some suggestions, Thanks in advance :D
There isn't issue in the code that you posted. To make sure I pretty much copy pasted the code you shared, filled in the missing parts and it works just fine:
https://codesandbox.io/s/3tuxv?file=/src/index.js
My guess what might be wrong is that your items are not stored in state.items but under some different path or you might be missing the react-redux Provider, but it is impossible to say for sure without seeing more of your code.
You need to understand that the calls of render/componentDidMount are not so linear as it could be expected. componentDidMount fires when all children were mount. And it doesn't means that render() was finished already.
Read here for more info:
https://github.com/facebook/react/issues/2659
The problem lays in the code below
class GroupsPage extends React.Component {
constructor(props){
super(props);
this.state = {
groups: [],
}
}
async fetchGroups (){
fetchGroupsFirebase().then((res) => {this.setState({groups:res})})
};
async componentDidMount() {
await this.fetchGroups();
}
render(){}
const mapStateToProps = createStructuredSelector({
user: selectCurrentUser
})
export default connect(mapStateToProps)(GroupsPage);
As you see , i call fetchGroups to get some data from firebase, it works allright but i want to get specific data for my current user, my problem is that i can't send de currentUser id as a param to the fetchGroupsFirebase functions, because at the time of the call, this.props.user is still null , and it gets the value from mapStateToProps only after the component mounted.
I hope that i am clear enough, i know it is messy
TLDR: I need the user id but when i get it it's too late
what you would need to do is to first check if the user prop is available right after the mount - if not skip the api call and wait untill the user prop gets updated using componentDidUpdate lifecycle method.
This way, your api call will be made as soon as the user prop gets injected to the component.
class GroupsPage extends React.Component {
constructor(props) {
super(props);
this.state = {
groups: [],
};
}
async fetchGroups(id) {
fetchGroupsFirebase(id).then((res) => {
this.setState({ groups: res });
});
}
async componentDidMount() {
if (this.props.user) {
await this.fetchGroups(this.props.user);
}
}
async componentDidUpdate(prevProps) {
if (this.props.user && this.props.user !== prevProps.user) {
await this.fetchGroups(this.props.user);
}
}
render() {}
}
const mapStateToProps = createStructuredSelector({
user: selectCurrentUser,
});
export default connect(mapStateToProps)(GroupsPage);
I am new in react js. I have started doing a small product with react-redux. I am using saga middle-ware.
What i have done is as under.
This is the component
//all import work
import { activateAuthLayout, onLoad } from '../../../store/actions';
class EcommerceProductEdit extends Component {
constructor(props) {
super(props);
this.state = {
checked: false,
unselected_lists: [],
main_checked: false
}
//here I get the products props always null
console.log(this.props);
}
componentDidMount() {
this.props.activateAuthLayout();
//dispatching an action to fetch data from api, done in midddleware
if (this.props.user !== null && this.props.user.shop_id)
this.props.onLoad({
payload: this.props.user
});
}
render() {
//here I get the products props
console.log(this.props);
return (
//jsx work
);
}
}
const mapStatetoProps = state => {
const { user, is_logged_in } = state.Common;
const { products, is_loading } = state.Products;
return { user, is_logged_in, products, is_loading };
}
export default withRouter(connect(mapStatetoProps, { activateAuthLayout, onLoad })(EcommerceProductEdit));
Action is
import { FETCH_PRODUCT, FETCH_PRODUCT_SUCCESS } from './actionTypes';
export const onLoad = (action) => {
return {
type: FETCH_PRODUCT,
payload: action.payload
}
}
export const productFetched = (action) => {
return {
type: FETCH_PRODUCT_SUCCESS,
payload: action.payload
}
}
Reducer is
import { FETCH_PRODUCT_SUCCESS } from './actionTypes';
const initialState = {
products: null,
is_loading: true
}
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_PRODUCT_SUCCESS:
state = {
...state,
products: action.payload,
is_loading: false
}
break;
default:
state = { ...state };
break;
}
return state;
}
And saga is
import { takeEvery, put, call } from 'redux-saga/effects';
import { FETCH_PRODUCT } from './actionTypes';
import { productFetched } from './actions';
import agent from '../../agent';
function* fetchProduct(action) {
try {
let response = yield call(agent.Products.get, action.payload);
yield put(productFetched({ payload: response }));
} catch (error) {
if (error.message) {
console.log(error);
} else if (error.response.text === 'Unauthorized') {
console.log(error)
}
}
}
function* productSaga() {
yield takeEvery(FETCH_PRODUCT, fetchProduct)
}
export default productSaga;
I am being able to get the products props only in render function. How would i be able to get it it in constructor ?
I would be really grateful if anyone explained me about react life cycle a little bit more.
Thanks.
updated
a constructor is called during object instantiation. According to the docs "The constructor for a React component is called before it is mounted". So if the props passed to the component are being changed after the component has been mounted you can use componentWillReceiveProps life cycle methods.
componentWillReceiveProps is deprecated so you can use componentDidUpdate instead. Example from the docs.
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.userID !== prevProps.userID) {
// update your component state from here.
this.fetchData(this.props.userID);
}
}
MiddleWare: Middleware just comes in between the flow after the action has been dispatched and before it reaches the reducers, like in your case once you fire onLoad action and before it reaches the reducers, its caught in Saga middleware which executes it according to code written in it
Lifecycle in your case goes the following way:
In your compoenentDidMount method, you dispatch an action of onLoad. The action type in such a case becomes "FETCH_PRODUCT" and same action is now caught in Saga.
Since this is async call, the code in your component continues executing while the Saga perform its action in parallel. It calls API through this line of code: yield call(agent.Products.get, action.payload); . Once API call is completed, it dispatches an action 'productfetched' through this line of code yield put(productFetched({ payload: response }));.
Now this action reaches reducer and modify the state of "products". Since the product state in your redux is modified, your component EcommerceProductEdit re-renders and you get your product list in render method. The point to be noted is that the flow must have already finished executing inside componentDidMount method by this time, so no chance of having products their
Solution to your problem:
Once an action is dispatched and which has become async due to Saga, you won't be able to get value in constructor, if you use Saga. You can just directly call upon the API using axios/fetch library in componentDidMount and await their (Making it synchronous). Once you get response, you may proceed further
In case you have functional component, then you may use Effect hook and bind the dependency to products state. You can write your code in this block, what you want to be executed after API call is made and product list modifies.
React.useEffect(
() => {
// You code goes here
},
[products]
);
You just have to console props rather than doing this.props. You should not reference props with this inside the constructor.
Do this instead:
console.log(props)
Middleware is not related to react lifecycle at all, other than it updates and connected components "react" to props updating.
Check the constructor docs
https://reactjs.org/docs/react-component.html#constructor
Question: why are you trying to log props in the constructor anyway? If you want to know what the props are, use one of the lifecycle functions, componentDidMount/componentDidUpdate, don't use the render function to do side-effects like make asynchronous calls or console log.
componentDidMount() {
console.log(this.props);
}
If you must log props in the constructor though, access the props object that was passed as the component won't have a this.props populated yet.
constructor(props) {
super(props);
...
console.log(props);
}
I have a Profile component that is loaded by react-router (path="profile/:username") and the component itself looks like this:
...
import { fetchUser } from '../actions/user';
class Profile extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { username } = this.props;
this.fetchUser(username);
}
componentWillReceiveProps(nextProps) {
const { username } = nextProps.params;
this.fetchUser(username);
}
fetchUser(username) {
const { dispatch } = this.props;
dispatch(fetchUser(username));
}
render() {...}
}
export default connect((state, ownProps) => {
return {
username: ownProps.params.username,
isAuthenticated: state.auth.isAuthenticated
};
})(Profile);
And the fetchUser action looks like this (redux-api-middleware):
function fetchUser(id) {
let token = localStorage.getItem('jwt');
return {
[CALL_API]: {
endpoint: `http://localhost:3000/api/users/${id}`,
method: 'GET',
headers: { 'x-access-token': token },
types: [FETCH_USER_REQUEST, FETCH_USER_SUCCESS, FETCH_USER_FAILURE]
}
}
}
The reason I added componentWillReceiveProps function is to react when the URL changes to another :username and to load that users profile info. At a first glance everything seems to work but then I noticed while debugging that componentWillReceiveProps function is called in a infinite loop and I don't know why. If I remove componentWillReceiveProps then the profile doesn't get updated with the new username but then I have no loops problem. Any ideas?
Try adding a condition to compare the props. If your component needs it.
componentWillRecieveProps(nextProps){
if(nextProps.value !== this.props.value)
dispatch(action()) //do dispatch here
}
Your componentWillReceiveProps is in an infinite loop because calling fetchUser will dispatch an action that will update the Props.
Add a comparison to check if the specific prop changes before dispatching the action.
EDIT:
In React 16.3+ componentWillReceiveProps will be slowly deprecated.
It is recommended to use componentDidUpdate in place of componentWillReceiveProps
componentDidUpdate(prevProps) {
if (this.props.params.username !== prevProps.params.username) {
dispatch(fetchUser(username));
}
}
See https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#fetching-external-data-when-props-change
If you have react routes with some path params like profile/:username,
You can simply compare the props.location.pathname
componentWillReceiveProps(nextProps){
if(nextProps.location.pathname !== this.props.location.pathname){
dispatch()
}
}
I would like to use redux to store the state of my whole react application, however I am stuck with a particular case:
what to do with redux when the component needs a local state, modified by lifecycle methods like componentDidUpdate or componentDidMount ?
Example of a react component to contain "cards" arranged by isotope layout library:
componentDidMount() {
let container = ReactDOM.findDOMNode(this);
if (! this.state.isotope) {
this.setState({ isotope: new Isotope(container, {itemSelector: '.grid-item', layoutMode: 'masonry'})});
}
}
componentDidUpdate(new_props, new_state) {
if (new_state.items_list != this.state.items_list) {
if (this.state.isotope) {
this.state.isotope.reloadItems();
this.state.isotope.layout();
this.state.isotope.arrange();
}
}
}
Is there a way to remove the local state in this component and to use redux instead ? I can't see how to do it
Put your items_list in your redux state. Your reducer might look like this:
const initialState = {
items: []
};
export function myReducer(state = initialState, action) {
switch (action.type) {
case 'SET_ITEMS':
return Object.assign({}, state, {
items: action.items
});
}
return state;
}
Or for something a little more complex:
const initialState = {
items: []
};
export function myReducer(state = initialState, action) {
switch (action.type) {
case 'ADD_ITEM':
return Object.assign({}, state, {
items: [ ...state.items, action.item ]
});
case 'REMOVE_ITEM':
return Object.assign({}, state, {
items: [
...state.items.slice(0, action.index),
...state.items.slice(action.index + 1)
]
});
}
return state;
}
Once you've configured your store and Provider (see the Redux docs), set up your "smart component" like so:
function mapStateToProps(state) {
return {
items: state.items
}
}
function mapDispatchToProps(dispatch) {
const actions = bindActionCreators(actionCreators, dispatch);
return {
addItem: actions.addItem,
removeItem: actions.removeItem
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Root);
Now your items and actions are props to your Root component. If your items live in a lower order component, simply pass them down the tree as props.
I hope that gives you the general idea. With Redux what you'll find that your React components will use state a lot less and props a lot more.
One more thing...
This might be a minor matter, but I urge you NOT to store your isotope object on the component state. (Regardless of whether or not you use Redux.) The isotope object isn't really a piece of state, it's your view. In React, a component updates in response to a change in state. But your componentDidUpdate does the reverse: it changes the state in response to a component update.
As an alternative, simply store it on the object itself. i.e.
componentDidMount() {
const container = ReactDOM.findDOMNode(this);
this.isotope = new Isotope(container, {
itemSelector: '.grid-item',
layoutMode: 'masonry'
});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.items !== this.props.items) {
this.isotope.reloadItems();
this.isotope.layout();
this.isotope.arrange();
}
}
(Whilst normally I would recommend against against using these sort of instance variables in React, DOM manipulation libraries like Isotope are a worthy exception.)