use React Hook in multi files? - react-native

Senario : I have a dialog, and i use a react-hook to make it disappear ,like const[show,setShow]= useState(false) , this dialog file is a seperate file with main screen file, which contain button to show this dialog
Problem : I don't know how to show this dialog in main screen, for example, my dialog file called Mydialog.js have componet Mydialog, so i tried to put that hook show in props , Mydialog(show), but look like it not work that way, i still can't show the dialog
Question. How can i use react-hook for multi file, like i have hook in dialog file, present the dialog status ( show or not) then i can use it in mainScreen file to set show to true, then i can use that show and set to false when click button in dialog

If I understood it right you're trying to bring up a dialog when you interact with something on the main page and then close it by clicking on the X within the dialog. Would something like this work?
Main.js:
import "./styles.css";
import { useState } from "react";
import MyDialog from "./MyDialog";
export default function App() {
const [showDialog, setShowDialog] = useState(false);
const handleDialog = () => {
setShowDialog(!showDialog);
};
return (
<>
<button onClick={handleDialog}>Show Dialog</button>
Show Dialog: {showDialog?.toString()}
{showDialog && <MyDialog handleDialog={handleDialog} />}
</>
);
}
MyDialog.js:
import "./styles.css";
export default function MyDialog({ handleDialog }) {
return (
<>
<div className="popup">
<div className="popup_open">
<h1>Dialog Content</h1>
<button onClick={handleDialog}>X</button>
</div>
</div>
</>
);
}
Sandbox link if you want to test: https://codesandbox.io/s/admiring-feather-sy1gf

You can use Context to maintain state between multiple components.
const DialogContext = createContext();
const DialogProvider = ({ children }) => {
const [isDialogVisible, setDialogVisible] = useState(false);
const value = {
isDialogVisible,
setDialogVisible,
}
return <DialogContext.Provider value={value}>{children}</DialogContext.Provider>
}
const useDialog = () => {
const context = useContext(DialogContext);
return context;
}
Render the DialogProvider in one of the top-level components, for example in App.js.
// App.js
return (
<DialogProvider>
// ...
</DialogProvider>
)
Then inside of your components you can use your hook and trigger the visibility of the dialog.
MyComponentA:
const { isDialogVisible, setDialogVisible } = useDialog();
const toggleDialogVisibility = () => {
setDialogVisible(!isDialogVisible);
}
return (
<Button title="Toggle" onPress={toggleDialogVisibility} />
)
MyComponentB:
const { isDialogVisible } = useDialog();
if(isDialogVisible) {
return <Text>My Dialog</Text>
}
return null;
A very simple example of usage, here's a Snack for the above.

Related

Is it possible to wait for a component to render? React Testing Library/Jest

