I know we can easily send the content of mapStateToProps in the component's state by doing so :
constructor(props){
super(props);
this.state = {
filteredApps: this.props.apps
}
}
In this usecase, this.state.filteredApps gets filled with what was mapped to props from Redux.
But what if this.props.apps is only filled properly after an async call? In an async context, this.props.apps will probably be an empty array for when it is initialized until the real data is fetched. Take this as an example :
class AppFilterer extends React.Component {
constructor(props) {
super(props);
this.state = {
filteredApps : this.props.apps
}
}
componentWillMount() {
this.props.getApps();
}
render(){ return <div> </div> }
}
const mapStateToProps = state => {
let { apps } = state.Admin;
return { apps };
};
export default connect(mapStateToProps, { getApps })(AppFilterer);
In this case, my Redux action (which is caught by an Saga) this.props.getApps(); is the call that fills my props full of apps and is called from the componentWillMount function. It is initialized as an empty array and then gets filled with apps once the call is complete.
I wish to filter these apps once they are fetched from the API so want to put them inside my component's state so that I don't mess with the Redux state. What is the best practice for updating the component's state in this case? In other words, is there any way to take the result of a saga that has been mapped to props and set it into the component's state or am I looking for a weird pattern and should filter it some other way?
First of all API calls go in componentDidMount not in componentWillMount which is also now deprecated. Please refer this guide:
https://reactjs.org/docs/react-component.html
Secondly, when you are using redux state and mapping it to props, you should not set that in your component local state, that’s not a good practice. You’ll receive updated props when your promise will return and you can always rely on props in that scenario.
But if you still want to do that you can override componentDidUpdate(prevProps) which will be called when your props or state is updated. Here is where you can set your state if you still want to do that.
Note for your filter thing
You can do filtering in componentDidUpdate method like:
this.setState({filteredApps. this.props.apps.filter(<your filter logic>)})
Related
I have a route (using React-Router) with component which it renders. Every time this route opened and its component created I need to reset some part of Redux state (one reducer's state in fact), used in this component. This reducer is shared in some other parts of the app, so I use Redux state and not local component's state. So how can I reset the reducer's state every time my component created? I am wondering about best practice to do this.
I think if I'll dispatch actions in componentDidMount method, there will be blinking of previous state for some second.
Can I dispatch action to reset some reducer's state in component's constructor?
Is there any better approach? Can I somehow to set initial state in connect() function, so component will have resetted state each time it created? I check the docs, but I cannot find some argument for this.
Yes, you can dispatch action in constructor to change reducer state
constructor(prop){
super(prop);
prop.dispatch(action);
}
Another approach you can try is setting default props so that you don't need to call reducer(dispatch action)
ButtonComponent.defaultProps = {
message: defaultValue,
};
One possible solution I can think of...
If you could go with the first approach, you can try to stop the previous state being shown while component is being re-rendered with reset state.
The only phase during which you would see the prevState is during the initial render. How about an instance variable to track the render count.
A rough draft.
import React from "react";
import { connect } from "react-redux";
import { add, reset } from "./actions";
class Topics extends React.Component {
renderCount = 0;
componentDidMount() {
// Dispatch actions to reset the redux state
// When the connected props change, component should re-render
this.props.reset();
}
render() {
this.renderCount++;
if (this.renderCount > 1) {
return (
<div>
{this.props.topics.map(topic => (
<h3 id={topic}>{topic}</h3>
))}
</div>
);
} else {
return "Initializing"; // You can return even null
}
}
}
const mapStateToProps = state => ({ topics: state });
const mapDispatchToProps = (dispatch) => {
return {
add(value){
dispatch(add(value));
},
reset(){
dispatch(reset());
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Topics);
Here renderCount is a class variable, that keeps incrementing on component render. Show a fallback UI on first render to avoid previous state from being shown and on second render (due to redux store update), you could display the store data.
A working example added below. I have added an approach to avoid the fallback UI as well. Have a look if it helps.
https://stackblitz.com/edit/react-router-starter-fwxgnl?file=components%2FTopics.js
I want to create an app with react and redux. My component subscribed to several states from the redux store, some of the state-data need to be prepared before the rendering can take place. Do I need to put the prepareData function into componentWillReceiveProps and write it to the state afterwards? It seems to create a lot of queries in the componentWillReceiveProps. Is there a best practice?
componentWillReceiveProps(nextProps) {
if (this.props.dataUser !== nextProps.dataUser) {
this.prepareData(nextProps.dataUser);
}
if (this.props.dataProject !== nextProps.dataProject) {
.....
}
if (this.props.dataTasks !== nextProps.dataTasks) {
.....
}
}
As Axnyff suggests, you can do your data preparation in mapStateToProps, this will trigger a render each time your redux state updates (your component can be stateless this way) :
mapStateToProps = (state) => {
const dataUserPrepared = prepareData(state.dataUser);
return { dataUser: dataUserPrepared };
}
If you have a lot of different data to prepare, which updates individually, that can be a loss in performance.
In this case you can use componentWillReceiveProps like in your question, this is fine because the setState in your prepareData() function will be batched with the received props to trigger only one render per prop update.
If you were using an app without redux then the solution would be to prepare your data before you call this.setState().
I believe the same solution applies to when using redux, your can prepare your data inside your action because you return the action object having a type and payload.
You can also prepare your data inside your reducer before returning the state object.
You could even prepare your data inside mapStateToProps of your component.
But in case you want to specific conditions under which component should re-render when state changes, then you do that in shouldComponentUpdate()
I have a react component with a form for updating database records.
Here's the thing: the data is loaded with React-Relay QueryRenderer component as follows:
class Update extends Component {
//constructor..
//some stuff
render() {
return(
<QueryRenderer
environment={environment}
query={UpdateQuery}
render={({error, props}) => {
//validations
return (
<Form loading={this.state.loading}>
//inputs
</Form>
)...
}/>
)}
The props variable is supposed to store the result from server response if successful. However, I need to call the update with this.state values.
I need a way to setState with props values.
I have tried with componentDidMount and using refs both string refs and callback ones to get defaultValue from Inputs. I got undefined values when calling this.refs
For now, it works if I call a function within QueryRenderer render function that sets the state with props if state is empty. E.g
function initValues(props){
if(!this.state.name)
this.setState({name: props.result.name})
}
But it results in an anti-pattern (pure render method) that I need to solve.
Edit:
For anyone wondering how I solved this. Thanks to charlie's answer I managed to create a UpdateForm component wrapper that receives the props from QueryRenderer, and in order to update my parent's component state I passed my handleChange function as props to my FormUpdate component
Use componentWillReceiveProps in your Form component
class Form extends React.Component {
...
componentWillReceiveProps(nextProps) {
if (nextProps.loading) return
this.setState({
name: nextProps.name
})
}
...
}
This will only set the state once as soon as the data is available, since QueryRenderer only calls render once after the data has loaded.
What is the best place to store the result of an expensive calculation from the React props which I use in render() but do not want to execute at each render() ?
constructor(props) {
super(props)
const result = this.doExpensiveCalculation(props)
}
componentWillReceiveProps(nextProps) {
// if nextProps differ from props
const result = this.doExpensiveCalculation(nextProps)
}
doExpensiveCalculation(props) {
// Some expensive stuff
}
render(){
// Use doExpensiveCalculation(this.props) here
}
The options are this and state but both I see rather unsatisfying. Is there a ready solution which uses memoisation?
On the other hand, should I worry about optimizing this ? I read that React can rerender component even if the props have not changed but does this happen often ?
You can handle the re-rendering in the lifecycle method of shouldComponentUpdate. Default value is always return true. By returning false there React will not re-render the component.
See the docs for more. Besides that, React only updates if a state change occurs since props are read-only.
Your options are to store it as you suggested or have a class with a static field to keep it there.
If all you want to do is perform the expensive calculation whenever you get new props, instead of on every render, you probably want componentWillReceiveProps:
componentWillReceiveProps() is invoked before a mounted component receives new props.
As far as where to store them, you can either store them in state, or as a property directly on the component instance. Either will work just as well.
You want to make sure compare values though, to avoid unnecessarily recomputing.
For example:
componentWillReceiveProps(nextProps) {
if (nextProps.someValue !== this.props.someValue) {
this.someResult = this.performExpensiveCalculation(nextProps.someValue);
}
}
I want to create HOC container which changes its state asynchronously.
It will be used by two components.
What is the best way for these components to subscribe to success/failure of HOC async methods and update their internal state accordingly?
REDUX can also be used.
your compoents must be pure and reusable do not tie them to state
cause app will just waste system resources doing unnessary re
render. The use of state should be as limited as it can get. When
you use state, you run the risk of introducing a number of
(sometimes subtle) errors in the behaviour and rendering of your
components. If you decide to use properties to define your initial
state; your properties might change, leaving your initial state
calculations based on stale data. You also introduce tight coupling
between the properties that need to be defined, and the internal
state of the component.
A general rule is not to use state for static components. If you
component does not need to change, based on external factors, then
do not use state. It’s better to calculate rendered values in the
render() method. link https://www.youtube.com/watch?v=ymJOm5jY1tQ.
https://medium.com/#mweststrate/3-reasons-why-i-stopped-using-react-setstate-ab73fc67a42e#.h3ioly71l
import React, {Component} from 'react';
export const fetchResource = msg => WrappedComponent =>
class extends Component {
constructor(props){
super(props);
this.state = {
resource: null,
msg: null
};
}
componentDidMount(){
this.setState({msg})
axios.get('https://api.github.com/users/miketembos/repos')
.then((response)=>{
console.log(response);
if(response.status===200){
return response.data;
} else {
throw new Error("Server response wasn't ok");
}
})
.then((responseData)=>{
this.setState({resource:responseData});
}).catch((error)=>{
this.props.history.pushState(null, '/error');
});
}
render(){
const {resource} = this.state
//check if the resource is valid eg not empty or null
if(!resource){return <div>Loading...... or show any othe component you want load.....</div>}
return <Post {...this.props} {...resource } />
}
}