I try to pass State and SetState by using useContext.
But there someting strange: only one can pass to children compoenents.
I want to pass {isLogin, setIsLogin, name}, only name can show correctly like this:
also can run in codesandbox
codesandbox
import React, { useState, createContext, useContext } from "react";
import { Text, View, Button } from "react-native";
const AppContext = createContext();
const AppProvider = (props) => {
const [isLogin, setIsLogin] = useState(false);
const [name, setName] = useState("molly");
return (
<AppContext.Provider value={(isLogin, setIsLogin, name)}>
{props.children}
</AppContext.Provider>
);
};
const App = () => {
return (
<AppProvider>
<Home />
</AppProvider>
);
};
const Home = () => {
const storedEvents = useContext(AppContext);
console.log("storedEvents", storedEvents);
const login = () => {
storedEvents.setIsLogin(true);
};
if (!storedEvents.isLogin) {
return (
<View>
<View>
<Text>{storedEvents}</Text>
</View>
<Button title="login" onPress={() => login()} />
</View>
);
}
return (
<View>
<Text>I am Login.</Text>
</View>
);
};
export default App;
You can pass multiple values as the JSON object, so your AppProvider should look something like this.
const AppProvider = (props) => {
const [isLogin, setIsLogin] = useState(false);
const [name, setName] = useState("molly");
return (
<AppContext.Provider value={{isLogin, name, setIsLogin}}>
{props.children}
</AppContext.Provider>
);
};
Notice the change value={{isLogin, name, setIsLogin}} instead of value={(isLogin, name, setIsLogin)}
There's another issue in the Home component which will give render error. You are trying to display storedEvents object which is not allowed. So change it to text string like Not logged in or something.
Related
new to React Native.
I'm using Context hook state to use an array in two tab screens of an application.
One of the screens displays the context array in the form of a FlatList, the other one inserts items into the array. What would be the proper way to rerender when the array changes?
Here's the code:
APP.JS
const App = () => {
const Tab = createBottomTabNavigator();
return(
<NavigationContainer>
<Tab.Screen name="HomeScreen" component={Home} />
<Tab.Screen name="DiscoverScreen" component={Discover} />
</NavigationContainer>
);
}
export default function AppWrapper() {
return (
<AuthProvider>
<App />
</AuthProvider>
);
}
AUTHCONTEXT.JS
const AuthContext = createContext()
const AuthProvider = ({ children }) => {
const [array, setArray] = useState([]);
return (
<AuthContext.Provider value={{ array, setArray }}>
{children}
</AuthContext.Provider>
)
}
export {AuthContext, AuthProvider}
HOME.JS
export default Home = () => {
const {array} = useContext(AuthContext);
return(
<View>
<FlatList
data={array}
renderItem={(item)=>{
return(
<Text>{item}</Text>
)}}
/>
</View>
)
}
DISCOVER.JS
export default Discover = () => {
const {setArray} = useContext(AuthContext);
setArray((currentArray)=>[...currentArray,'test']);
return(
<View></View>
);
}
You shouldn't invoke setArray unconditionally in Discover function component. If you need to add value to the array when component appears, use useEffect hook (and don't forget to setup second argument, probably it will be empty array, otherwise you get into infinite loop and component crash with an error)
export default Discover = () => {
const {setArray} = useContext(AuthContext);
useEffect(() => {
setArray((currentArray)=>[...currentArray,'test']);
}, [])
return(
<View></View>
);
}
And you don't need to worry about rerender the component, React handle this for you. Once setArray invokes it automatically rerender all component that use AuthContext and their children.
How should I test if className was added to child component in Jest with react-testing-library when props comes from context? I'm using CSS modules
Here's an example i wrote for this issue:
const Context = createContext()
const ContextProvider = ({ children }) => {
const [state, setState] = useState(false);
return (
<ContextProvider value={state}>
{children}
</ContextProvider>
);
};
const Display = () => {
const state = useContext(Context)
return <div className={`${state && styles[my-class]}`}></div>
}
const App = () => {
return (
<ContextProvider>
<Display />
</ContextProvider>
)
}
I tried something like this
it('should add classname', () => {
const { baseElement } = render(
<Context.Provider value={value}>
<Display />
</Context.Provider>
);
expect(baseElement).toHaveAttribute(
'class',
'my-class'
);
});
what is the way to change class component to function component in my example ?
in my example i try to change it to a function component but it doesnt works ,
I would be happy to some help with this issue .
import * as React from 'react';
import { Searchbar } from 'react-native-paper';
export default class MyComponent extends React.Component {
state = {
firstQuery: '',
};
render() {
const { firstQuery } = this.state;
return (
<Searchbar
style={{marginTop: 60}}
placeholder="Search"
onChangeText={query => { this.setState({ firstQuery: query }); }}
value={firstQuery}
/>
);
}
}
import React, {useState} from 'react';
import { Searchbar } from 'react-native-paper';
const MyComponent =()=> {
const [firstQuery, setFirstQuery] = useState("");
return (
<Searchbar
style={{marginTop: 60}}
placeholder="Search"
onChangeText={query => { setFirstQuery(query ) }}
value={firstQuery}
/>
);
}
export default MyComponent;
Do it like this
const MyComponent = () => {
const [firstQuery, setFirstQuery] = useState('');
const _onChange = query => setFirstQuery(query);
return (
<Searchbar
style={{marginTop: 60}}
placeholder="Search"
onChangeText={_onChange}
value={firstQuery}
/>
);
}
const MyComponent = () => {
const [firstQuery, setFirstQuery] = useState('');
const handleChange = (query) => setFirstQuery(query);
return (
<Searchbar
style={{marginTop: 60}}
placeholder="Search"
onChangeText={handleChange}
value={firstQuery}
/>
);
}
Try this way
import * as React from 'react';
import { Searchbar } from 'react-native-paper';
const MyComponent = (props) => {
const [firstQuery, setFirstQuery] = React.useState('');
return (
<Searchbar
style={{marginTop: 60}}
placeholder="Search"
onChangeText={query => { setFirstQuery(query) }}
value={firstQuery}
/>
);
}
export default MyComponent;
This should do it for you:
//Only get what we need to reduce overhead
import React, { useState } from "react";
import { Searchbar } from "react-native-paper";
//you may include props here if needed, otherwise = () =>
const MyComponent = (props) => {
//State as hook, first object in array will be value second will be function to set value and notify update required
const [firstQuery, setFirstQuery] = useState("");
//handle for onChangeText
const onChangeTextHandle = (query) => {
//set state using hook
setFirstQuery(query);
};
//No need for render method, just return JSX
return (
<Searchbar
style={{ marginTop: 60 }}
placeholder="Search"
onChangeText={onChangeTextHandle}
value={firstQuery}
/>
);
};
//Export component Here
export default MyComponent;
I have the main component as below.
const MainApp: React.FC = () => {
return (
<div>
<DummyComp/>
<ComponentA />
<ComponentB />
</div>
);
}
export default App;
"Component B" has some form elements and when it's value changes, I require all the form values to be passed to the "Component A".
All the examples that I found are not using Functional Components. So I am not sure how to create callback functions and use states in Functional Components.
const MainApp: React.FC = () => {
const [formData, setFormData ] = useState({});
return (
<div>
<DummyComp/>
<ComponentA formData={formData} />
<ComponentB onClick={setFormData} />
</div>
);
}
export default App;
You can edit formData object as you wish in ComponentB with setFormData method, and you can read the formData object in ComponentA.
You can either use Redux (Global State) to do this, OR use a state manage in the Main Component.
const MainApp: React.FC = () => {
const [data, setData] = useState();
useEffect(() => {
setData() //anyhting initial
},[formdata]);
const handleData = (data) => {
setData(data)
}
return (
<div>
<DummyComp/>
<ComponentA data={data} />
<ComponentB onformclick={handleData} />
</div>
);
}
export default App;
Lets Suppose component A is like
const ComponentA: React.FC = ({props}) => {
const data = props.data;
return (
<div>
//any html here
//example
<div>
{
data ?
data.property //any property existing on data
:null
}
</div>
</div>
);
}
export default ComponentB;
Lets Suppose component A is like
const ComponentA: React.FC = ({props}) => {
const [formdata, SetformData] = useState();
return (
<div>
//form here
//any button or any element to submit form
<button onClick={()=>props.onformclick(formdata)}> submit </button>
</div>
);
}
export default ComponentB;
I wonder if there is not a better way to manage the open and close of Dialogs in a functional component? You can find an example below:
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import EditDialog from './EditDialog';
import DeleteDialog from './DeleteDialog';
const ContactCard = ({ contact }) => {
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
const handleDeleteOpen = () => {
setDeleteOpen(true);
};
const handleDeleteClose = () => {
setDeleteOpen(false);
};
const { type, firstName, lastName, phoneNumber, mail } = contact;
return (
<>
<div className={classes.main}>
{/* All my contact informations */}
</div>
<EditDialog handleClose={handleEditClose} open={editOpen} />
<DeleteDialog handleClose={handleDeleteClose} open={deleteOpen} />
</>
);
};
ContactCard.propTypes = {
contact: PropTypes.object.isRequired
};
export default ContactCard;
I think this is super redundant but I cannot find a nicer way to manage several different dialogs.
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
const handleDeleteOpen = () => {
setDeleteOpen(true);
};
const handleDeleteClose = () => {
setDeleteOpen(false);
};
Many thanks for your time and advice!
To reduce some of the redundancy of your code, you could set the open/close in one function, by essentially toggling the current state. I did mine inline, but you could still create a handleEdit function and toggle the state there.
import React, {useState} from "react";
import ReactDOM from "react-dom";
function App() {
const [editCard, setEditCard] = useState(false)
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => setEditCard(!editCard)}>Toggle Edit</button>
{editCard && <div>Card is open for editing</div>}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is another example with your code. I didn't run it, but it should look something like this.
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import EditDialog from './EditDialog';
import DeleteDialog from './DeleteDialog';
const ContactCard = ({ contact }) => {
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const handleEdit = () => {
setEditOpen(!editOpen);
};
const handleDelete = () => {
setDeleteOpen(!deleteOpen);
};
const { type, firstName, lastName, phoneNumber, mail } = contact;
return (
<>
<div className={classes.main}>
{/* All my contact informations */}
</div>
{
editOpen && <EditDialog handleEdit={handleEdit} />
}
{
deleteOpen && <DeleteDialog handleClose={handleClose} />
}
</>
);
};
ContactCard.propTypes = {
contact: PropTypes.object.isRequired
};
export default ContactCard;
The responsibility of open the dialog should be of the main component. This way the modal is only rendered if the state property is true.
Another tip is use <React.Fragment> insted <>
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import EditDialog from './EditDialog';
import DeleteDialog from './DeleteDialog';
const ContactCard = ({ contact }) => {
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const handleEditOpen = () => {
setEditOpen(!editOpen);
};
const handleDeleteOpen = () => {
setDeleteOpen(!deleteOpen);
};
const { type, firstName, lastName, phoneNumber, mail } = contact;
return (
<React.Fragment>
<div className={classes.main}>
{/* All my contact informations */}
</div>
{
editOpen && <EditDialog handleClose={handleEditOpen} />
}
{
deleteOpen && <DeleteDialog handleClose={handleDeleteOpen} />
}
</React.Fragment>
);
};
ContactCard.propTypes = {
contact: PropTypes.object.isRequired
};
export default ContactCard;
To incapsulate logic of changing dialog opening state, I'd recommend to create separate hook:
const useToggle = (defaultValue) => {
return useReducer((value) => !value, !!defaultValue)
}
This hook is basically useState but setState function isn't waiting for argument to update state, it updates state with the inverse of current state.
This might be useful while working with dialogs:
const ContactCard = () => {
const [editOpen, toggleEditOpen] = useToggle(false);
const [deleteOpen, toggleDeleteOpen] = usetoggle(false);
return (
<>
<div className={classes.main}>
{/* All my contact informations */}
</div>
{editOpen && <EditDialog handleEdit={toggleEditOpen} />}
{deleteOpen && <DeleteDialog handleClose={toggleDeleteOpen} />}
</>
);
};