Export child components and use individually - react-native

I have a case where I need to export two child components and use individually.
Much Desired outcome (Extremely simplified):
Controls.js:
const Controller = ( props ) => {
const ControlBoxes = () => {
return(<Button>Move around!</Button>)
}
const MoveableBox = () => {
return(<View>I will be moved! </View>)
}
return {ControlBoxes, MoveableBox}
}
export default Builder
Canvas.js:
import Controller from './controls'
const boxScaleMove = boxes.map((box, index) => {
return (
<Bulilder.MoveableBox key={box.id} box={box}/>
)
}
const boxController = boxes.map((box, index) => {
return (
<Bulilder.ControlBoxes key={box.id} box={box}/>
)
}
return (
...
{boxController}
...
...
{boxScaleMove}
...
)
Any idea how I can achieve this or am I missing something fundamental? The main issue is that I want to avoid resorting to useContext (due to performance reasons in the case of a lot of boxes rendered) and be able to share variables and states between MoveableBox-component and ControlBoxes-component via Controller -parent.
Any help would be greatly appreciated.

You could use the compound component and use a lower level context to avoid re-rendering of the whole tree and share states across your components that way, below I would ilustrate a basic example of how that would work.
const RandomContext = createContext();
export default function Controller({children, ...rest}) {
const [randomState, setRandom] = useState(0);
return (
<RandomContext.Provider value={{ randomState, setRandom }}>
<div {...rest}>{children}</div>
</RandomContext.Provider>
);
}
Controller.ControlBoxes = function (props) {
const { setRnadom } = useContext(RandomContext);
return (
<Button onClick={() => setRandom(2)} {...props}>Move around!</Button>
);
};
Controller.MoveableBox = function (props) {
const { randomState } = useContext(RandomContext);
return randomState ? <View {...props}>I will be moved!</View> : null;
};
And you would use it as:
<Controller>
<Controller.ControlBoxes />
<Controller.MoveableBox />
<Controller>
In the compound components pattern we are leveraging the fact that in javascript when you declare a function you create a function/object combo. Therefor Controller function is both a function and an object, so we can assign properties the the object part of that combo, properties which are in our case ControlBoxes and MoveableBox which are functions themselves.
NOTE you should probably assign named function the the properties of that object, it's easier to debug if the case needed.
Example.Function = function ExampleFunction(props) {
return "Example";
};

Related

Passing state Array to child components in React

I'm trying to pass two array variables to a child component in react. I know it's something simple and I'm being stupid but I jest need a hand.
I make two API calls in two components that are in the same file. They should be adding the iterable data into the variable using the set methods. By the time the call to the component comes, the variables are undefined.
What am I doing wrong?
const ShowResults = props => {
const { pk } = props;
const [attributionsData, setAttributionsData] = useState([])
const [interactionsData, setInteractionsData] = useState([])
function ListAttributions({ pk }) {
const { loading, error, data } = useQuery(GET_ATTRIBUTIONS, {
variables: { pk },
});
if ( loading ) return <h2>LOADING... </h2>;
if ( error ) return `Error! ${error}`;
if ( data !== undefined){
setAttributionsData(data.listAttributions.items);
return <div>
{attributionsData.map(({ sk, nominal, organisation, attribution, file_name, datetime_added, exhibit }, index) => (
<AttributionsCard
key={index}
Sk={sk}
Nominal={nominal}
Organisation={organisation}
Attribution={attribution}
FileName={file_name}
FoundInsidePhone={file_name.match(/[0-9]+/g)}
DateTimeAdded={datetime_added}
Exhibit={exhibit}
Pk ={pk}
/>
))}
</div>
}
}
function ListInteractions({ pk }) { //Aug 2022 - This is a working prototype of the query behavious i'm looking for.
const { loading, error, data } = useQuery(GET_INTERACTIONS, {
variables: { pk },
});
if ( loading ) return <h2>LOADING... </h2>;
if ( error ) return `Error! ${error}`;
if ( data !== undefined){
console.log("Interactions Data - " + data );
setInteractionsData(data.listInteractions.items)
return <div>
{interactionsData.map(({ direction, interaction, partner, duration, datetime, exhibit, organisation, file_name, datetime_added }, index) => (
<InteractionsCard
key={index}
Interaction={interaction}
Direction={direction}
Partner={partner}
Duration={duration}
DateTime={datetime}
Exhibit={exhibit}
Organisation={organisation}
FileName={file_name}
DateTimeAdded={datetime_added}
Pk={pk}
/>
))}
</div>
}
}
return (
<div style={{display: "inline-grid", inlineSize: "max-content", width: "100%"}}>
{/* <h2>🚀 Quest has identified the following results against {pk}</h2> */}
<center>
<ListAttributions pk={pk}/>
<ListInteractions pk={pk}/>
</center>
<br/>
<br />
{/* <ShowInteractionsDataGrid pk={pk}/> */}
{attributionsData !== [] &&
// Sep 14th 20:42 - No idea whats going on with this... The ListAttributions components above work fine
<TabbedResults pk={pk} attributionsData={attributionsData} interactionsData={interactionsData} />
}
<br />
<br />
</div>
);
}
ShowResults.propTypes = {
pk: PropTypes.string
};
export default ShowResults
a) You cant setSomeState and then use it immediately. It will not be immediately available within that same function. So the first time you try to map you will have the initialiser of the useState. In this case an empty array.
b) You should do the queries in the parent component, and pass them in as props so there is no need to set parent state
c) Its not good to be setting the state of a parent scope like this. You should move the children to different files (or at least outside of the component) and communicate with props and callbacks. You will save yourself a lot of trouble.
d) This will always be true so TabbedResults will always render:
attributionsData !== []
This is because [] makes a new array. You should use:
attributionsData && attributionsData.length
e) Never set state straight form the render function. Use a useEffect hook to set it based on the change of a property, such as your data suddenly being populated

