How to reduce the repeat object.chain in react-native? - javascript

I have react-native code with mobx like below, as you guys see, I need reference this.props.store.user.avatar to get deep object value from props, I don't wanna use the long syntax repeatedly, I know I can let it be a instance variable in constructor for example2, but I find that's a anti-pattern by the posts, it actually occurs some side-effect by my experiment cause the constructor execute only once when components initial, so I use the third way for example3, as you like, I create function in components and return the value by the long syntax, that's what can I do in my best, but I don't like this way, it looks not elegant, so anyone has better suggest or solution/way?
Example1 : My question
#observer
export default class Profile extends Component {
constructor(props) {
super(props);
}
render() {
return(
<BasicInfo
avatar = { this.props.store.user.avatar }
displayName = { this.props.store.user.displayName }
location = { this.props.store.user.location }
/>
)
}
}
Example2 : Anti-Pattern
#observer
export default class Profile extends Component {
constructor(props) {
super(props);
this.avatar = this.props.store.user.avatar
this.displayName = this.props.store.user.displayName
this.location = this.props.store.user.location
}
render() {
return(
<BasicInfo
avatar = { this.avatar }
displayName = { this.displayName }
location = { this.location }
/>
)
}
}
Example3 : Anti-Pattern
#observer
export default class Profile extends Component {
constructor(props) {
super(props);
this.state = {
avatar: this.props.store.user.avatar,
displayName: his.props.store.user.displayName,
location: this.props.store.user.location,
}
}
render() {
return(
<BasicInfo
avatar = { this.state.avatar }
displayName = { this.state.displayName }
location = { this.state.location }
/>
)
}
}
Example 4 : It work, but exist better way?
#observer
export default class Profile extends Component {
constructor(props) {
super(props);
}
avatar(){ return this.props.store.user.avatar}
displayName(){ return this.props.store.user.displayName}
location(){ return this.props.store.user.location}
render() {
return(
<BasicInfo
avatar = { this.avatar() }
displayName = { this.displayName() }
location = { this.location() }
/>
)
}
}
Example 5 : This is a good way, but it not work on callback
#observer
export default class Profile extends Component {
constructor(props) {
super(props);
}
callback = () => {
Actions.aboutMeEdit({ avatar: user.avatar })
// there are not work
}
render() {
const { user } = this.props.store;
return(
<BasicInfo
avatar = { user.avatar }
displayName = { user.displayName }
location = { user.location }
callback = { this.callback }
/>
)
}
}

You could do it like this to reduce the repetition:
render() {
const { user } = this.props.store;
return(
<ScrollView>
<BasicInfo
avatar = { user.avatar }
displayName = { user.displayName }
location = { user.location }
/>
)
}

Use spread:
render() {
const { user } = this.props.store;
return (
<ScrollView>
<BasicInfo {...user} callback={this.callback.bind(this)} />
</ScrollView>
)
}

Related

How to test logic in ComponenWillMount using Enzyme/Jest

