Normally with navigation you receive the props on child component like this, with the curly braces:
const MatchHistoryScreen = ({ navigation, route }) => {
But I believe props you do without the curly braces, like this:
const MatchToggleComponent = (props) => {
So how do you combine both 'navigation' and 'props' for a child component? Putting 'props' in curly braces did not work for me. I think a potential use case for this is if you are passing down props from App.js together with your navigation prop.
So the props object is what a component receives from React depending on how you have configured your app. If you have Redux, then you are going to receive some props from there. If the component is a child, and you are passing some properties from the parent, you are going to receive props from the father too. If the parent is passing navigation, then you will have it as props in your child.
props is just an object that a component receives from multiple sources. It can actually be called anything, banana or sporp, it's just a convention to call that object props.
Destructuring on the other hand is used to "unpack" properties of an object (or values from an array) into separate variables. So when you are unpacking the navigation properties, you are essentially going from
const MatchToggleComponent = (props) => {
// props object contains navigation and route properties, which can be accessed as props.navigation and props.route
into
const MatchToggleComponent = ({ navigation, route }) => {
// you unpack navigation and route properties into separate variables `navigation` and `route`, ready to be used in your component.
Imagine that the parent of MatchToggleComponent looks like
return (
<>
<MatchToggleComponent
foo={bar}
one={two}
/>
</>
)
So your props object is going to have the foo property, one property, and then navigation property and route property. Destructuring effectively let's you pick the properties that interest you and "unpack" them, ready to be used inside your component without referencing the props object all the time.
So if you wanted to use foo prop, AND your navigation props, you would do
const MatchToggleComponent = ({ foo, navigation, route }) => {
You could do that in any component, just try it out.
If you have any additional questions please don't hesitate to continue the discussion :)
Edit 1:
req.query seems to be an object with properties userId and activeMethods. When you destructure the object, you should be using curly braces. Consequently, when you destructure arrays, you should be using square brackets [].
The variable names that you destructure should be called the same as in the objects. For example:
const foo = {
some: ...,
other: ...,
last: ...,
};
If you do const { bar } = foo, then JS is not going to know that you want to get the first prop of the object and this will throw an error. So the variable you create should match the prop name in the object, like
const { last, other } = foo;
As you can see, the order doesn't really matter (the situation is a bit different with arrays, but I won't be covering this here).
What you can do is that you can rename the variable WHILE destructuring properties from the object. Imagine you want to get the last prop from the foo object, but you want it named first for some reason. Then you would do
const { last: first } = foo;
This will effectively unpack the last prop from the foo object for you and rename it into first, so you'll be able to use const first across your code.
You can do it with using useNavigation hook which gives access to navigation object.
example:
import { useNavigation } from '#react-navigation/native';
function MyBackButton() {
const navigation = useNavigation();
return (
<Button
title="Back"
onPress={() => {
navigation.goBack();
}}
/>
);
}
Related
I'm using MST with Reactive Native and a FlatList and displaying the list all OK.
However, one property of my state tree model is a derived view, and as far as I can see, this is not passed into the FlastList renderItem
My Model:
export const BookModel = types
.model("Book")
.props({
bookId: types.number,
price: types.number,
})
.views((store) => ({
get priceFormatted() {
return store.price.toFixed(2)
},
}))
export const RootModel = types
.model("BookStore")
.props({
books: types.array(BookModel ),
})
My Flat List
<FlatList<Book>
data={rootModel.books}
extraData={rootModel.books.length}
renderItem={({ item }) => <BookCard key={item.bookId} book={item} />}
In my BookCard component, the price property is available, but the priceFormatted is undefined.
I can make the priceFormatted a property returned from the server and so avoid the problem, but I'd like to know if this is a bug or expected behaviour for MST?
I think that a snapshot of the state-tree is passed to the renderItem, ie a plan JS object, and so the dervied state is not passed with it. The docs say
MobX Computed properties will not be stored in snapshots or emit patch
events.
https://mobx-state-tree.js.org/concepts/trees
I am using React Native with functional components. componentDidMount() etc. are not available in functional components, instead I use Hooks. But Hooks don't act like lifecycle methods. I am wondering what the best practices are.
Assumed that we have a function like this one:
const ABCScreen = () => {
const [someHook, setSomeHook] = useState<any>()
useEffect(() => {
// some code inside this function which is called on every component update
}, [])
server.asyncCall().then(data => {
setSomeHook(data)
})
return (<View>
{someHook ? (<Text> `someHook` was assigned </Text>) : (<Text> `someHook` was not assigned, display some ActivityIndicator instead</Text>)}
</View>)
}
Where to place server.asyncCall()? Inside or outside of useEffect?
I think you have a misunderstanding here. The convention is that all the fetching data is going to be placed inside the componentDidMount lifecycle method. React useEffect hook can replace this easily by placing an empty array of dependencies, which means you can place that call inside the useEffect you already have.
Unlike you mention in your code comment, this hook won't be triggered on each component update. It will be only be triggered once the component is being mounted. So, you should be able to do it as follows:
const ABCScreen = () => {
const [someHook, setSomeHook] = useState<any>()
useEffect(() => {
server.asyncCall().then(setSomeHook)
}, [])//only triggered when component is mounted.
In the future, you might want to check the rules of the hooks.
I created the child using:
const ComponentClass = Vue.extend(someComponent);
const instance = new ComponentClass({
propsData: { prop: this.value }
})
instance.$mount();
this.$refs.container.appendChild(instance.$el);
When this.value is updated in the parent, its value doesn't change in the child. I've tried to watch it but it didn't work.
Update:
There's an easier way to achieve this:
create a <div>
append it to your $refs.container
create a new Vue instance and .$mount() it in the div
set the div instance's data to whatever you want to bind dynamically, getting values from the parent
provide the props to the mounted component from the div's data, through render function
methods: {
addComponent() {
const div = document.createElement("div");
this.$refs.container.appendChild(div);
new Vue({
components: { Test },
data: this.$data,
render: h => h("test", {
props: {
message: this.msg
}
})
}).$mount(div);
}
}
Important note: this in this.$data refers the parent (the component which has the addComponent method), while this inside render refers new Vue()'s instance. So, the chain of reactivity is: parent.$data > new Vue().$data > new Vue().render => Test.props. I had numerous attempts at bypassing the new Vue() step and passing a Test component directly, but haven't found a way yet. I'm pretty sure it's possible, though, although the solution above achieves it in practice, because the <div> in which new Vue() renders gets replaced by its template, which is the Test component. So, in practice, Test is a direct ancestor of $refs.container. But in reality, it passes through an extra instance of Vue, used for binding.
Obviously, if you don't want to add a new child component to the container each time the method is called, you can ditch the div placeholder and simply .$mount(this.$refs.container), but by doing so you will replace the existing child each subsequent time you call the method.
See it working here: https://codesandbox.io/s/nifty-dhawan-9ed2l?file=/src/components/HelloWorld.vue
However, unlike the method below, you can't override data props of the child with values from parent dynamically. But, if you think about it, that's the way data should work, so just use props for whatever you want bound.
Initial answer:
Here's a function I've used over multiple projects, mostly for creating programmatic components for mapbox popups and markers, but also useful for creating components without actually adding them to DOM, for various purposes.
import Vue from "vue";
// import store from "./store";
export function addProgrammaticComponent(parent, component, dataFn, componentOptions) {
const ComponentClass = Vue.extend(component);
const initData = dataFn() || {};
const data = {};
const propsData = {};
const propKeys = Object.keys(ComponentClass.options.props || {});
Object.keys(initData).forEach(key => {
if (propKeys.includes(key)) {
propsData[key] = initData[key];
} else {
data[key] = initData[key];
}
});
const instance = new ComponentClass({
// store,
data,
propsData,
...componentOptions
});
instance.$mount(document.createElement("div"));
const dataSetter = data => {
Object.keys(data).forEach(key => {
instance[key] = data[key];
});
};
const unwatch = parent.$watch(dataFn || {}, dataSetter);
return {
instance,
update: () => dataSetter(dataFn ? dataFn() : {}),
dispose: () => {
unwatch();
instance.$destroy();
}
};
}
componentOptions is to provide any custom (one-off) functionality to the new instance (i.e.: mounted(), watchers, computed, store, you name it...).
I've set up a demo here: https://codesandbox.io/s/gifted-mestorf-297xx?file=/src/components/HelloWorld.vue
Notice I'm not doing the appendChild in the function purposefully, as in some cases I want to use the instance without adding it to DOM. The regular usage is:
const component = addProgrammaticComponent(this, SomeComponent, dataFn);
this.$el.appendChild(component.instance.$el);
Depending on what your dynamic component does, you might want to call .dispose() on it in parent's beforeDestroy(). If you don't, beforeDestroy() on child never gets called.
Probably the coolest part about it all is you don't actually need to append the child to the parent's DOM (it can be placed anywhere in DOM and the child will still respond to any changes of the parent, like it would if it was an actual descendant). Their "link" is programmatic, through dataFn.
Obviously, this opens the door to a bunch of potential problems, especially around destroying the parent without destroying the child. So you need be very careful and thorough about this type of cleanup. You either register each dynamic component into a property of the parent and .dispose() all of them in the parent's beforeDestroy() or give them a particular selector and sweep the entire DOM clean before destroying the parent.
Another interesting note is that in Vue 3 all of the above will no longer be necessary, as most of the core Vue functionality (reactivity, computed, hooks, listeners) is now exposed and reusable as is, so you won't have to $mount a component in order to have access to its "magic".
I'm wanting to assert that a component gets called from within another component with the correct arguments.
So within the component that I am testing there is a Title component that gets called with properties title & url. I'm trying to assert that it gets called with the correct arguments.
I'm pretty sure I want to use a sinon spy and do something like this
const titleSpy = sinon.spy(Title, render)
expect(titleSpy).to.be.calledWith( '< some title >' )
but with regards to React and Enzyme, I'm not really sure what I should be spying on. (Because apparently it's not render!)
In my spec file I am importing Title and console.loging it's value to find a function to spy on and I get:
function _class() {
_classCallCheck(this, _class);
return _possibleConstructorReturn(this, Object.getPrototypeOf(_class).apply(this, arguments));
}
Any ideas on how I can do this? Is it a case of going through and finding the element and checking it's attributes? If so that seems a bit...messy and seems like it goes against the principle of the Shallow render ("Shallow rendering is useful to constrain yourself to testing a component as a unit").
If you're just checking the value of properties passed to the component, you don't need sinon. For example, given the following component:
export default class MyComponent extends React.Component {
render() {
return (
<MyComponent myProp={this.props.myProp} />)
}
}
Your test might look like this:
describe('MyComponent ->', () => {
const props = {
myProp: 'myProp'
}
it('should set myProp from props', () => {
const component = shallow(<MyComponent {...props} />)
expect(component.props().myProp).to.equal(props.myProp)
})
})
You can achieve it with the help of .contains() method, without messing up with spies.
If you have a component:
<Foo>
<Title title="A title" url="http://google.com" />
</Foo>
You can make such an assertion:
const wrapper = shallow(<Foo />);
expect(wrapper.contains(<Title title="A title" url="http://google.com" />)).to.equal(true);
Such will fail:
const wrapper = shallow(<Foo />);
expect(wrapper.contains(<Title title="A wrong title" url="http://youtube.com" />)).to.equal(true);
This is an older question, but my approach is a little different than the existing answers:
So within the component that I am testing there is a Title component that gets called with properties title & url. I'm trying to assert that it gets called with the correct arguments.
ie. You're wanting to check that the component being tested renders another component, and passes the correct prop(s) to it.
So if the component being tested looks something like:
const MyComp = ({ title, url }) => (
<div>
<Title title={title} url={url} />
</div>
)
Then the test could look something like:
import Title from 'path/to/Title';, u
it('renders Title correctly', () => {
const testTitle = 'Test title';
const testUrl = 'http://example.com';
const sut = enzyme.shallow(<MyComp title={testTitle} url={testUrl} />);
// Check tested component rendered
expect(sut.exists).toBeTruthy();
// Find the Title component in the subtree
const titleComp = sut.find(Title); // or use a css-style selector string instead of the Title import
// Check that we found exactly one Title component
expect(titleComp).toHaveLength(1);
// Check that the props that were passed were our test values
expect(titleComp.prop('title')).toBe(testTitle);
expect(titleComp.prop('url')).toBe(testUrl);
});
I generally find Enzyme's functions to be very useful for all kinds of checks about components, without needing other libraries. Creating Sinon mocks can be useful to pass as props to components, to (for example) test that a callback prop is called when a button is clicked.
How can I pass props to the component that's inside the createContainer() function?
From what I see, createContainer() does not seem capable of passing props to the component.
For example, when you have:
export default createContainer(() => {
const myProp = 'one prop';
return {
my-prop: myProp,
};
}, App);
The first argument of createContainer receives a function where you can declare your props. The second argument is the component you want to wrap.
For more information, take a look in the createContainer itself:
https://github.com/meteor/react-packages/blob/devel/packages/react-meteor-data/createContainer.jsx