It's possible to pass all the props of the page to all its children components implicitly (without passing as parameter)?
I need something like this:
export default function Home({ user }) {
if (!user) {
return (
<>
<component1/>
<component2/>
<component3/>
</>
);
} else {
return (
<></>
);
}
}
export async function getServerSideProps(context) {
const { Auth } = withSSRContext(context);
try {
const user = await Auth.currentAuthenticatedUser();
return {
props: {
user: JSON.parse(JSON.stringify(user)),
}
}
}
I need that user is available to component1, component2, component3 and all its sub-components without passing explicitly.
I've read that you can use context for this. But I'm using amplify aws and I don't know if it's possible ...
First your components should be capitalize.
You can use context to pass the values without the use of the props.
For that you need to setup context using createContext
import { createContext } from "react";
const UserContext = createContext()
export default function Home({ user }) {
if (user) {
return (
<UserContext.Provider value={user}>
<Component1/>
<Component2/>
<Component3/>
</UserContext.Provider>
);
} else {
return (
<></>
);
}}
And then inside the Component1 or 2 or 3 ... You can use useContext to get the value.
import { useContext } from "react"
function Component1() {
const user = useContext(UserContext);
return (
<>
<h1>Component 1</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}
You can use react context, redux, recoil. Recoil is easy to use.
Context provides a way to pass data through the component tree without having to pass props down manually at every level, It's react concept, AWS Amplify will not restrict from using this. Feel free to use it here.
Related
I am using Context API to escape prop drilling and I have created context like:
const {
Provider,
Consumer,
} = React.createContext<Context>({
carNames: [],
});
and Providing the context like:
<Provider
value={{
carNames
}}
>
{children}
</Provider>
So, to consume the context value I wanted to use useContext() BUT since I destructured Consumer and Provider out of createContext. What value should I pass to the argument of useContext(???). Tried this useContext({Consumer, Provider}) but no result 😅.
Not sure why you need to deconstruct it. Here is an working example:
App.tsx
import { ContextProvider } from "../ContextProvider";
...
const contextValue = {
carValues,
setCarValues,
};
return (
<ContextProvider.Provider value={contextValue}>
...
</ContextProvider.Provider>
);
};
ContextProvider.tsx
export interface IContextProvider {
carValues: ICarVlaues[];
setCarValues: Dispatch<SetStateAction<ICarValues[]>>;
}
export const ContextProvider = createContext<IContextProvider>({
carValues: [],
setCarValues: () => {},
});
Use it like so:
const { carValues } = useContext(ContextProvider);
I have this userContext file:
import React from 'react'
const userContext = React.createContext({ loggedInUser: {} })
export { userContext }
I provide correctly a value in App:
import { userContext } from './../contexts/UserContext'
class App extends Component {
//...
componentDidMount = () => {
this.authServices
.isLoggedIn()
.then(response => this.setState({ loggedInUser: response.data })
.catch(() => this.setUser(undefined))
}
return (
<>
<userContext.Provider value={this.state.loggedInUser}>
<Navigation/>
</userContext.Provider>
</>
)
}
And can access that value in any nested component such as in Navigation:
/* ... */
<userContext.Consumer>
{value => value && <h1>Hi! {value.username}</h1>}
</userContext.Consumer>
/* ... */
How can I change from these nested components such as Navigation the value from the loggedInUser property in the context?
I don't find the way to attach methods to the context and use them, for example, when user logs out in the Navigation component:
/* ... */
logOut = () => {
this.authService
.logout()
.then(() => /* modify here the loggedInUser from the context */ )
/* ... */
Please note this is a stateful component and I need a non-Hooks solution.
First you need to create a function that changes the user data and pass it in a context so child components can leverage it.
updateUserData = (userInfo) => {
this.setState({ loggedInUser: userInfo })
}
After that you can pass that function in the context:
<userContext.Provider value={{
loggedInUser: this.state.loggedInUser,
updateUserData: this.updateUserData
}}>
<Navigation/>
</userContext.Provider>
Since you are accessing context Provider the old way you will need to attach
the static contextType = userContext; on top of your class to be able to access context value like this outside of your render method:
const { loggedInUser, updateUserData } = this.context;
Now in your logOut access the updateUserData like above and pass it the new userData.
Here is more info about contextType:
https://reactjs.org/docs/context.html#classcontexttype
You need to modify your context file and set your method in the context file itself as shown below:
export const UserContext = createContext();
class UserContextProvider extends Component {
state={
loggedInUser: {}
}
/* ---Logout method here also --- */
logOut = () => {
this.authService
.logout()
.then(() => {} /* modify here the loggedInUser from the context */ )
}
render() {
return (
<UserContext.Provider value={{...state},logout:{this.logout}>
{this.props.children}
</UserContext.Provider>
)
}
}
export default UserContextProvider;
You also need to updated your App.js file by wrapping so that {this.props.children} works fine in context file:
import UserContextProvider from './userContext.js'; // import as per your path
<UserContextProvider>
<div className="App">
<Navigation/>
</div>
</UserContextProvider>
And your Consumer file is modified as follows:
<userContext.Consumer>
>{(context)=>{
const {loggedInUser,logout} = context;
return(
<div>
// Your JSX as per your condition
</div>
)
</userContext.Consumer>
You can visit my github Context API Project to learn more about Context-API:
You will learn context creation and different ways to consume the context also with and without useContext.
https://github.com/emmeiwhite/Context-API
I have a functional componet without React but that uses Redux like following:
export const isAuthenticated = () => ({user}) => {
console.log("user : ", user);
return true;
};
const mapStateToProps = (state) => {
return {
user: state.auth.userInfo
}
}
export default connect(mapStateToProps)(isAuthenticated as any)
And to use above function, I uses like:
{isAuthenticated() && (
<li className="nav-item">
<NavLink
className="nav-link"
activeStyle={{
color: "#1ebba3"
}}
to="/dashboard"
onClick={(e) => { if (this.menu.classList.contains("show")) { this.inputElement.click() } }}
>
Dashboard
</NavLink>
</li>
)}
It doesn't work. It just doesn't even get into that isAuthenticated function since I don't see any output for console.log("user : ", user);. It should output something like user: undefined, but it doesn't even output that.
If I change
export const isAuthenticated = () => ({user}) => {
to
export const isAuthenticated = ({user}) => {
then the problem is that I can't call it with isAuthenticated() and might be duplication between passed param from function call and retrived state from Redux.
How can I fix it if I want to keep using "isAuthenticated()" for calling that method, without passing any param, but let Redux pass user state to that function?
This can be solved with React's Hooks API. What you are aiming for is a custom hook that will internally use useSelector from react-redux. If you don't want to use functional components, you can always opt for Higher-Order Components (HOCs)
Code Samples
Custom Hook
import { useSelector } from 'react-redux';
export function useIsAuthenticated() {
return useSelector(state => !!state.auth.userInfo);
}
export function YourComponent(props) {
const isAuthenticated = useIsAuthenticate();
// Return your react sub-tree here based on `isAuthenticated`
// instead of `isAuthenticated()` like before.
}
Higher-Order Components
import { connect } from 'react-redux';
export function withIsAuthenticated(Component) {
function mapStateToProps(state) {
return {
isAuthenticated: !!state.auth.userInfo
};
}
return connect(mapStateToProps)(function({ isAuthenticated, ...props }) {
return <Component isAuthenticated={isAuthenticated} {...props}/>;
});
}
export function YourComponent({ isAuthenticated, ...props }) {
// Return your react sub-tree here based on `isAuthenticated`
// instead of `isAuthenticated()` like before.
}
Opinion
Personally, I feel that HOCs introduce a lot more complexity than necessary, and if I'm not mistaken, this was one of the primary drivers behind the creation of the Hooks API.
I have a custom modal component:
export default ModalLoader = props=>{
const {
loading,
...attributes
} = props;
function closeModal(){
console.log('Close Modal Kepanggil coyyy!!!!!!!')
}
return(
<Modal
transparent={true}
animationType={'none'}
visible={loading}
>
<View>
<View>
<ActivityIndicator
size={'large'}
color={colors.darkRed}
/>
<CustomText style={{fontSize:24}}>Mohon Tunggu...</CustomText>
</View>
</View>
</Modal>
)
}
i want to used closeModal() in axios instance, so everytime axios get a response, i want to close modal in axios file itself not in all of my component,
let say my axios instance something like this:
AxiosHttp.interceptors.response.use((res)=>{
CustomLog(res.data, 'Interceptor')
// call closeModal of ModalLoader
ModalLoader.closeModal()
return res.data;
},(err)=>{
CustomLog(err, 'Interceptor Error')
// call closeModal of ModalLoader
ModalLoader.closeModal()
return Promise.reject(err)
})
export default AxiosHttp
is it possible to do that?
One way is to use React Context.
Create a context provider with the function you want to use to close/toggle the modal. Then in the ModalLoader (or any component of choice) use the function from that context.
./ModalContext.jsx
import React, { createContext } from 'react';
const ModalContext = createContext({
closeModal: () => {
console.log('Close Modal Kepanggil coyyy!!!!!!!');
},
});
export default ModalContext;
With the introduction of react-hooks in v16.8.0 you can use context in functional components using the useContext hook.
Axios instance
import React, { useContext } from 'react';
import { ModalContext } from './ModalContext';
const modalContext = useContext(ModalContext);
AxiosHttp.interceptors.response.use((res)=>{
CustomLog(res.data, 'Interceptor')
// call closeModal in context
modalContext.closeModal()
return res.data;
},(err)=>{
CustomLog(err, 'Interceptor Error')
// call closeModal in context
modalContext.closeModal()
return Promise.reject(err)
})
export default AxiosHttp;
See working example to play around with here: https://codepen.io/studiospindle/pen/xxxMRow
In that example there is a async function as an example which will close the modal window after three seconds. This is to mimic the Axios example. Also provided an example with a button.
a simple example on using react context as #Remi suggested
Core part is ModalContext.js. It exports the context for other components.
You can edit the state inside the provider if you need more common function/prop.
If you really need a static function to do so. You might need a manager
class ModalInstanceManager {
_defaultInstance = null;
register(_ref) {
if (!this._defaultInstance && "_id" in _ref) {
this._defaultInstance = _ref;
}
}
unregister(_ref) {
if (!!this._defaultInstance && this._defaultInstance._id === _ref._id) {
this._defaultInstance = null;
}
}
getDefault() {
return this._defaultInstance;
}
}
export default new ModalInstanceManager();
In your ModalLoader:
componentDidMount() {
ModalInstanceManager.register(this);
}
then in your static function:
ModalLoader.open/close = ()=> {
ModalInstanceManager.getDefault().open/close();
}
In a several components i have a function returning URL of user avatar:
import defaultAvatar from 'assets/images/default-pic.jpg'
class MyComponent extends Component {
userAvatar () {
const { profile } = this.props
if (profile.has_avatar) {
return profile.avatar
} else {
return defaultAvatar
}
}
}
is there a way to DRY this function between multiple components?
With Default Props
If you make an avatar component that accepts avatar as a top level property, then you can just use default props to specify the value when it's not provided.
function Avatar({ avatar }) {
return <img src={avatar} />;
}
Avatar.defaultProps = { avatar: defaultAvatar };
Then render this new component from inside your existing one.
return (
<Avatar profile={props.profile} />
);
This way you can keep everything declarative and remove the need for a has_avatar property.
As a Utility Function
But you could also just rip it straight out and fiddle the arguments so you can call it from anywhere.
function getUserAvatar(profile) {
if (profile.has_avatar) {
return profile.avatar
} else {
return defaultAvatar
}
}
Then rewrite your original code.
class MyComponent extends Component {
userAvatar () {
const { profile } = this.props
return getUserAvatar(profile);
}
}
As a Higher Order Component
It would also be possible to implement this as a higher order component.
function WithAvatar(Component) {
return function(props) {
const { profile } = props;
const avatar = getUserAvatar(profile);
return <Component avatar={avatar} {...props} />;
};
}
This would allow you to wrap any existing component with the WithAvatar component.
function Profile(props) {
const { profile, avatar } = props;
return (
<div>
<img src={avatar.src} />
<span>{profile.name}</span>
</div>
);
}
const ProfileWithAvatar = WithAvatar(Profile);
render(
<ProfileWithAvatar profile={exampleProfile} />,
document.getElementById('app')
);
Passing profile as a prop to the outer component causes WithAvatar to process it and select the correct avatar, then pass it down as a prop to the wrapped component.
If you've used the React.createClass approach you could able to use mixins to share code across components. Since you are using ES6 approach you can checkout HOCs (Higher Order Components)
Reference: https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775