I am beginner in react unit testing with enzyme/jest,
I want to test my logic inside componentWillMount method.
I want to test based on my context object whether redirect happens or not based on my business logic
class ActivateSF extends Component {
constructor(props) {
super(props);
this.className = 'ActivateSF.js'
this.state = {
messages: null,
}
}
render() {
return (
<SDPActivateInterstitialUI
context={this.props.context}
messages={this.state.messages}
/>
);
}
componentWillMount() {
let context = this.props.context
if(!context.userInfo){
return this.callIdentify(context)
}
let externalLP = ExternalLandingPageUtil.getExternalLandingPageUrl(context);
if (externalLP) {
window.location.replace(`${externalLP}`);
return;
}
if (context.userInfo)
{
console.log("user identified prior to activation flow")
if (UserInfoUtil.isSubsribedUser(context))
{
window.location = '/ac'
}
else
{
this.callPaymentProcess(context)
}
}
}
You can try beforeEach to mount and in your test you call .unmount and perform your test on it.
beforeEach(() => {
const myComponent= mount(<MyComponent myprop1={...} />);
});
describe('<MyComponent/>', () => {
it('actually unmounts', () => {
...
...
myComponent.unmount();
... Do unmount tests here
});
});
Example straight from the enzyme docs: https://airbnb.io/enzyme/docs/api/ShallowWrapper/unmount.html
import PropTypes from 'prop-types';
import sinon from 'sinon';
const spy = sinon.spy();
class Foo extends React.Component {
constructor(props) {
super(props);
this.componentWillUnmount = spy;
}
render() {
const { id } = this.props;
return (
<div className={id}>
{id}
</div>
);
}
}
Foo.propTypes = {
id: PropTypes.string.isRequired,
};
const wrapper = shallow(<Foo id="foo" />);
expect(spy).to.have.property('callCount', 0);
wrapper.unmount();
expect(spy).to.have.property('callCount', 1);

Re-render component with componentWillReceiveProps?

I have a .jsx with a parent class and a child, in the parent i initialize the api and stock the json content in a state:
constructor(props) {
super(props);
this.state = {
all: '',
};
}
componentDidMount() {
this.loadApi();
}
loadApi(){
this.setState({ all: myApiGet('https://********') });
}
After that i need to get the "url" of the differents pics for show them on the site. But there is the problem, I get the api json when i load the page and i don't success to re-load the function.
componentWillReceiveProps(nextProps) {
this.apiGetProductPicture(nextProps.categorie);
}
apiGetProductPicture = (i) => () => {
// TODO do something with the data
var stock = this.props.all
.then(stock => this.setState({ pictures: stock.content.categories[i].background }))
.catch(error => console.log('home2', error));
}
I try a lot of possibility and check the net but the solution doesn't work for me (or i just doesn't understand them ...)
Thanks for your time :/
Full component:
class ProductItem extends React.Component {
constructor(props) {
super(props);
this.state = {
pictures: '',
name: '',
price: '',
json: '',
};
//this.apiGetProductPicture = this.apiGetProductPicture.bind(this);
}
componentWillReceiveProps(nextProps) {
this.apiGetProductPicture(nextProps.categorie);
}
apiGetProductPicture = (i) => () => {
// TODO do something with the data
var stock = this.props.all
.then(stock => this.setState({ pictures: stock.content.categories[i].background }))
.catch(error => console.log('home2', error));
}
render() {
return (
......
)
}
}
Error message:
The above error occurred in the component:
in ProductItem (created by Home2)
in div (created by Home2)
in div (created by Home2)
in div (created by Home2)
in div (created by Home2)
in main (created by Home2)
in Home2
Consider adding an error boundary to your tree to customize error handling behavior.
You can learn more about error boundaries at https:// fb.me/react-error-boundaries.
react-dom.development.js:9312:5
ReferenceError: props is not defined
Ok i think i see some changes to be made
in your parent component your setting this.state.all to be a promise (the promise returned from your api call)
let's change that to be the actual json from your api call
Parent component:
constructor(props) {
super(props);
this.state = {
all: '',
};
this.loadApi = this.loadApi.bind(this);
}
componentDidMount() {
this.loadApi();
}
loadApi() {
myApiGet('https://********')
.then(all => this.setState({ all }));
}
Child Component:
class ProductItem extends React.Component {
constructor(props) {
super(props);
this.state = {
pictures: '',
name: '',
price: '',
json: '',
};
this.apiGetProductPicture = this.apiGetProductPicture.bind(this);
}
ComponetDidMount() {
apiGetProductPicture(this.props.categorie);
}
componentWillReceiveProps(nextProps) {
if (nextProps.categorie !== this.props.categorie)
{
this.apiGetProductPicture(nextProps.categorie);
}
}
apiGetProductPicture(categorie) {
// TODO do something with the data
if (!this.props.all) return;
var categories = (((this.props.all || {}).stock || {}).content || {}).categories;
if (categories.indexOf(categorie) > -1)
{
this.setState({ pictures: categories[categorie].background }));
}
}
render() {
return (
......
);
}
}
Thanks for your time :/
no worries :)
i se you posted "Lost in the javascriptception"
this and other questions have provided me with enough info to solve your problem, sorry the stackoverflow community was so mean to you, but not all of us are like that.
I would recommend in the future you post more info on your questions, like full code (except sensible stuff), not just parts, the codesanbox was the thing that let me test code and see where the problem was.
Also i f*** up on some of the previous answer, but to be fair i had very limited info to go along with, and most people answering won't test the code for tipos or stuff
version One
import React from "react";
import { render } from "react-dom";
import Hello from "./Hello";
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
class ProductItem extends React.Component {
constructor(props) {
super(props);
this.state = {
pictures: '',
name: '',
price: '',
json: '',
};
this.apiGetProductPicture = this.apiGetProductPicture.bind(this);
}
componentDidMount() {
this.apiGetProductPicture(this.props.categorie);
}
componentWillReceiveProps(nextProps) {
this.apiGetProductPicture(nextProps.categorie);
}
apiGetProductPicture(categorie) {
// TODO do something with the data
var categories = this.props.send;
categorie = parseInt(categorie, 10);
if (categorie < categories.length) {
console.log(categories[categorie].background);
this.setState({ pictures: categories[categorie].background });
}
}
render() {
return (
<div>
<p>{this.props.name}</p>
<img src={this.state.pictures} />
</div>
);
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
all: "",
categories: []
};
this.loadAPI = this.loadAPI.bind(this);
}
componentDidMount() {
this.loadAPI();
}
loadAPI() {
var test = fetch("https:*******")
.then(test => test.json())
.then(testJson => {
// alert(testJson.content.categories[0].description)
var obs = testJson.content.categories.slice();
// alert(testJson);
this.setState({ categories: obs });
});
}
render() {
return (
<div style={styles}>
<Hello name="CodeSandbox" />
<h1>Products</h1>
{this.state.categories.map( (value, i) => {
return <ProductItem
key={value.uid}
send={this.state.categories}
name={value.description}
categorie={i} />
})}
<h2>Start editing to see some magic happen {"\u2728"}</h2>
</div>
);
}
}
render(<App />, document.getElementById("root"));
My recommended Version
import React from "react";
import { render } from "react-dom";
import Hello from "./Hello";
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
class ProductItem extends React.Component {
render() {
return (
<div>
<p>{this.props.name}</p>
<img src={this.props.picture} />
</div>
);
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
all: "",
categories: []
};
this.loadAPI = this.loadAPI.bind(this);
}
componentDidMount() {
this.loadAPI();
}
loadAPI() {
var test = fetch("https:*****")
.then(test => test.json())
.then(testJson => {
// alert(testJson.content.categories[0].description)
var obs = testJson.content.categories.slice();
// alert(testJson);
this.setState({ categories: obs });
});
}
render() {
return (
<div style={styles}>
<Hello name="CodeSandbox" />
<h1>Products</h1>
{this.state.categories.map( (value, i) => {
return <ProductItem
key={value.uid}
picture={value.background}
name={value.description}
categorie={i} />
})}
<h2>Start editing to see some magic happen {"\u2728"}</h2>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Hope this helps you out, don't be so hard on yourself, you know practice makes perfect, also would recommend you follow the react tutorial, to see what react is about, i can seam super hard and weird because it maybe a completely different programming model (it was for me), but when it click it's really cool

Getting the ref from a dynamic component when using Redux, React and react-router-dom 4.x

I have the following class
class MatchBox extends React.Component {
constructor(props) {
super(props);
this.countdownHandler = null;
this.showBlocker = true;
this.start = this.start.bind(this);
}
start() {
...
}
render() {
...
return (
<div style={ styles.mainContainer } className="fluid-container">
...
</div>
);
}
};
function mapStateToProps(state) {
...
}
function matchDispatchToProps(dispatch) {
...
}
export default withRouter(connect(mapStateToProps, matchDispatchToProps, null, { withRef: true })(MatchBox));
which is used in this class
class GameBox extends React.Component {
constructor(props) {
super(props);
...
}
render() {
var mainElement = null;
switch(this.props.mainElement.element) {
case 'SEARCHING': mainElement = <SearchingBox gameType={ this.props.gameType }/>; break;
case 'MATCH': mainElement = <MatchBox ref='matchBox'/>; break;
default: mainElement = <SearchingBox/>;
}
return (
<div style={ styles.mainContainer } className="fluid-container">
{ mainElement }
</div>
);
}
};
function mapStateToProps(state) {
...
}
function matchDispatchToProps(dispatch) {
...
}
export default withRouter(connect(mapStateToProps, matchDispatchToProps, null, { withRef: true })(GameBox));
And I can't get the ref of the object MatchBox. I tried with this.refs.matchBox and is null, also tried getting directly from ref(ref={(r) => { // r is null } }) and I don't know what to try anymore.
I'm using react-router-dom 4 and I don't know if function withRouter affect the outcome component.
It's not pretty, but I think this is the solution. withRouter exposes the child ref via a wrappedComponentRef callback, which gets us to the connect hoc. That exposes its child ref via getWrappedInstance if you pass the withRef attribute as you did. So you just have to combine both of those.
class GameBox extends React.Component {
matchboxRefCallback = (connectHOC) => {
this.matchboxRef = connectHOC ? connectHOC.getWrappedInstance() : null;
}
render() {
return <MatchBox wrappedComponentRef={this.matchboxRefCallback}/>;
}
}
Much more cleaner solution would be to create a HOC. which will forward the ref to actual component
const matchBoxHOC = (WrappedComponent) => {
class MatchBoxHOC extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return <WrappedComponent {...rest} ref={forwardRef} />;
}
}
const WithRouterMatchBoxHOC = withRouter(MatchBoxHOC, { withRef: true });
return React.forwardRef((props, ref) => {
return <WithRouterMatchBoxHOC {...props} forwardRef={ref} />;
});
}
Call is like
export default matchBoxHOC(connect(mapStateToProps, matchDispatchToProps, null, { withRef: true })(MatchBox));

