React redux current user param gets too late to use - reactjs

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);

Related

How do I make Component rerender when props passed from parent and state changed?

I'm making a Todolist. The Submit component to handle input from user and make PUT request to the server to update the database. The Progress component shows the progress. When user submits, Submit will send props to Progress and Progress will call axios.get to get data and update the state. The props is passed successfully and the Progress state's variables do change but the component is not re-rendered.
submit.js
import Progress from './progress';
export default class Submit extends Component {
constructor(props) {
super(props);
this.state = {
updateProgress: 'No'
}
}
handleSubmit = (e) => {
// ...
this.setState({updateProgress: 'Yes'}));
}
render() {
return (
<div>
<div style={{visibility: 'hidden'}}>
<Progress update={this.state.updateProgress} />
</div>
// Form
</div>
)
}
}
progress.js
export default class Progress extends Component {
constructor(props) {
super(props);
this.state = {
tasksDone: 0
};
}
getData = () => {
axios.get('/task')
.then(res => {
let resData = res.data[0];
this.setState({tasksDone: resData.done});
})
.catch(err => console.log(err));
}
componentDidUpdate(prevProps) {
if (this.props.update != prevProps.update) {
console.log('Props changed');
this.getData();
}
}
componentDidMount() {
this.getData();
}
render() {
return (<div>You have done: {this.state.tasksDone}</div>)
}
}
I know getData() won't be called again after the first submit because the props passed doesn't change but that's not the case because it doesn't work the first submit already.
When I use console.log(this.state.tasksDone) in componentDidUpdate() the tasksDone is updated but the div is not re-rendered, even I tried using forceUpdate() after calling getData().
I have read other questions and they said mutating state doesn't trigger re-render. Is my setState just mutating? I see changing state this way is very common. What am I missing?
Reproduce problem: https://codesandbox.io/s/determined-curie-ipr6v
The problem lies in your use for Progress.
The Progress you are updating is not the Progress that is shown on the display.
https://codesandbox.io/s/vigilant-ardinghelli-vd9nl
this is the working code you provided.

Why setState interrupt componentDidUpdate?

I have this component (simplified version):
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
data: {}
};
}
componentDidUpdate(prevProps, prevState) {
if(this.props.time && this.props.time !== prevProps.time){
this.setState({
isLoading: true
})
fetch(...).then(data => {
this.setState({
data: data
isLoading:false
}
}
}
render(){
{isLoading, data} = this.state;
return (isLoading ? /*show spinner*/ : /* show data*/);
}
}
This component works: it shows a spinner while fetching data, then it shows the data.
I'm trying to test it using jest and enzyme:
test('Mounted correctly', async() => {
let myComponent = mount(<MyComponent time='01-01-18'/>);
myComponent.setProps({time: '02-01-18'}); //necessary to call componentDidUpdate
expect(myComponent.state()).toMatchSnapshot();
}
From my knowledge, in order to call componentDidUpdate you have to call setPros (link). However, following the debugger, the call end when hitting:
this.setState({
isLoading: true
})
Which is kinda of expected, the problem is that the snapshot is:
Object {
"isLoading": true
"data": {}
}
Which is, of course, something that I don't want. How can I solve this?
UPDATE: I found a(n ugly) solution!
The problem is that what we want to test is this setState is completed:
this.setState({
data: data
isLoading:false
}
Now, this doesn't happen even by setting await myComponent.setProps({time: '02-01-18'}); (as suggested in one of the answers), because it doesn't wait for the new asynchronous call created by the setState described above.
The only solution that I found is to pass a callback function to props and call it after setState is completed. The callback function contains the expect that we want!
So this is the final result:
test('Mounted correctly', async() => {
let myComponent = mount(<MyComponent time='01-01-18'/>);
const callBackAfterLastSetStateIsCompleted = () => {
expect(topAsins.state()).toMatchSnapshot();
}
myComponent.setProps({time: '02-01-18', testCallBack: callBackAfterLastSetStateIsCompleted}); //necessary to call componentDidUpdate
expect(myComponent.state()).toMatchSnapshot();
}
And modify the component code as:
this.setState({
data: data
isLoading:false
},this.props.testCallBack);
However, as you can see, I'm modifying a component in production only for testing purpose, which is something very ugly.
Now, my question is: how can I solve this?
All you need to do here to test is make use of async/await like
test('Mounted correctly', async () => {
let myComponent = mount(<MyComponent time='01-01-18'/>);
await myComponent.setProps({time: '02-01-18'}); //necessary to call componentDidUpdate, await used to wait for async action in componentDidUpdate
expect(myComponent.state()).toMatchSnapshot();
}

Redux ComponentDidMount not firing

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.

Initialize state with async data in Redux

I want to populate two tokens properties via AJAX whenever the state is created. It seems that Redux doesn't have much documentation on this. I can't use componentWillMount to do this because the way I have my containers set up it just won't work.
const Auth = Record({
token: '',
yelpToken: '',
});
Is there someway run a function that will happen before createStore is invoked?
You can replace your index with this:
class EntryPoint extends Components {
constructor(){
this.state = {
//can be replaced with store state..
finishedFetching: false
}
}
componentDidMount() {
//you can chain dispatches with redux thunk
dispatch(fetchAsyncData())
.then(() => this.setState({finishedFetching: true}))
}
render() {
return this.state.finishedFetching ? <App/> : null;
}
}

infinite loop when dispatching in componentWillReceiveProps

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()
}
}

Resources