i have set of components those use central store(i.e. redux store) via container component. these components are tend to be flat simple and reside within HOC component. HOC component utilizes react-redux connect.
class HOComponent extends Component {
const { data1, data2 } = this.props;
render() {
<Component1 data={data1} />
<Component2 data={data2} />
}
}
const selector = (state) => ({
data1 = selectors.data1(state),
data2 = selectors.data2(state),
other = selectors.other(state)
});
above is the selector for child component Component1 Component2 respectively.
Below how is the selectors look like which utilizes reselect.
const alldata = (state) =>
state.alldata;
export other = (state) =>
state.other;
export data1 = createSelector(
[alldata],
(alldata) => {
//lets assume
return alldata.filter(d => d.criteria === data1.criteria);
})
export data2 = createSelector(
[alldata],
(alldata) => {
//lets assume
return alldata.filter(d => d.criteria === data2.criteria);
})
the question is this right usage of the reselect i noticed this is ineffective, since if my HOC other selctor triggered Component1 Component2 also being rerendered anyway. should i be checking each data flow within shouldComponentUpdate method. i thought reselect usage will battle with this issue in first place. did i misunderstand it. is it only prevents recalculation part?
this is continuation of my earlier post
prevent selectors of child components being triggerred in redux
Related
I am trying to create an architecture that in some way imitates the slots from VUE.
The idea is for the parent component to be able to inject some props into the component and the child can inject the rest of the props.
This is how I tried to approach this problem, unfortunately this approach will not work because the compontent will be "monut" every time the parent re-render takes place.
Filters = (prams) => {
useEffect(()=>{ //RENDER ALL THE TIME },[])
...
}
ParentComponent = () => <ChildComponent Filters={(props) => <Filters propA={"A"} />}
ChildComponent = (props) => {
const Filters = props.Filters;
render(<Filters probB="B" />)
}
I know, I can use useCallback for ((props) => <Filters propA={"A"} />), but only it will help only if what I want to pass to "propA" is steady.
I want to "manage" <Filters /> component in parent, so that the child does not have to handle Filters logic (props).
React gives you proper API to do most things. Using it forces you into certain paradigms that are proven to work well.
You should probably have a look at the Context and Memo APIs from React.
Or if you have to select and update state from multiple components, you might wanna have a look at libraries that provide global state, like Redux and Recoil.
Context example
// The shape
interface ContextProps {
myProp: string
}
// The context
export const MyContext = React.createContext<Partial<ContextProps>>({
myProp: 'nothing'
});
// The provider
<MyContext.Provider value={{ myProp: 'override' }}>
{children}
</MyContext.Provider>
// Consumer
const { myProp } = useContext(MyContext)
In some case you can also use useMemo or React.memo and use your own custom compare function if needed to prevent re-renders in very specific situations.
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.
I am trying to solve a state management issue. I have 2 react native components. The react native component goes via native code/module to open the other component instead of going the react native route.
Code that starts another react component from the first component
reactView.startReactApplication(
reactInstance,
"Second Component",
null
);
If I look at this it's recreating the underlying react context object again. The First component is properly loaded which obviously has the store configured on it i.e. in my AComponent.js
import data from '../../initialState'
const initialState = data
const store = storeFactory(initialState);
const mapStateToProps = (state) => {
return {
intValue : state.intValue,
}
}
const Container = connect(mapStateToProps)(AView)
const AComponent = () => (
<Provider store={store}>
<Container />
</Provider>
)
AppRegistry.registerComponent('AComponent', () => AComponent);
but when I try to access the store in the B component, it does not work and comes back with the error
Could not find "store" in either the context or props of
"Connect(BComponent)"
The B component looks something like this
const mapStateToProps = (state) => {
return {
intValue : state.intValue
}
}
const BContainer = connect(mapStateToProps)(BView)
AppRegistry.registerComponent('BContainer', () => BContainer);
My initialState.json file
{
"intValue": 10
}
I have some data loaded in the store after initial Axios call.
Then I render two components match (parent component) and player (child component).
This is the way to show the two components in a related way (this is a simplified example from my original code, in this example I could solve my problem in another way, but in my complex real code it is essential to do an operations in children component first):
match.js
class Match extends Component {
constructor(props) {
super(props);
}
render() {
return (
Object.values(this.props.matchs).map(( match_id ) => {
let match = this.props.matchs[match_id];
return (
<div key={match_id}>
<p>{match.tournament}</p>
<p>{match.color}</p> {/* this color depends of children condition*/ }
<div className="players">
{match.array_players.map ( ( player_id ) => {
let player = this.props.players[player_id];
return (
<Player key={odd_id} ownPlayer={player} />
)
})
</div>
</div>
)
});
)
}
}
const mapStateToProps = state => {
return {
matchs: state.matchs.matchs,
players: state.players.players
};
}
const mapDispatchToProps = dispatch => {
return {
// actions
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Matchs);
player.js
class Player extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>{this.props.ownPlayer.name}</p>
<p>{this.props.player_color}</p>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
// I need to make some previous operations before render
let player_color;
if (ownProps.ownPlayer.name == "paul")
player_color = 'yellow';
else
player_color = 'blue';
// Then I Need to update parent component with children color condition
// if (player_color == 'yellow')
// match_color = 'yellow'
//
// Call some action here to update parent component???
// things like these do not work:
// let id_p = ownProps.player.id_player;
// state.players.players[id_p].color = 'blue'; This does not work
return {
player_color
};
};
const mapDispatchToProps = dispatch => {
return {
//
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Player);
Then I need to update a prop in a parent component after some conditions in children component.
I've read this article:
https://redux.js.org/docs/recipes/ComputingDerivedData.html
But I don't know how to send data to store and refresh parent component before render.
I thought about calling like an action in componentWillMount or componentWillUpdate to send data to store, but I don't know if it's correct way.
There is nothing wrong with calling an action inside the lifecycle, it is not recommended to do it inside the render method because it my trigger infinite actions, but in your situation if you indeed have to do this calculation inside the child component I believe you should dispatch this action inside componentWillReceiveProps or ComponentDidMount, in some situations you actually have to do it in both places.
go for it!
The docs are pretty clear:
You can either do one-time ops in constructor / ComponentWillMount / ComponentDidMount or repetitive ops in recurring life-cycle methods like ComponentWillReceiveProps.
If you need a way for the child component to update the store, than you have to dispatch an action that will go and do so, and put it in ComponentWillMount or ComponentWillReceiveProps depending on the need, sometimes you need to put it in both.
But, on a side note, like Bruno Braga said, it does seem like the wrong place to put logic in.
I would suggest to put this logic in the reducer, as Component really shouldn't handle store logic, just notify (dispatch) state changes.
Also, I don't think that you need to connect the Player component to the redux store, since it seems like each player has it's own independent instance.
What I would suggest is passing the Player Component a function from the Match Component, something like
<Player ... onGoalScored={()=> this.handleGoalScored()} />
and on the Match component do:
handleGoalScore() {
this.props.dispatch(updateGoalsAction())
}
and have the logic in the reducer, the let's say will figure out what the color of Match should be, and, on the next state update to Match, because of the binding to store.matchs.color will be rendered as Red
I have a component which isn't re-rendering as I'd expect. I'm less concerned about this specific example than having a better understanding about how state, props, updates, re-rendering and more happens in the react-redux lifecycle.
My current code is about creating a delivery with a list of locations. The main issue is that reordering the location's itinerary doesn't seem to work - the state updates in the reducer correctly, but the components are not rerendering.
This is the relevant snippet from delivery.js, a component which uses the LocationSearch custom component to display each location in the list of locations:
{console.log("Rendering...")}
{console.log(delivery.locations)}
{delivery.locations.map((location, index) => (
<div key={index}>
<LocationSearch
{...location}
total={delivery.locations.length+1}
index={index}
/>
</div>
))}
The console.logs print out the correct data where and when expected. When an action to reorder the locations is triggered (from within LocationSearch), the console log prints out the list of locations with the data updated correctly. However the component is not displaying anything updated.
Here is some relevant parts of the LocationSearch component:
export class LocationSearch extends Component {
constructor(props) {
super(props)
this.state = {
searchText: this.props.address
}
this.handleUpdateInput = this.handleUpdateInput.bind(this)
}
handleUpdateInput (searchText) {
this.setState({
searchText: searchText
})
this.props.handleUpdateInput(searchText)
}
render(){
const { type, itineraryOrder, floors, elevator, accessDistance, index} = this.props
return (
...display all the stuff
)
}
}
...map dispatch, connect, etc...
function mapStateToProps(state, ownProps) {
return {
suggestions: state.delivery.suggestions,
data: ownProps.data
};
}
This is where I get confused - I figure I'm meant to do something along the lines of componentWillUpdate, but can't find any good explanations for what happens at each step. Do I just set this.props = nextProps in there? Shouldn't this.props have updated already from the parent component passing them in? How come most components seem to rerender by themselves?
Any help you can give or links to good resources I'd be grateful for. Thanks in advance!
I kept running into this kind of issues right before I discovered redux. Considering you mentioned already using react-redux, maybe what you should do is switch to a container/component structure and forget about componentWillUpdate()
Basically, what this allows for is just passing fresh props to the components that renders the actual HTML, so you don't have to replace the props by hand.
Your container could be something like this
import React from 'react'
import { connect } from 'react-redux'
import PresentationalComponent from 'components/PresentationalComponent'
const Container = props => <PresentationalComponent {...props} />
export default connect( state => ({
searchText: state.UI.searchText,
locations: [...],
}), dispatch => ({
handleUpdateInput: e => dispatch( {
type: "CHANGE_SEARCH_TEXT",
text: e.target.value,
} ),
}))(Container)
And your presentational component
import React from 'react'
const PresentationalComponent = ({ searchText, locations, handleUpdateInput }) =>
<div>
{locations.map(location => <p>{location}</p>)}
<input defaultValue={searchText} type="text" onChange={handleUpdateInput} />
</div>
export default PresentationalComponent