If I have to set states from multiple different atoms as part of some high level action user is taking, I want all subscribing components for any of the impacted atoms to be re-rendered only once when the entire states are set as a batch. Is this possible with Recoil?
Recoil batches the state updates by default (this is also true for React state itself). If you don't want to batch the updates code wise, you could use the useRecoilCallback hook, something like this:
const Component = () => {
const batchUpdates = useRecoilCallback(({set}) => (valueA, valueB) => {
set(atomA, valueA);
set(atomB, valueB);
}, []);
return (
<button onClick={() => batchUpdates(someValueA, someValueB)}>Batch Updates</button>
);
};
Related
I probably misunderstood something very basic here.
I want to run a method in my child component whenever the state "oranges" in my parent component changes.
My Child component looks like this:
const [someText, setSomeText] = useState(props.oranges);
const consoleThis= () => {
if (someText == true){
console.log("win!")
}else{
return
}
};
useEffect(() => {
consoleThis();
}, [someText]);
This is the code in my parent component
...
const [apples, setApples] = useState(false);
<child
oranges ={apples}
/>
...
<Pressable
onPress={() => {
setApples(true);
}
>
Any ideas why I don't get my "win!" console logged?
useState(initial) doesn't reset the state each time the value passed to it changes - it is only used for setting the initial state of the value. Passing a new prop value will not update the state variable value - the state value becomes its own copy of the initial value that is managed independently of the prop value. In general, when you're lifting the state up, you don't want to also be keeping state in your child component. Instead, just drive the child component off of the state value passed via props.
You probably want something that responds to changes in the orange prop, like this:
function ChildComponent({orange}) {
useEffect(() => {
if (orange) {
console.log("win!")
}
}, [orange])
};
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 trying to pass the result of the button pressed to a Redux state. The user has a choice between "No" 'and "Yes"
I want to add it to the state and then navigate the user to the next screen. Below is how I am declaring the state.
const [state, setState ] = useState ({
isDisabled: true,
showAnswer: false,
uuid: '',
})
const [value, onChange] = useState('')
const [codeValue, onCodeChange] = useState('')
state = {
animation: new Animated.Value(0),
};
const handleOnChange = (name, value) => {
setState({
...state,
[name] : value,
});
console.log([name],':',value)
};
This is the button.
<Button
title={"No"}
style = {GettingToKnowYouStyle.choices}
onPress={() => navigation.navigate ('Employment')}
>
No
</Button>
I know I am doing a lot wrong here please help.
careful, you're mixing things up here! useState refers to the component state, not the redux state.
you need a redux state (aka reducer), at least one action to dispatch and something to handle side effects (I recommend either rxjs and redux-observable or saga).
the hook into redux then is called useDispatch, as from the docs.
I suggest the following: Dispatch action 1 with the answer, update the state. after successful state reduction dispatch action 2 handling the route change. try reading the docs (basic tutorial is a good point to get started) and come back with detailed questions if you have some.
You should use useDispatch() to change the redux state.
I have the following code:
export default class Testing extends Component {
state = ({
data: []
});
componentDidMount() {
this.setState({
data: this.props.values
});
console.log(this.state.posts); //prints empty but if I do
console.log(this.props.values); //prints the array correctly
}
Where is the error since I can print the props not the state?
Thanks
You're not storing anything in this.state.posts. Your initial state only contains data.
Also when you construct your initial state you should do it like this:
state = {
data: []
}
You do not need the ( ) around it.
If you are wanting to print a value from state as soon as you have stored it you must use the callback functionality of state. This is due to the fact that setState is asynchronous and takes time to set the value. Currently you are trying to read the value before it has been set, use the callback functionality like below.
this.setState({
data: this.props.values
}, () => console.log(this.state.data));
Here are some great articles on setState.
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
you don't need the ( ) when you set the initial state because it is an object.
export default class Testing extends Component {
state = { //remove (
data: []
}; //remove )
Also worth noting, setState is an async function. You will not be able to getState directly after setState.
In order to get the state right away, you would provide a callback to setState() https://reactjs.org/docs/react-component.html#setstate
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.
*/