React - How can I force call a props function - reactjs

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

Related

How to use mapStateToProps to get a value from the state based on a context value?

I've an application when most of the data are stored in the store but the selected item is provided thought the usage of a React.Context.
React-Redux provide the connect api that accept a mapStateToProps function with state and props as a component.
What I would like, if it didn't break the hooks, is something like:
function mapStateToProps(state){
const selectedItemId = useContext(MySelectedItemContext)
return {
item: state.items[selectedItemId]
}
}
but of course it is not possible since I'm outside of the component and cannot invoke the useContext.
So, I tried to use the old API context:
function mapStateToProps(state, props){
return {
item: state.items[props.id]
}
}
export default connect(mapStateToProps)((props) =>
<MySelectedItemContext.Consumer>
{ selectedItemId => <Component id={selectedItemId} {...props}/> }
</MySelectedItemContext.Consumer>)
but this still not works because the connect returns a new component that has the consumer inside instead of outside and id prop is not defined yet in mapStateToProps.
Any idea?
The best way is to remove mapStateToProps and use useSelector hooks and Redux selectors. But if you need mapStateToProps, then you can wrap your component that must be connected to Redux into another component that will get value from context and will pass it to a component that uses Redux.
// Use this component
export function ContextConsumerComponent() {
const selectedItemId = useContext(SelectedItemIdContext);
return <ReduxConsumerComponent id={selectedItemId} />;
}
function mapStateToProps(state, props) {
return {
item: state.items[props.id]
}
}
const ReduxConsumerComponent = connect(mapStateToProps)((props) => {
// props.item will be here
});

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.

When to use componentWillReceiveProps lifecycle method?

