Unable to access this.props data inside constructor, react+redux - reactjs

I'm using redux, redux-form and react-select inside the Form component as shown below. I am having problem with using props as a multi-select value of the form.
Multi-select value displays and works correctly when props are loaded, or when the page is refreshed. However it doesn't seem to work correctly during normal use cases.
Smart container calls asyncconnect to dispatch book-runner data, and I'm using connect in this component to access this.props.bookRunners.
class Form extends Component {
constructor(props) {
super(props);
const bookRunnerArray = this.getBookRunnerArray(this.props.bookRunners.bookRunners);
this.state = {
options: bookRunnerArray,
value: [],
};
}
Connecting to store:
const mapStateToProps = (state) => {
return {
bookRunners: state.bookrunners,
};
};
const mapDispatchToProps = (dispatch) => {
return { actions: bindActionCreators(dealActions, dispatch) }
};
export default connect(mapStateToProps, mapDispatchToProps)(Form);
I think this.props.bookRunners is empty when I try to set initial state inside the constructor. I tried using componenetWillMount() but no luck. Please help!

Looks like you just have a typo on your mapStateToProps function. Change state.bookrunners to state.bookRunners on the return object.

Adding the code below solved the problem. super(props) and prop accessed in constructor were all still "fetching". Therefore did not have the data needed. componenetWillMount also was the same case. However, I was able to access the data from componentWillReceiveProps(). Please let me know if anyone has any thoughts.
componentWillReceiveProps() {
if(!this.props.bookRunners.isFetching && this.state.options.isEmpty){
const bookRunnerArray = this.getBookRunnerArray(this.props.bookRunners.bookRunners);
this.setState ({
options: bookRunnerArray,
})
}
}
--- day 2 was working on another container with the same problem. And decided that componentDidUpdate() + initializing inside constructor behaved more consistently.
So below + constructor shown above.
componentDidUpdate() {
if(!this.props.bookRunners.isFetching && this.state.options.isEmpty){
const bookRunnerArray = this.getBookRunnerArray(this.props.bookRunners.bookRunners);
this.setState ({
options: bookRunnerArray,
})
}
}

Related

React - How can I force call a props function

I have a Component who calls a function via props
function mapState(state) {
return state;
}
const actionCreators = {
getById: productActions.getById,
};
export default connect(mapState, actionCreators)(ShopBook);
this function is used at the constructor of the Component to retrieve content from my API and store the response into a reducer
constructor(props) {
super(props);
this.props.getById(this.props.match.params.slug)
this.state = {
}
}
then in the render I use the store
render() {
const product = store.getState().product.item
return (
// my code
);
}
my problem is, if I change content in the database the function does not retrieve the latest change vía the api, I only see the changes if I force the browser by pressing the Shift key while click reload.
how can I force to get the latest content, where do I need to use the function instead the constructor?
I already tried this solution:
How to fetch data when a React component prop changes?
You are using redux wrongly. You should pass correct mapStateToPop. If u want to render. Since, u mappping entire state. Redux find no change. That is why it is not rerendering.
Sample:
const mapStateToProps = (state, ownProps) => ({
product: state.product
})
export default connect(mapState, actionCreators)(ShopBook);

Common DRY principle violation React Native/Redux

I seem to be having a reoccurring issue that I'm hoping there is a design solution out there that I am not aware of.
I'm running into the situation where I need to dispatch the exact same things from two different components. Normally I would set this to a single function and call the function in both of the components. The problem being that if I put this function (that requires props.dispatch) in some other file, that file won't have access to props.dispatch.
ex.
class FeedScreen extends Component {
.
.
.
componentWillReceiveProps(nextProps) {
let {appbase, navigation, auth, dispatch} = this.props
//This is to refresh the app if it has been inactive for more
// than the predefined amount of time
if(nextProps.appbase.refreshState !== appbase.refreshState) {
const navigateAction = NavigationActions.navigate({
routeName: 'Loading',
});
navigation.dispatch(navigateAction);
}
.
.
.
}
const mapStateToProps = (state) => ({
info: state.info,
auth: state.auth,
appbase: state.appbase
})
export default connect(mapStateToProps)(FeedScreen)
class AboutScreen extends Component {
componentWillReceiveProps(nextProps) {
const {appbase, navigation} = this.props
//This is to refresh the app if it has been inactive for more
// than the predefined amount of time
if(nextProps.appbase.refreshState !== appbase.refreshState) {
const navigateAction = NavigationActions.navigate({
routeName: 'Loading',
});
navigation.dispatch(navigateAction);
}
}
}
const mapStateToProps = (state) => ({
info: state.info,
auth: state.auth,
appbase: state.appbase
})
export default connect(mapStateToProps)(AboutScreen)
See the similar "const navigateAction" blocks of code? what is the best way to pull that logic out of the component and put it in one centralized place.
p.s. this is just one example of this kind of duplication, there are other situations that similar to this.
I think the most natural way to remove duplication here (with a react pattern) is to use or Higher Order Component, or HOC. A HOC is a function which takes a react component as a parameter and returns a new react component, wrapping the original component with some additional logic.
For your case it would look something like:
const loadingAwareHOC = WrappedComponent => class extends Component {
componentWillReceiveProps() {
// your logic
}
render() {
return <WrappedComponent {...this.props} />;
}
}
const LoadingAwareAboutScreen = loadingAwareHOC(AboutScreen);
Full article explaining in much more detail:
https://medium.com/#bosung90/use-higher-order-component-in-react-native-df44e634e860
Your HOCs will become the connected components in this case, and pass down the props from the redux state into the wrapped component.
btw: componentWillReceiveProps is deprecated. The docs tell you how to remedy.

Containing provider re-rendered by changes in a render props child using Redux

I have something like:
const higherOrderComponent = (WrappedComponent) => {
return class extends React.Component {
render() {
<CustomProvider
render={({isLoading, playNote, playNoteAtTime, stopNote}) => {
return <WrappedComponent />;
}
/>
}
};
I have a single redux store higher up in my app and when the user interacts with 'WrappedComponent', it dispatches an action to the store, which causes 'higherOrderComponent' to re-render.
This hasn't been a problem before, as I have been favoring stateless components, and letting the new state wash through the full app on state change worked with no performance issues.
The Custom provider is asynchronously loading some files though, so now there is a noticable issue.
An idea for a solution is to introduce state in the component at the 'higherOrderComponent' level. When the component is first loaded, I will populate the state from the store props.
Then I could save up and dispatch actions to update the store later at an opportune time.
I'm not sure if I have created an anti-pattern with the above though (I'm using a Higher Order Component with 'Render props' at the same time).
Or if anyone can recommend a better solution?
edit
I was asked to show what CustomProvider is doing so I have added part of it here:
class SampleProvider extends React.Component {
static propTypes = {
instrumentNames: PropTypes.array.isRequired,
audioContext: PropTypes.instanceOf(window.AudioContext),
render: PropTypes.func
};
static defaultProps = {
currentInstrument: 'mooga'
};
constructor(props) {
super(props);
this.playNote = this.playNote.bind(this);
this.state = {
activeAudioNodes: {},
instrument: null
};
}
componentDidMount() {
sampleLoader.init(this.props.audioContext, sampleFilePaths).then(loadedSamplesBuffers => {
this.samplesBuffers = loadedSamplesBuffers;
console.log('End: loading samples');
});
}
playNote = midiNumber => {
let play = samplePlayer(this.props.audioContext, this.props.mainOutput, this.samplesBuffers);
play(midiNumber, 0, null, null, this.props.currentInstrument);
};

ComponentDidMount not getting called after redux state update?

I think I'm missing a concept here about React and Redux. I'm trying to work with objects stored in redux, and I'm having trouble.
REDUX:
I have an action fetchItems, that gets all items from the database. This action works successfully.
REACT:
I have a container, UserProfile, that calls fetchItems in componentDidMount.
class UserProfile extends Component {
componentWillMount() {
console.log('------------ USER PROFILE -------------------');
}
componentDidMount() {
console.log('[ComponentDidMount]: Items: ', this.props.items);
this.props.fetchItems();
}
render() {
let profile = null;
console.log('[Render]: Items: ', this.props.items);
return <Auxillary>{profile}</Auxillary>;
}
}
const mapStateToProps = state => {
return {
items: state.items.items
};
};
const mapDispatchToProps = dispatch => {
return {
fetchItems: () => dispatch(actions.fetchItems())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
The problem I'm seeing is that this.props.items is always null (even though fetchItems is successful). The only way I can detect that items were stored in redux store is if I use componentWillRecieveProps(nextProps). Here, I successfully see the items in nextProps. I feel like using componentWillReceiveProps might be too "messy" though. I guess what I'm asking is, what is the standard way of dealing with updates to redux states in react?
Aseel
The cycle will be :
constructor()
componentWillMount() (will be soon deprecated by the way : https://medium.com/#baphemot/whats-new-in-react-16-3-d2c9b7b6193b)
render() => first render (this.props.items, coming from mapStateToProps will be undefined)
componentDidMount() => launching fetchItems() => changing redux state => changing the this.props.items => launching the second render() where this.props.items will be set.
So :
you should have two console.log('[Render]: Items: ', this.props.items);
you should deal with a "loading" state when the this.props.items is null
If the second console.log is still null, Try to add log in your reducer, in the mapStateToProps, ... perhaps it's not state.items.items ...
In react, we have something called state. if the state of a component is changed the component will re-render. Having said that we can use this.setState() inside componentWillRecieveProps to update the state which in turn will rerender the component. So your code will look like this which is the standard way to handle Redux level state changes in react.
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
items: props.items
}
}
componentWillMount() {
console.log('------------ USER PROFILE -------------------');
}
componentWillRecieveProps({ items }) {
this.setState({ items });
}
componentDidMount() {
console.log('[ComponentDidMount]: Items: ', this.state.items);
this.props.fetchItems();
}
render() {
let profile = null;
console.log('[Render]: Items: ', this.state.items);
return <Auxillary>{profile}</Auxillary>;
}
}
const mapStateToProps = state => {
return {
items: state.items.items
};
};
const mapDispatchToProps = dispatch => {
return {
fetchItems: () => dispatch(actions.fetchItems())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
P.S Just making the API call inside componentWillMount will not help either as API call is async and can take up some time to resolve and till then react will finish rendering the component. so you'll still have to use componentWillRecieveProps
Standard practice is to call this.props.fetchItems() in your constructor or componentWillMount().
componentDidMount is called after render which is why your items do not render - they do not exist until after the initial render.
There are certain ways you can resolve this.
The very first time when render() gets called it was subscribed to the initial props/state that was initialise in redux store through redux connect method. In your case items was null.
Always initialise your redux store with some meaningful data.
In your case if items will be array you can initialise with empty array.
When you dispatch action your store will get updated and the component which was subscribed to items will be re rendered and in this way you donot have to use setState inside componentWillReceiveProps and you can avoid using it.
You need to handle certain cases in render like if array is empty and data is still loading then show some kind of loader and once data is fetched then display it.

React / Redux Components not re-rendering on state change

I think this question has been answer several time but I can't find my specific case.
https://codesandbox.io/s/jjy9l3003
So basically I have an App component that trigger an action that change a state call "isSmall" to true if the screen is resized and less than 500px (and false if it is higher)
class App extends React.Component {
...
resizeHandeler(e) {
const { window, dispatch } = this.props;
if (window.innerWidth < 500 && !this.state.isSmall) {
dispatch(isSmallAction(true));
this.setState({ isSmall: true });
} else if (window.innerWidth >= 500 && this.state.isSmall) {
dispatch(isSmallAction(false));
console.log(isSmallAction(false));
this.setState({ isSmall: false })
}
};
componentDidMount() {
const { window } = this.props;
window.addEventListener('resize', this.resizeHandeler.bind(this));
}
...
I have an other component called HeaderContainer who is a child of App and connected to the Store and the state "isSmall", I want this component to rerender when the "isSmall" change state... but it is not
class Header extends React.Component {
constructor(props) {
super(props);
this.isSmall = props.isSmall;
this.isHome = props.isHome;
}
...
render() {
return (
<div>
{
this.isSmall
?
(<div>Is small</div>)
:
(<div>is BIG</div>)
}
</div>
);
}
...
even if I can see through the console that redux is actually updating the store the Header component is not re-rendering.
Can someone point out what I am missing ?
Am I misunderstanding the "connect()" redux-react function ?
Looking at your code on the link you posted your component is connected to the redux store via connect
const mapStateToProps = (state, ownProps) => {
return {
isHome: ownProps.isHome,
isSmall: state.get('isSmall')
}
}
export const HeaderContainer = connect(mapStateToProps)(Header);
That means that the props you are accessing in your mapStateToProps function (isHome and isSmall) are taken from the redux store and passed as props into your components.
To have React re-render your component you have to use 'this.props' inside the render function (as render is called every time a prop change):
render() {
return (
<div>
{
this.props.isSmall
?
(<div>Is small</div>)
:
(<div>is BIG</div>)
}
</div>
);
}
You are doing it well in the constructor but the constructor is only called once before the component is mounted. You should have a look at react lifecycle methods: https://reactjs.org/docs/react-component.html#constructor
You could remove entirely the constructor in your Header.js file.
You should also avoid using public class properties (e.g. this.isSmall = props.isSmall; ) in react when possible and make use of the React local state when your component needs it: https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class
A component is only mounted once and then only being updated by getting passed new props. You constructor is therefore only being called once before mount. That means that the instance properties you set there will never change during the lifetime of your mounted component. You have to directly Access this.props in your render() function to make updating work. You can remove the constructor as he doesn't do anything useful in this case.

Resources