Data in Relay not fetched when using it with react-navigation in React Native

I'm trying to setup my app with Relay and react-navigation, following the hints that were discussed in this GitHub issue. Also note that I used create-react-native-app to create the project.
This is what my setup looks like:
App.js
Relay.injectNetworkLayer(
new Relay.DefaultNetworkLayer('https://api.graph.cool/relay/v1/ciyeih9590fhl0162e5zh1z4h', {
headers: {
},
})
)
const RootNavigationStack = StackNavigator({
PokemonList: {
screen: PokemonList
}
})
export default class App extends React.Component {
render() {
return <RootNavigationStack />
}
}
PokemonList.js
class IndexRoute extends Route {
static queries = {
viewer: () => Relay.QL`query { viewer }`
}
static routeName = 'IndexRoute'
}
export default class PokemonList extends React.Component {
render() {
return (
<Relay.RootContainer
Component={PokemonListRelayContainer}
route={new IndexRoute()}
renderFetched={(data) => {
console.log('PokemonList - renderFetched', data)
return <Text>Test</Text>
}}
/>
)
}
}
const PokemonListRelayContainer = Relay.createContainer(PokemonList, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
id
allPokemons(first: 10) {
edges {
node {
id
name
}
}
}
}
`
}
})
The logging statement in renderFetched is being executed, but for some reason the datais empty:
PokemonList - renderFetched {"viewer":{"__dataID__":"viewer-fixed","__fragments__":{"0::client":[{}]}}}
Any idea what I'm missing in this setup?
I solved the issue myself, so apparently the PokemonList, being my root component, didn't get access to the data that was requested - though I assume it actually was loaded, but due to Relay's data masking wasn't visible to the component.
The solution I came up with to wrap PokemonList in another component that would render the Relay.RootContainer and then go from there with my conventional Relay setup.
This is what it now looks likes:
PokemonListWrapper.js
class PokemonListWrapper extends React.Component {
render() {
return (
<Relay.RootContainer
Component={PokemonList}
route={new IndexRoute()}
renderFetched={data => <PokemonList {...this.props} {...data}/>}
/>
)
}
}
PokemonList.js
class PokemonList extends React.Component {
render() {
return (
<ScrollView>
{this.props.viewer.allPokemons.edges.map(pokemonEdge => (
<Text key={pokemonEdge.node.id}>{pokemonEdge.node.name}</Text>
))}
</ScrollView>
)
}
}
export default Relay.createContainer(PokemonList, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
id
allPokemons(first: 10) {
edges {
node {
id
name
}
}
}
}
`
}
})
App.js
Relay.injectNetworkLayer(
new Relay.DefaultNetworkLayer('https://api.graph.cool/relay/v1/ciyeih9590fhl0162e5zh1z4h')
)
export class IndexRoute extends Route {
static queries = {
viewer: () => Relay.QL`query { viewer }`
}
static routeName = 'IndexRoute'
}
const RootNavigationStack = StackNavigator({
PokemonList: {
screen: PokemonListWrapper
}
})
export default class App extends React.Component {
render() {
return (
<RootNavigationStack />
)
}
}
😎