I am new to React/Redux and have a problem with state.
TrajectContainer.jsx
class TrajectContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
trajects: props.trajects,
onClick: props.onClick
};
}
componentWillReceiveProps(nextProps) {
console.log('componentWillReceiveProps', nextProps);
this.setState(nextProps);
}
render() {
// When the componentWillReceiveProps is not present, the this.state will hold the old state
console.log('rerender', this.state);
return (<div className="col-md-6">
<h2>Trajects</h2>
<button className="btn btn-primary" onClick={this.state.onClick}>Add new Traject</button>
{this.state.trajects.map(traject => <Traject traject={traject} key={traject.name}/>)}
</div>)
}
}
const mapStateToProps = function (store) {
console.log('mapToStateProps', store);
return {
trajects: store.trajects
};
};
const mapDispatchToProps = function (dispatch, ownProps) {
return {
onClick: function () {
dispatch(addTraject());
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TrajectContainer);
When a reducer returns a new state, the component will rerender with the new data.
However: if I remove the componentWillReceiveProps function, the render() function has the old state.
I checked the data received in mapStateToProps, and this is new New State.
So I don't understand why I need the componentWillReceiveProps function in order for the render function to receive the new data.
Am I doing something wrong?
componentWillReceiveProps is required if you want to update the state values with new props values, this method will get called whenever any change happens to props values.
In your case why you need this componentWillReceiveProps method?
Because you are storing the props values in state variable, and using it like this:
this.state.KeyName
That's why you need componentWillReceiveProps lifecycle method to update the state value with new props value, only props values of component will get updated but automatically state will not get updated. If you do not update the state then this.state will always have the initial data.
componentWillReceiveProps will be not required if you do not store the props values in state and directly use:
this.props.keyName
Now react will always use updated props values inside render method, and if any change happen to props, it will re-render the component with new props.
As per DOC:
componentWillReceiveProps() is invoked before a mounted component
receives new props. If you need to update the state in response to
prop changes (for example, to reset it), you may compare this.props
and nextProps and perform state transitions using this.setState() in
this method.
React doesn't call componentWillReceiveProps with initial props during
mounting. It only calls this method if some of component's props may
update.
Suggestion:
Do not store the props values in state, directly use this.props and create the ui components.
Update
componentDidUpdate()
should now be used rather than componentWillReceiveProps
also see an article from gaearon re writing resilient components
There are two potential issues here
Don't reassign your props to state that is what you are using redux for pulling the values from the store and returning them as props to your component
Avoiding state means you no longer need your constructor or life-cycle methods. So your component can be written as a stateless functional component there are performance benefits to writing your component in this way.
You do not need to wrap your action in dispatch is you are passing mapDispatcahToProps. If an object is passed, each function inside it is assumed to be a action creator. An object with the same function names, but with every action creator wrapped into a dispatch will be returned
Below is a code snippet that removes the state from your component and relies on the state that has been returned from the redux store
import React from "react";
import { connect } from "react-redux";
const TrajectContainer = ({ trajects, addTraject }) => (
<div className="col-md-6">
<h2>Trajects</h2>
<button className="btn btn-primary" onClick={addTraject}>Add new Traject</button>
{trajects.map(traject => <Traject traject={traject} key={traject.name} />)}
</div>
);
const mapStateToProps = ({ trajects }) => ({ trajects });
export default connect( mapStateToProps, { addTraject })(TrajectContainer);
In your case you will require componentWillReceiveProps and you have to update the state when you receive new props. Because
In your constructor, you have declared your state as below. As you can see you construct your state using the props that are passed in. (This is why you require componentWillReceiveProps and the logic to update it there)
this.state = {
trajects: props.trajects,
onClick: props.onClick
};
So when your props, changes componentWillReceiveProps is the function that gets called. The constructor does not gets called. So you have to set the state again so that the changes goes into the state of the component.
However your logic should be as below. With a condition so that you can prevent repeated state updates if its called multiple times.
componentWillReceiveProps(nextProps) {
console.log('componentWillReceiveProps', nextProps);
if (this.props !== nextProps) {
this.setState(nextProps);
}
}
One should store the props into state, only if you are going to modify the content of it. But in your case i see that there is no modification. So you can directly use this.props.trajects directly instead of storing it into the state and then using it. This way you can get rid of the componentWillReceiveProps
So your render function will use something like below
{this.props.trajects.map(traject => //what ever is your code.... }
I had similar issue add withRouter() like this:
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(TrajectContainer));
The problem with your implementation is that you are duplicating your Redux store state (comming from the props) into your React state (this.state)
In your example, if store.trajects is updated, then this.props.traject will be updated and a render will be triggered only if this.props.traject is used in your render method (not the case).
Since you are using the state instead of the prop in your render method, you have to manually change the state of you component using this.setState to trigger a render.
This is not a good pattern: I would advise not to use the state, and use directly the props like this:
class TrajectContainer extends React.Component {
render() {
return (<div className="col-md-6">
<h2>Trajects</h2>
<button className="btn btn-primary" onClick={this.props.onClick}>Add new Traject</button>
{this.props.trajects.map(traject => <Traject traject={traject} key={traject.name}/>)}
</div>)
}
}
const mapStateToProps = function (store) {
console.log('mapToStateProps', store);
return {
trajects: store.trajects
};
};
const mapDispatchToProps = function (dispatch, ownProps) {
return {
onClick: function () {
dispatch(addTraject());
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TrajectContainer)

How to get React component to detect when state object has changed and re-render?

I have a React component: stepA, which obtains a value on submit which is then stored in my redux store as state.chef_positions.[0].chef_title_id.
After a user submits StepA, they are instantly directed to StepB. StepB is mounting before stepA's reducer has finished, meaning StepB is loading a incorrect value from the store.
What is the right way to get stepB to re-render when stepA user action has been saved to the server/store, to re-render it's dispatch has finished?
class stepA extends React.Component {
handleSubmit(data) {
this.props.actions.createChefPosition(data);
this.props.nextStep();
}
...
class stepB extends React.Component {
componentDidMount() {
const title_id = this.props.chef_positions[0].chef_title_id;
this.props.dispatch(loadChefTitleSkills(chef_title_id));
}
stepB.propTypes = {
chef_positions: PropTypes.array.isRequired,
};
function mapStateToProps(state, ownProps) {
return {
chef_positions: state.chef_positions,
};
}
export default connect(mapStateToProps)(stepB);
actually, nothing.
Because of the way Redux is connected to React app is through "first-class object" level (you connect() mapStateToProps to your component), the props in the components are modified to current store state when changes happen at the store.
the only thing you need to do is to re-assign "title_id" with { componentDidUpdate } lifecycle method/use directly "this.props ..." wherever you use title_id/use "useSelector" redux hook.
one other thing, you might cinsider add { mapDispatchToProps } to the connect

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

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

Resources