I have a component. It has a button. Upon pressing the button, I am changing the style of the button text (color) using setState function. When I am testing the changed component, the test is failing because the change happens asynchronously. I want to do something as is given here (https://testing-library.com/docs/dom-testing-library/api-async/)
const button = screen.getByRole('button', { name: 'Click Me' })
fireEvent.click(button)
await screen.findByText('Clicked once')
fireEvent.click(button)
await screen.findByText('Clicked twice')
But rather than waiting for the text to change. I want to wait for the text color to change. Thanks
This is the code for my button
<Button onPress = {() => {this.setState({state : 1});}}>
<Text style = {style}>Button Text</Text>
</Button>
So when this button is pressed. state is set to 1. And in render :
if(this.state.state === 1) style = style1
else style = style2;
But it can be seen from logs that render is called after the test checks for the styles. So How can I wait for the render to complete before checking if the font color has been changed?
Here is the testing code
test('The button text style changes after press', () => {
const {getByText} = render(<Component/>);
fireEvent.press(getByText('button'));
expect(getByText('button')).toHaveStyle({
color : '#ffffff'
});
})
It looks like you have a custom button, not a native button. I'm guessing your component is something like this:
import React from "react";
import {Text, TouchableOpacity} from "react-native";
const Button = ({pressHandler, children}) => (
<TouchableOpacity onPress={pressHandler}>
{children}
</TouchableOpacity>
);
const ColorChangingButton = ({text}) => {
const [color, setColor] = React.useState("red");
const toggleColor = () => setTimeout(() =>
setColor(color === "green" ? "red" : "green"), 1000
);
return (
<Button pressHandler={toggleColor}>
<Text style={{color}}>{text}</Text>
</Button>
);
};
export default ColorChangingButton;
If so, you can test it with waitFor as described here:
import React from "react";
import {
fireEvent,
render,
waitFor,
} from "#testing-library/react-native";
import ColorChangingButton from "../src/components/ColorChangingButton";
it("should change the button's text color", async () => {
const text = "foobar";
const {getByText} = render(<ColorChangingButton text={text} />);
fireEvent.press(getByText(text));
await waitFor(() => {
expect(getByText(text)).toHaveStyle({color: "green"});
});
});
For a native button which has rigid semantics for changing colors and doesn't accept children, instead using title="foo", a call to debug() shows that it expands to a few nested elements. You can use
const text = within(getByRole("button")).getByText(/./);
expect(text).toHaveStyle({color: "green"});
inside the waitFor callback to dip into the button's text child and wait for it to have the desired color.
I used the same packages/versions for this post as shown in React Testing Library: Test if Elements have been mapped/rendered.
You can try
<Text style = {this.state.state === 1 ? style1 : style2}>Button Text</Text>
This will consequently lead to the style being defined all time. So you don't have to wait for the setState to complete.
Edit
You can use the callback provided by setState function to perform your tests for styles.
this.setState({
state : 1
} , () => {
//this is called only after the state is changed
//perform your test here
})

How to allow the props of every component to be defined in one central place in react native?

the question is clear. I think it can be done with react native elements. But how? I am very new to react native.
I read the documentation in here. It has a code like this:
import { ThemeProvider, Button } from 'react-native-elements';
const theme = {
Button: {
raised: true,
},
};
// Your App
const App = () => {
return (
<ThemeProvider theme={theme}>
<Button title="My Button" />
<Button title="My 2nd Button" />
</ThemeProvider>
);
};
What if this part of the code:
const theme = {
Button: {
raised: true,
},
};
was coded in another file. How will I make the buttons raised?
You have 2 ways to aboard your problem.
First, if you want to use the same style for your react-native-elements components across certain files and they are all the children of the same parent file, you use this bit of code :
<ThemeProvider theme={theme}>
<Button title="My Button" />
<Button title="My 2nd Button"/> // ... plus any other component that contains your react native elements components
</ThemeProvider>
for this case if you want to put your config variable in another file , you can do it like this :
create a new file that contains your config variable let say for example a new file called config.js
// config.js file
export default {
Button:{
raised:true
}
// ... other RN-elements props
}
// or this
const theme = {
Button: {
raised: true,
},
}
export {theme}
then import it in your workspace like so :
// the file were you are using your themeProvider
import theme from "path_to_your_config.js_file"
// or respectively
import {theme} from "path_to_your_config.js_file"
// then use the variable theme as you like
the second way is that you create your own custom component and you style it however you want and you use it in your app.

set React Native state to the Button title prop

I can't figure out how to update the state in my basic React Native application to equal whatever is in the title prop of the Button.
I've tried just setting the state to be {title} and that hasn't worked. I am using the useState hook so I don't think I should need to use "this.".
import React, {useState} from 'react';
import { View, Text, Button } from 'react-native';
const StarterForm = () => {
const [formStage, setFormStage] = useState(1)
const [feelings, setFeelings] = useState('')
console.log(feelings)
const updateFormStage = () => {
setFormStage(formStage + 1)
setFeelings({title})
}
switch (formStage) {
case 1:
return (
<View>
<Text>How are you?</Text>
<Button title="Excellent" onPress={updateFormStage}/>
</View>
)
case 2:
return (
<Text>This is the case of two</Text>
)
}
};
In the example, I expect console.log(feelings) to equal "Excellent" once the button has been pressed.
You can use ref for that, but I think the best way to solve your problem is store "Excellent" in a variable, and use onPress={() => updateFormStage(mVariable)}
One way would be setting reference for your defined button and after click on it, retrieve data from reference like this:
<Button ref={ref => { this.button = ref; }}
title="Excellent"
onPress={this.updateFormStage} />
You can access your title via button reference using this.button.title:
updateFormStage = () => {
console.log(this.button.title);
}

Pivot: Start with no tabs selected

Using Fabric React, I am working on a component that uses the Pivot element.
When the component is first shown, no tabs should be selected, and some content must be shown under the tab headers. Once a tab is clicked, related content will be shown there.
The example "No Pivots Selected" in the documentation page is pretty close to what I want. In the first render, no tabs are selected. I am thinking that the prop selectedKey={null} should give that result.
Following code is based on that example, yet even when the component is shown first time, a tab (first one) is shown as selected (e.g. there is a blue underline under it).
What is the problem?
import * as React from "react";
import { Pivot, PivotItem } from "office-ui-fabric-react";
export interface MainProps {}
export const Main: React.FC<MainProps> = () => {
const [selectedKey, setSelectedKey] = React.useState(null);
const pivotItems: { [key: string]: React.ReactElement<any> } = {
Settings: <div>Settings</div>,
Controls: <div>Controls</div>
};
const _getTabId = (itemKey: string): string => {
return `ShapeColorPivot_${itemKey}`;
};
const _handleLinkClick = (item: PivotItem): void => {
setSelectedKey(item.props.itemKey);
};
return (
<>
<Pivot
headersOnly
selectedKey={selectedKey}
getTabId={_getTabId}
onLinkClick={_handleLinkClick}
style={{ flexGrow: 1 }}
>
{Object.keys(pivotItems).map(name => (
<PivotItem
key={`pivotItemKey_${name}`}
headerText={name}
itemKey={name}
/>
))}
</Pivot>
{selectedKey ? pivotItems[selectedKey] : <div>Start</div>}
</>
);
};
I did a quick codepen using
<Pivot selectedKey={null} >
and it worked just fine. Are you sure you're on the most recent version of Fabric?

Using a class as props in a react native component

I'm learning react (coming from a native iOS/Swift background) and I'm a bit confused about something I can't get to work.
I have a component that accepts props, so I figured I would write a class to model those props:
class HeaderProps {
text: string;
constructor(headerText:string) {
this.text = headerText;
}
}
// Make a component
const Header = (props:HeaderProps) => {
const { textStyle, viewStyle } = styles;
return (
<View style={viewStyle}>
<Text style={textStyle}>{props.text}</Text>
</View>
);
};
and I'm exporting from my component like so:
export {Header, HeaderProps};
I'm then importing it:
import {Header, HeaderProps} from './src/components/header';
// Create a component
const App = () => ( <Header headerText={ new HeaderProps('Album') } />);
No text is appearing in my component.
If I just pass a string through as props it works fine, can't think of any reason why sending a class through wouldn't work.
I'm using flow type to declare the types of my arguments, not sure if that might be causing any issues.
A point in the right direction would be much appreciated!