How to force rendering if the global variable value changes?

#File 1:
let ticketEnable = false;
export default class SupportTicketMain extends Component {
constructor () {
super();
}
render () {
let expandIcon = <DownIcon/>;
if (this.state.ticketDetailExpanded) {
expandIcon = <UpIcon/>;
}
return (
<Section className="ticketMain" primary={true}>
<TicketHeader expanded={ticketEnable}/>
</Section>
);
}
};
export function setTicketEnablement (value) {
ticketEnable = value;
}
#file 2:
import { setTicketEnablement } from file1;
export default class SupportTicketTabs extends Component {
constructor () {
super();
this.state = {
ticketDetailExpanded: false
};
this._expandClick = this._expandClick.bind(this);
}
_expandClick() {
this.setState({ticketDetailExpanded: !this.state.ticketDetailExpanded});
setTicketEnablement(this.state.ticketDetailExpanded);
}
render () {
let expandIcon = <DownIcon/>;
if (this.state.ticketDetailExpanded) {
expandIcon = <UpIcon/>;
}
return (
<Button className="expander" type="icon" onClick={this._expandClick}>
{expandIcon}
</Button>
);
}
};
Here a button click in supportTicketTabs class of #file2 will update global variable in #File1 , but SupportTicketMain render doesn't update if the global variable value changes! please guide me on this.
ticketEnable should be a prop passed into SupportTicketMain. The component that wraps both SupportTicketTabs and SupportTicketMain should be handing down a callback as a prop that modifies the value of ticketEnable (toggleTicketEnable) and the value of ticketEnable
class Main extends Component {
constructor (props) {
super(props);
this.onToggleTicketEnable = this.onToggleTicketEnable.bind(this);
this.state = {
ticketEnabled: false;
};
}
onToggleTicketEnable() {
this.setState({ ticketEnabled: !this.state.ticketEnabled });
}
render () {
return (
<App centered={false}>
<SupportTicketMain ticketEnable={this.ticketEnabled} />
<SupportTicketTabs onToggleTicketEnable={this.onToggleTicketEnable}/>
</App>
);
}
}

Categories