Dispatched and re-rendered callback? - reactjs

It's easiest to explain what I'm trying to accomplish with an example:
addContact = ev => {
ev.preventDefault();
this.props.setField('contacts', contacts => update(contacts, {$push: [{name: 'NEW_CONTACT'}]}));
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
};
In this example, this.props.setField dispatches an action which causes an extra field to be added to my form.
this.props.setFocus then attempts to focus this new field.
This won't work because the form hasn't re-rendered yet when setFocus is called.
Is there any way to get a callback for when my component has been re-rendered after a dispatch call?
If you need to see it, setField looks like this:
setField(name, value) {
if(_.isFunction(value)) {
let prevValue = _.get(data, name);
if(prevValue === undefined) {
let field = form.fields.get(name);
if(field) {
prevValue = field.props.defaultValue;
}
}
value = value(prevValue);
}
dispatch(actions.change(form.id, name, value));
},

I would put
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
in componentDidUpdate and I would call it on some condition. Like let's say, prevProps.data.contact.length < this.props.data.contacts.
UPDATE
You should keep this:
addContact = ev => {
ev.preventDefault();
this.props.setField('contacts', contacts => update(contacts, {$push: [{name: 'NEW_CONTACT'}]}));
};
In a parent component, and in that component you will render all the sub components:
render() {
return {
<div>
{contacts.map(c => <ContactComponent key='blah' contact={c}>)}
<a onClick={addContact}>Add Contact</a>
</div>
};
}
Then your contact component, will be as you like, the same goes for all the other elements you want to accommodate with this functionality.
At that point, you're asking:
Where is the focus thingy?
What you need for this abstraction-ish is higher order composition. I will give you an example, but please make time to read about HOCs.
This will be you HOC:
function withAutoFocusOnCreation(WrappedComponent) {
// ...and returns another component...
return class extends React.Component {
componentDidMount() {
// contacts string below can be changed to be handled dynamically according to the wrappedComponent's type
// just keep in mind you have access to all the props of the wrapped component
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
And then in each child component you can use it as a decorator or just call it with your HOC and that's all. I won't write more, but do make the time to read more about HOCs, here is the official documentation's page
official documentation's page. But you can check Dan Abramov's video on egghead as well. I hope my answer helps you, please accept it if it does :) Take care!

Related

Why is the paragraph element not rendering the component's state on first load?

I have been breaking my head over this issue for the past 2-3 days. I am trying to show a new user's displayName set and stored on the Firebase Authentication database upon the load of their account page. I can not seem to get it showing upon the first load. I tried the setState way of doing it within the UserAccount component itself but it causes the same issue. The page needs to be manually refreshed for the username state to change and the p element to show the displayName in the screen. Please help me figure this out. Here's the code of the UserAccount component. If you need other codes from other components, please let me know.
class UserAccount extends React.Component {
constructor(props) {
super(props)
this.state = {
username: this.props.user.displayName
}
this.signout = this.signout.bind(this);
}
signout() {
firebase.auth().signOut()
}
The following commented code is my first try to get the displayName to show but it had the same issue so I tried to pass the props from the App.js component to see if it would work but it again has the same issue:
// componentDidMount () {
// firebase.auth().onAuthStateChanged((user) => {
// if (user) {
// var displayName = user.displayName;
// console.log(displayName);
// this.setState((state) => {
// return {username: displayName} })
// } else {
// console.log('Please sig in')
// }
// })
// }
render () {
return (
<div>
<h1> You are in </h1>
<button onClick={this.signout}>Sign Out</button>
<p>Hey {this.state.username}</p>
</div>
)
}
}
In your code you are using props value to initialize state in constructor. Since child component is already rendered, changing the parent prop doesn't call constructor of the child component. That's why it doesn't change the username and you need to forcefully trigger an update either by calling setState or forceUpdate. You can rather use getDerivedStateFromProps also as mentioned in above answer.
Otherwise, you can use react hooks useState and useEffect to fix this. By passing props as dependency for useEffect, component will be rendered each time prop changes.
https://codesandbox.io/s/red-mountain-2zqbn?file=/src/App.js:202-206
https://github.com/uberVU/react-guide/issues/17
make sure the props has user.displayName, keep state.username == "" and try this:
static getDerivedStateFromProps(props,state){
if(props.user.displayName !== state.username){
state['username'] = props.user.displayName
return state;
}
return null;
}

State Updates But Does Not Trigger a Render When I Reassign an Object to an Existing Object

Source Code
This Works
const { id, title, complete, updated_at } = todoItem
...
todoItems[todoItemIndex].title = title
todoItems[todoItemIndex].complete = complete
todoItems[todoItemIndex].updated_at = updated_at
this.setState({ todoItems })
This Doesn't Work
todoItems[todoItemIndex] = todoItem
this.setState({ todoItems })
or
todoItems[todoItemIndex] = { ...todoItem }
this.setState({ todoItems })
or
this.setState(state => {
todoItems: state.todoItems.map(item => {
if (item.id === todoItem.id) {
item = { ...todoItem }
}
return item
})
})
Other Notes
I call componentDidUpdate() to confirm that this.state.todoItems is actually being updated when I use todoItems[todoItemIndex] = { ...todoItem }.
componentDidUpdate() {
console.log(this.state.todoItems)
}
always update your state in immutable way, so the current solution and the other to options are not valid.
the problem with your last solution is that you forgot to put the a return or even better put a Parenthesis around the object that is being return by the updater func.
in your case
this.setState(prevState => ({
todoItems: prevState.todoItems.map(item => {
if (item.id === todoItem.id) {
return {...todoItem};
}
return item;
}))
})
Thanks to ryanhinerman
for their answer on Reddit as outlined below.
The better solution is to not save the data from props inside the constructor. Instead, use this.props.todoItem directly. If you want to make it look prettier, you can destructure it at the top of the TodoItem's render function like so: const { todoItem } = this.props;
What's actually happening here is going to be slightly difficult to explain, but it's important to your understanding of React, so hopefully I can do an okay job of explaining it.
constructor(props) {
super(props);
this.todoItem = this.props.todoItem;
}
This constructor is only going to be called one time for the lifetime of a component, at the initial creation. What you're doing here is taking this.props.todoItem at the initial creation and saving it inside the component. This constructor will not be called again, so this.todoItems will never be updated no matter how many times this.props.todoItems changes.
<TodoItem todoItem={todoItem} key={todoItem.id} />
Here we give our component a key, and when this key changes, React knows to recreate our component. Because the id of todoItem never changes, React never recreates that component. If we were to use todoItem.title or something, the key would change and the entire component would be reset and the constructor would be called again, "solving" the problem. This, however, isn't the correct solution.
The better solution is to not save the data from props inside the constructor. Instead, use this.props.todoItem directly. If you want to make it look prettier, you can destructure it at the top of the TodoItem's render function like so: const { todoItem } = this.props;

Where is the best place to make calculations outside the render method in React?

I have a render method in my container component like this:
render() {
const { validationErrors } = this.state
const { errorsText, errorsFields} = validationErrors.reduce(
(acc, error) => {
acc.errorsText.push(error.text)
acc.errorsFields[error.field.toLowerCase()] = true
return acc
},
{
errorsText: [],
errorsFields: {},
},
)
return (
<MyViewComponent
errorsText={errorsText}
errorsFields={errorsFields}
/>
)
}
As you can see every render there are some computations happens (returned array and object with the new values), then I pass it into my child component as a props. I have a feeling that this is a wrong pattern. We should keep render function 'pure'. Isn't it? The question is: Where is the best place for making such computations outside the render?
If this were a functional component (which I highly recommend you use in the future, by the way), you'd be able to use the 'hook' useEffect to recalculate errorsText and errorsField whenever this.state.validationErrors changes, and only when it changes.
For your Class Component, however, I assume at some point you set this.state.validationErrors. What you should do is create a method that runs your reducer and stores errorsText and errorsField to state, then place a call to this method after each point you set this.state.validationErrors. Then, remove the logic in the render method and replace errorsText and errorsField with this.state.errorsText and this.state.errorsField respectively.
Doing this will ensure you only ever run your reducer when necessary (i.e. when this.state.validationErrors changes).
Your component would end up looking something like this:
class MyComponent extends Component {
...
someCallback() {
const validationErrors = someFunctionThatReturnsErrors();
// We do the logic here, because we know that validationErrors
// could have changed value
const { errorsText, errorsFields } = validationErrors.reduce(
(acc, error) => {
acc.errorsText.push(error.text);
acc.errorsFields[error.field.toLowerCase()] = true;
return acc;
}, {
errorsText: [],
errorsFields: {},
},
);
// Put everything in the state
this.setState({
validationErrors, // you may not even need to set this if it's not used elsewhere`
errorsText,
errorsFields
});
}
...
render() {
const {
errorsText,
errorsFields
} = this.state;
return (
<MyViewComponent
errorsText={errorsText}
errorsFields={errorsFields}
/>
);
}
}
It is pure, as it has no side effects.
As long as this does not create performance issues I see no problem with this. If it does create performance issues, you should look into memoizing the reduce. If you were using hooks you could use the built-in React.useMemo for this. While using class version you could look into something like https://www.npmjs.com/package/memoize-one

React subscriptions which depend on state

We are currently refactoring to use higher-order components. For the most part this is making everything much simpler.
We have HOCs for fetching data and listening to stores. For example, we have connectStores, which takes a list of stores to subscribe to and a function to fetch the data (to pass as extra props):
connectStores(FooComponent, [FooStore], function (props) {
return {
foo: FooStore.get(props.id),
};
});
However, there are a few places where the process of fetching the data from the store depends upon the state. For example, we have a SelectFooPopup the presents the user with a list of items to select from. But there is also a search box to filter the list, so at the moment the component listens directly to the store and then fetches the data itself like this:
componentDidMount() {
var self = this;
this.listenTo(FooStore, 'change', function () {
self.forceUpdate();
});
}
render() {
var items = FooStore.search(this.state.searchText);
// render...
}
(this.listenTo is a mixin which we're trying to replace with HOCs so we can use ES6 classes)
I can think of a few options, but I don't like any of them:
Option 1: Remove listenTo and cleanup the listener manually
componentDidMount() {
var self = this;
this.listener = function () {
self.forceUpdate();
};
FooStore.on('change', this.listener);
}
componentWillUnmount() {
if (this.listener) {
FooStore.removeListener('change', this.listener);
}
}
render() {
var items = FooStore.search(this.state.searchText);
// render...
}
I really hate having to do this manually. We did this before we had the listenTo mixin and it's far too easy to get wrong.
This also doesn't help when the subscription has to fetch the data from the server directly rather than using a pre-filled store.
Option 2: Use connectStores but don't return any extra data
class SelectFooPopup extends React.Component {
render() {
var items = FooStore.search(this.state.searchText);
}
}
connectStores(SelectFooPopup, [FooStore], function (props) {
// Just to forceUpdate
return {};
});
This just feels wrong to me. This is asking for trouble when we start optimising for pure components and suddenly the child component doesn't re-render anymore.
Option 3: Use connectStores to fetch all the data and then filter it in render
class SelectFooPopup extends React.Component {
render() {
var items = filterSearch(this.props.items, this.state.searchText);
}
}
connectStores(SelectFooPopup, [FooStore], function (props) {
return {
items: FooStore.getAllItems(),
};
});
But now I have to have a completely separate filterSearch function. Shouldn't this be a method on the store?
Also, it doesn't make much difference in this example, but I have other components with a similar issue where
they are fetching data from the server rather than subscribing to a pre-filled store. In these cases the
data set is far too large to send it all and filter later, so the searchText must be available when fetching the data.
Option 4: Create a parent component to hold the state
Sometimes this is the right solution. But it doesn't feel right here. The searchText is part of the state of this component. It belongs in the same place that renders the search box.
Moving it to a separate component is confusing and artificial.
Option 5: Use a "parentState" HOC
function parentState(Component, getInitialState) {
class ParentStateContainer extends React.Component {
constructor(props) {
super();
this.setParentState = this.setParentState.bind(this);
if (getInitialState) {
this.state = getInitialState(props);
} else {
this.state = {};
}
}
setParentState(newState) {
this.setState(newState);
}
render() {
return <Component {...this.props} {...this.state} setParentState={ this.setParentState } />;
}
}
return ParentStateContainer;
}
// Usage:
parentState(SelectFooPopup, function (props) {
return {
searchText: '',
};
});
// In handleSearchText:
this.props.setParentState({ searchText: newValue });
This also feels really wrong and I should probably throw this away.
Conclusion
In React we have 2 levels: props and state.
It seems to me that there are actually 4 levels to think about:
props
data that depends on props only
state
data that depends on props and state
render
We can implement layer 2 using HOCs. But how can we implement layer 4?

Re-render React component when prop changes

I'm trying to separate a presentational component from a container component. I have a SitesTable and a SitesTableContainer. The container is responsible for triggering redux actions to fetch the appropriate sites based on the current user.
The problem is the current user is fetched asynchronously, after the container component gets rendered initially. This means that the container component doesn't know that it needs to re-execute the code in its componentDidMount function which would update the data to send to the SitesTable. I think I need to re-render the container component when one of its props(user) changes. How do I do this correctly?
class SitesTableContainer extends React.Component {
static get propTypes() {
return {
sites: React.PropTypes.object,
user: React.PropTypes.object,
isManager: React.PropTypes.boolean
}
}
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
render() {
return <SitesTable sites={this.props.sites}/>
}
}
function mapStateToProps(state) {
const user = userUtils.getCurrentUser(state)
return {
sites: state.get('sites'),
user,
isManager: userUtils.isManager(user)
}
}
export default connect(mapStateToProps)(SitesTableContainer);
You have to add a condition in your componentDidUpdate method.
The example is using fast-deep-equal to compare the objects.
import equal from 'fast-deep-equal'
...
constructor(){
this.updateUser = this.updateUser.bind(this);
}
componentDidMount() {
this.updateUser();
}
componentDidUpdate(prevProps) {
if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID (this.props.user.id !== prevProps.user.id)
{
this.updateUser();
}
}
updateUser() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Using Hooks (React 16.8.0+)
import React, { useEffect } from 'react';
const SitesTableContainer = ({
user,
isManager,
dispatch,
sites,
}) => {
useEffect(() => {
if(isManager) {
dispatch(actions.fetchAllSites())
} else {
const currentUserId = user.get('id')
dispatch(actions.fetchUsersSites(currentUserId))
}
}, [user]);
return (
return <SitesTable sites={sites}/>
)
}
If the prop you are comparing is an object or an array, you should use useDeepCompareEffect instead of useEffect.
componentWillReceiveProps() is going to be deprecated in the future due to bugs and inconsistencies. An alternative solution for re-rendering a component on props change is to use componentDidUpdate() and shouldComponentUpdate().
componentDidUpdate() is called whenever the component updates AND if shouldComponentUpdate() returns true (If shouldComponentUpdate() is not defined it returns true by default).
shouldComponentUpdate(nextProps){
return nextProps.changedProp !== this.state.changedProp;
}
componentDidUpdate(props){
// Desired operations: ex setting state
}
This same behavior can be accomplished using only the componentDidUpdate() method by including the conditional statement inside of it.
componentDidUpdate(prevProps){
if(prevProps.changedProp !== this.props.changedProp){
this.setState({
changedProp: this.props.changedProp
});
}
}
If one attempts to set the state without a conditional or without defining shouldComponentUpdate() the component will infinitely re-render
You could use KEY unique key (combination of the data) that changes with props, and that component will be rerendered with updated props.
componentWillReceiveProps(nextProps) { // your code here}
I think that is the event you need. componentWillReceiveProps triggers whenever your component receive something through props. From there you can have your checking then do whatever you want to do.
I would recommend having a look at this answer of mine, and see if it is relevant to what you are doing. If I understand your real problem, it's that your just not using your async action correctly and updating the redux "store", which will automatically update your component with it's new props.
This section of your code:
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Should not be triggering in a component, it should be handled after executing your first request.
Have a look at this example from redux-thunk:
function makeASandwichWithSecretSauce(forPerson) {
// Invert control!
// Return a function that accepts `dispatch` so we can dispatch later.
// Thunk middleware knows how to turn thunk async actions into actions.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
You don't necessarily have to use redux-thunk, but it will help you reason about scenarios like this and write code to match.
A friendly method to use is the following, once prop updates it will automatically rerender component:
render {
let textWhenComponentUpdate = this.props.text
return (
<View>
<Text>{textWhenComponentUpdate}</Text>
</View>
)
}
You could use the getDerivedStateFromProps() lifecyle method in the component that you want to be re-rendered, to set it's state based on an incoming change to the props passed to the component. Updating the state will cause a re-render. It works like this:
static getDerivedStateFromProps(nextProps, prevState) {
return { myStateProperty: nextProps.myProp};
}
This will set the value for myStateProperty in the component state to the value of myProp, and the component will re-render.
Make sure you understand potential implications of using this approach. In particular, you need to avoid overwriting the state of your component unintentionally because the props were updated in the parent component unexpectedly. You can perform checking logic if required by comparing the existing state (represented by prevState), to any incoming props value(s).
Only use an updated prop to update the state in cases where the value from props is the source of truth for the state value. If that's the case, there may also be a simpler way to achieve what you need. See - You Probably Don't Need Derived State – React Blog.

Resources