How to pass value and run function between two siblings component. I am using React , Hooks
Items list exist in CreateArea.jsx component, which I want to delete an item from.
The delete button exist in Note.jsx component
There are many ways of doing this.. but I find Context API to work great if the app is not too complicated. Its a simple process:
Configure a provider
Configure a consumer and read/write data
Configure a provider
//App.js
const [data, setData] = useState()
let contextValue = {
data: data,
handleChange: (d) => setData(d)
}
return (
<RootContext.Provider value ={contextValue}>
<AppContainer/>
</RootContext.Provider>
)
Configure consumer
//SomeComponent.js - you can use consumer in any number of components
return(
<RootContext.Consumer>
{(handleChange, data) => {
handleChange({key: value})
<Text>{JSON.stringify(data)}</Text>
}}
</RootContext.Consumer>
)
More information can be found here
Related
I am working on an Expo app, that uses authentication with JWTs that are stored using SecureStore. The top-level component initially displays a Login screen but also checks if a JWT exists in SecureStore. If a JWT exists the app verifies that it is still valid and if it is the app takes the user to the Landing page, from which the user can navigate to many other pages that fetch and display all sorts of data.
I am looking for a way to handle an expired JWT, so that if the user is navigating to a page that tries to fetch some data and the API response returns e.g. 401 the app should take the user back to the login screen.
The top component uses this state to decide what page to show
const [appState, setAppState] = useState(appStates.STARTUP_SETUP);
with valid values for appState being:
const appStates = {
STARTUP_SETUP: "StartupSetup", // Initial state, during which the app checks for an existing valid JWT
SHOW_LOGIN_SCREEN: "ShowLoginScreen", // There is no stored JWT, app shows login screen
SHOW_SIGNUP_SCREEN: "ShowSignupScreen", // There is no stored JWT, app shows signup screen
SHOW_SIGNUP_CONFIRMATION_SCREEN: "ShowSignupConfirmationScreen", // There is no stored JWT, user just registered and is prompted to check their email for verification
USER_LOGGED_IN: "UserLoggedIn", // user logged in, JWT is stored
}
The component uses appState in the following way:
if (appState === appStates.USER_LOGGED_IN) {
comp = <Landing onLogout={logUserOut} />;
} else if (appState === appStates.SHOW_LOGIN_SCREEN) {
comp = <Login onSuccessfulLogin={updateUser} onSignup={() => setAppState(appStates.SHOW_SIGNUP_SCREEN)} />;
} else if (appState === appStates.SHOW_SIGNUP_SCREEN) {
comp = <Signup onSuccessfulSignup={() => setAppState(appStates.SHOW_SIGNUP_CONFIRMATION_SCREEN)} onLogin={() => setAppState(appStates.SHOW_LOGIN_SCREEN)} />;
} else if (appState === appStates.SHOW_SIGNUP_CONFIRMATION_SCREEN) {
comp = <SignupConfirmation onLogin={() => setAppState(appStates.SHOW_LOGIN_SCREEN)} />
}
With Landing having its own tree of child components.
I am basically looking for a way to be able to do
setAppState(appStates.SHOW_LOGIN_SCREEN)
from anywhere in my app.
One possibility would be to pass that hook from the top component to Landing and every child that has but I feel there should be an easier way.
Edit - Solution
In my top component I created a method that deletes the token and sets the appState to SHOW_LOGIN_SCREEN
const deleteStoredToken = () => {
deleteToken();
setAppState(appStates.SHOW_LOGIN_SCREEN);
}
const appStateValue = { deleteStoredToken };
I then created a context (with a default value)
export const AppContext = React.createContext({
deleteStoredToken: () => { }
});
I used this context to wrap the children of the top component by providing appStateValue as its value
return (
...
<AppContext.Provider value={appStateValue}>
{children}
</AppContext.Provider>
...
)
And now in any child component I can do
const { deleteStoredToken } = useContext(AppContext);
and use deleteStoredToken()
It sounds like you're looking for a state management solution. A React Context is a low-effort solution to this - be sure to read the caveats in the documentation though.
There are tens if not hundreds of libraries that offer different ways to do this. The most popular are probably Redux and Mobx; these offer dedicated ways to share complex state between components. However, if it's only for one value, and it won't be updated frequently, a Context is perfectly fine.
I have these components without any recoil's hooks.
const C = () => {
console.log('---->C')
return <Text>C</Text>
}
const B = () => {
console.log('--->B')
return <>
<Text>B</Text>
<C/>
</>
}
const A = () => {
console.log('-->A')
return <>
<Text>A</Text>
<B/>
</>
}
const App = () => {
console.log('->App')
return (
<RecoilRoot>
<A />
</RecoilRoot>
);
};
when i run the app in the console show up the spected logs:
LOG ->App
LOG -->A
LOG --->B
LOG ---->C
now im gonna use recoil hooks to mutate and access the atom state
import { atom, useSetRecoilState, useRecoilState, useRecoilValue, RecoilRoot } from "recoil";
const atomTest = atom({
key: "abcatomTest",
default: "A"
})
const C = () => {
console.log('---->C')
const [value, set] = useRecoilState(atomTest)
return <>
<Text>C</Text>
</>
}
const B = () => {
console.log('--->B')
const set = useSetRecoilState(atomTest)
return <>
<Text>B</Text>
<C/>
</>
}
const A = () => {
console.log('-->A')
const value = useRecoilValue(atomTest)
return <>
<Text>A</Text>
<B/>
</>
}
const App = () => {
console.log('->App')
return (
<RecoilRoot>
<A />
</RecoilRoot>
);
};
i dont even using the values and functions returned from useRecoilValue, useSetRecoilState, useRecoilState, if i use it, it works properly, BUT in the very first render the logs are:
LOG ->App
LOG -->A
LOG -->A
LOG ->App
LOG --->B
LOG ---->C
LOG ---->C
LOG --->B
LOG -->A
LOG ->App
LOG -->A
LOG --->B
LOG ---->C
why is recoil forcing the re-render of multiple components including root, im not mutating the state at all, and in the App component there is not dependency to any state neither!
First of all: React executing a function does not mean that the component actually re-renders.
React has a commit and a rendering phase. During the commit phase React goes through changes and calls the child components, checking if there is anything new to render. During the rendering phase React checks if there are components that actually have to re-render. If the outputs, hook states and props are identical there will be no re-render, even though React previously called your function component. This is why you see all the logs. You are not checking for re-renders with that, but for function executions.
Your App component actually has dependencies to state, since it renders the RecoilRoot component. When that component changes, React will enter the commit phase again and go through all children, to see if there are changes.
Since every component uses a hook that references the atomTest atom, Recoil has to subscribe to that atom for that components. So Recoil as well as React have to look for changes through the tree.
If you check with the Profiler of the React Developer Tools you'll see that there are no actual re-renders, since your components didn't change any output.
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 am using react-native-navigation v2 and every component needs to be registered into the navigation by calling registerComponent(). However, I found myself having 500 lines of code where I register every component of my app using the same registerComponent structure with the only difference of using different jsx tag for every component I register, like so:
import ItemsList from './src/components/ItemsList';
import { Navigation } from "react-native-navigation";
import { Provider } from "react-redux";
import reduxStore from "./src/store";
Navigation.registerComponent(
"app.ItemsList",
() => props => (
<Provider store={reduxStore}>
<ItemsList {...props} />
</Provider>
),
() => ItemsList
);
+++ 35 more components almost exactly just like this one
Now, in order to reduce that huge amount of identical code, I've decided to write an IIFE that maps through an array of objects(components) that look like:
[...,
{
name: "ItemsList",
component: ItemsList
},
...]
then calls registerComponent on every item and returns the JSX I need, like so:
(function componentsRegistration() {
return components.map(({ name, component }) => {
const Tag = name;
Navigation.registerComponent(
`app.${name}`,
() => props => (
<Provider store={reduxStore}>
<Tag {...props} />
</Provider>
),
() => component
);
});
})()
After this specific manipulation, my app doesn't render anymore. Instead it throws the "Invariant Violation: View config is not found for name ItemsList". I think I've made all of this respecting the React commandments (capital letter jsx tag, etc.), however can't get it to work. If anyone could, please help.
[SOLVED] I was getting an error because I was trying to pass a string with the component name instead of the component itself.
The right way to do it would be:
const Tag = component;
instead of:
const Tag = name;
In Redux I can easily subscribe to store changes with
store.subscribe(() => my handler goes here)
But what if my store is full of different objects and in a particular place in my app I want to subscribe to changes made only in a specific object in the store?
There is no way to subscribe to part of the store when using subscribe directly, but as the creator of Redux says himself - don't use subscribe directly! For the data flow of a Redux app to really work, you will want one component that wraps your entire app. This component will subscribe to your store. The rest of your components will be children to this wrapper component and will only get the parts of the state that they need.
If you are using Redux with React then there is good news - the official react-redux package takes care of this for you! It provides that wrapper component, called a <Provider />. You will then have at least one "smart component" that listens to state changes passed down by the Provider from the store. You can specify which parts of the state it should listen to, and those pieces of the state will be passed down as props to that component (and then of course, it can pass those down to its own children). You can specify that by using the connect() function on your "smart" component and using the mapStateToPropsfunction as a first parameter. To recap:
Wrap root component with Provider component that subscribes to store changes
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Now any child of <App /> that is wrapped with connect() will be a "smart" component. You can pass in mapStateToProps to pick certain parts of the state and give it those as props.
const mapStateToProps = (state) => {
return {
somethingFromStore: state.somethingFromStore
}
}
class ChildOfApp extends Component {
render() {
return <div>{this.props.somethingFromStore}</div>
}
}
//wrap App in connect and pass in mapStateToProps
export default connect(mapStateToProps)(ChildOfApp)
Obviously <App /> can have many children and you can pick and choose which parts of the state the mapStateToProps should listen to for each of its children. I'd suggest reading the docs on usage with React to get a better understanding of this flow.
Redux only offers a single generic way to know when the store has updated: the subscribe method. Callbacks to subscribe do not get any info on what might have changed, as the subscribe API is deliberately low-level, and simply runs each callback with no arguments. All you know is that the store has updated in some way.
Because of that, someone has to write specific logic to compare old state vs new state, and see if anything has changed. You could handle this by using React-Redux, specifying a mapStateToProps function for your component, implementing componentWillReceiveProps in your component, and checking to see if specific props from the store have changed.
There are also a couple addon libraries that try to handle this case: https://github.com/ashaffer/redux-subscribe and https://github.com/jprichardson/redux-watch . Both basically let you specify a specific portion of the state to look at, using different approaches.
In addition to what Andy Noelker said, mapStateToProps not only passes part of the state properly down your component tree, it also subscribes to changes made directly in these subscribed portions of the state.
It is true that every mapStateToProp function you bind to the store gets called each time any part of the state is changed, but the result of the call gets shallow compared to the previous call - if top level keys you subscribed onto did not change (the reference stays the same). Then mapStateToProps would not call re-render. So if you want the concept to work, you have to keep mapStateToProps simple, no merging, type changing or anything, they should simply pass down parts of the state.
If you want to reduce the data from the state when subscribing, for example you had list data in the state and you want to convert it to object with ids as keys, or you want to join multiple states into data structures, you should combine mapStateToProps with createSelector from reselect library, by doing all these modifications inside selector. Selectors are pure functions that reduce and cache state chunks passed in as input and if input did not change - they return exactly the same reference they did on the last call - without performing the reduction.
Created a hack to help understand the subscribers can be differentiated based on store data, with multiple store capability.
//import { createStore } from 'redux';
let createStore = require('redux').createStore;
let combineReducers = require('redux').combineReducers;
/**
* This is a reducer, a pure function with (state, action) => state signature.
* It describes how an action transforms the state into the next state.
*
* The shape of the state is up to you: it can be a primitive, an array, an object,
* or even an Immutable.js data structure. The only important part is that you should
* not mutate the state object, but return a new object if the state changes.
*
* In this example, we use a `switch` statement and strings, but you can use a helper that
* follows a different convention (such as function maps) if it makes sense for your
* project.
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function messanger(state = 'Mr, khazi', action) {
switch(action.type) {
case 'WELCOME':
return 'Hello, Mr Khazi';
case 'BYE':
return 'Bye, Mr Khazi';
case 'INCREMENT':
return 'Incremented khazi';
default:
return state;
}
};
function latestAction(state = null, action) {
switch(action.type) {
case 'WELCOME':
return '$messanger';
case 'BYE':
return '$messanger';
case 'INCREMENT':
return '$messanger, $counter';
case 'DECREMENT':
return '$counter';
default:
return state;
}
};
let reducers = {
counts: counter,
message: messanger,
action: latestAction
};
let store = createStore(
combineReducers(reducers, latestAction)
);
// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
//let store = createStore(counter)
// You can use subscribe() to update the UI in response to state changes.
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
// However it can also be handy to persist the current state in the localStorage.
store.subscribe(() => {
if(store.getState().action.indexOf('messanger') !== -1) {
console.log('subscribed for counter actions', store.getState());
}
});
store.subscribe(() => {
if (store.getState().action.indexOf('counter') !== -1) {
console.log('subscribed for messanger actions', store.getState());
}
});
// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
console.log('----------------Action with both subscriber-------------');
store.dispatch({ type: 'INCREMENT' });
console.log('---------------Action with counter subscriber-----------');
store.dispatch({ type: 'DECREMENT' });
console.log('---------------Action with messenger subscriber---------');
store.dispatch({ type: 'WELCOME' });
/*
every reducer will execute on each action.
*/