How do you map props in react-native when their is a require property

Now when these tags are in component file how does one map through various values.
Example
1. <Image source = require("")/>
2. const { sound } = await Audio.Sound.createAsync(require("../../assets/sounds/slack.mp3")
You can map like this:
const { sound } = await Audio.Sound.createAsync(require("../../assets/sounds/slack.mp3")
return sound.map((item, index) => (
<YourComponent key={index}>{item.value}</YourComponent>
))

Check if first child renders nothing in Native Testing Library

Suppose I have the following component:
const MyComponent = () => null;
In React Testing Library (RTL), the following, AFAIK, should work:
const { container } = render(<MyComponent />);
expect(container.firstChild).toBeEmpty();
where .toBeEmpty is a custom matcher from jest-dom. However, in NTL, container.firstChild is not defined so I can't use .toBeEmpty matcher from jest-native. After some experimentation, I got it to work as follows:
expect(container.children[0]).toBeUndefined();
Is there any other, possibly better, way to do this?
In my case I had a situation like so:
function PhotoComponent({policy}) {
if (policy === 'unavailable')
return null;
return (<View />);
}
Tested like this:
it("should not render if policy is 'unavailable'", () => {
const {toJSON} = render(<PhotoComponent policy={'unavailable'} />);
const children = toJSON().children;
expect(children.length).toBe(0);
});
Some times you will want to filter the children like, for instance:
const children = toJSON().children.filter(
(child) => child.type === 'View' || child.type === 'Modal'
);
Keep calm and Happy coding!

Store result from api call React native

How can I store the recipe name from this api call into a state variable or some other variable to be used further down in the render
{
this.props.recipeList.recipeList.filter((recipe) => recipe.recipeName === search).map(recipe => {
return <Recipe
name={recipe.recipeName}
numberOfServings={recipe.numberOfServings}
key={'recipe-' + recipe.recipeId}
/>
})
}
As this is performed within render, avoid modifying the component state.
I would recommend declaring a variable at the top of your render method and then assigning it in your map call. This, of course, assumes that filter will only ever return a single result.
Something like:
render() {
let name;
....
{
this.props.recipeList.recipeList.filter((recipe) => recipe.recipeName === search).map(recipe => {
name = recipe.recipeName; // this bit
return <Recipe
name={recipe.recipeName}
numberOfServings={recipe.numberOfServings}
key={'recipe-' + recipe.recipeId}
/>
})
}
....

Can I use multiple or nested elements in the Label of a Picker Item in React Native?

I'm using React Native with NativeBase and would like to make the labels of my Picker more complicated than just one plain string of text.
But is it even possible to pass elements as the label, say multiple child elements wrapped in a single top-level element?
Or do Pickers only support plain text as labels?
As requested by bennygenel, here's a version of what I've tried:
export default class ThingPicker extends React.Component {
render() {
const {
initialThing,
things,
onThingChanged,
} = this.props;
const orderedThings = things.sort();
return (
<Picker
selectedValue={initialThing}
onValueChange={onThingChanged}>
{buildThingItems(orderedThings)}
</Picker>
);
}
}
function buildThingItems(orderedThings) {
let items = orderedThings.map(th => {
const it = th === "BMD" ? (<Text key={th} label={"foo"} value={"bar"}}>Hello</Text>)
: (<Picker.Item key={th} label={th} value={th} />);
return it;
});
}
Yes! It is possible, it just might not look very "right" for React/JSX code. Just create the elements you need and assign them to the label field:
function buildThingItems(orderedThings) {
let items = orderedThings.map(th => {
const it = (<Picker.Item
key={th}
label={currency === "BMD" ? (<Text>Hello</Text>) : th}
value={th} />);
return it;
});
}