Passing values between React components with useState - react-hooks

I use a React component with Apollo client as function component. The function body of my main search component looks like this:
function SearchContainer(props) {
const [begTime, setBegTime] = useState('')
const runSearch(begin_time) {
console.log('begin_time: ', begin_time) <== The printed value is Ok!
setBegTime(begin_time) <=== Use hook to set the value of begTime
console.log('begTime: ', begTime) <=== The output shows no change in begTime. Why?
}
return (
// Use SearchForm to get the search parameters.
<SearchForm
handleSearch={runSearch} <== Use SearchForm to get begin_time back into this component.
/>
// Provide the parameters from SearchForm and run the useQuery from Apollo using this parameters.
<SearchResults
begTime={begTime}
/>
)
}
The SearchForm is just a usual form as a React function component with useState hooks and calls on form submit the hanldeSearch function.
function SearchForm({handleSearch}) { <== handleSearch function from the parent component.
const handleSubmit = (begin_time) => {
handleSearch(begin_time) <== This call works and the value will be provided to the parent.
}
...
}
My idea of this code is to create 2 independent components. One component (SearchForm) should get the parameters. The other component (SearchResults) will get this parameters as arguments, run the query using useQuery and show the results.
But the useState hook does not work very well in my case. Interesting enough if I call the corresponding search form twice, I can see in the runSearch function, that the begTime has got the previous search values and not the initial value. So apparently the useState kind of works, but I want to run the search with the current values and not with the previous ones.
Is it possible at all to create such components with React hooks? It's my first big test with hooks instead of classes.
Thanks in advance
Andrej

For your question
const runSearch(begin_time) {
console.log('begin_time: ', begin_time)
setBegTime(begin_time)
console.log('begTime: ', begTime) <=== The output shows no change in begTime. Why?
}
The output shows no change in begTime. Why?
As stated in docs when we set state that is async function.
By that i mean your code will keep running and to set state react will start child process on another thread. And when its complete it will pass result to main thread. ( that you can c in useEffect or componentDidUpdate for early version).
So Main points are
at setBegTime(begin_time) async process is started
at main thread code will not wait for it
So next statement that is console.log('begTime: ', begTime) is processed and u saw no changes as in actual its value is not updated yet. React is still updating value.
Updating process is async because React dont want to main thread to wait for heavy work ( updating state is heavy process ) as if it wait then webpage will not respond untill it is completed. So it instead run that process on another thread.
For second one
you can try this
function SearchContainer(props) {
const [begTime, setBegTime] = useState('')
const [response,setResponse] = useState({})
useEffect(()=>{
const runSearch = (begin_time) {
setBegTime(begin_time)
}
},[begin_time])
// u can rather define working of handleSubmit in parent component and <br/>
// store its output in state and then pass it to another component
const handleSubmit = (begin_time) => {
resp = handleSearch(begin_time)
setResponse(resp)
}
return (
// Use SearchForm to get the search parameters.
<SearchForm
handleSearch={()=>handleSubmit()}
/>
// Provide the parameters from SearchForm and run the useQuery from Apollo using this parameters.
<SearchResults
begTime={begTime}
response={response}
/>
)
}

Related

access to redux last state within listener function

i have a react functional component with a signal callback
how can i access to last redux state with in it:
const Demo = ()=>{
const data = useSelectore(s=>s.items);
const dispatch= useDispatch();
const listener = (x)=>{
console.log(data);//here i need access to last state
}
signalObj.on("EventName",listener);
//== init items
useEffect(()=>{
//=== after api calls
dispatch(initItems(items));
},[])
}
in above example items state will be init after first render with api call,
should i store items in use ref, is it the way?
the best approach i found was using redux thunk,
in that you can get last state and even have async function calls

How do you get data back from a react-redux store?

Using React-Redux
I have a select list that when the user chooses one of the options, a item is created and placed in the database (if it matters, the reason its a select box is that there are multiple variations of the same item and what variation is important).
This is working correctly.
My problem is that I am not sure how I can get the id of the new item out of the redux store.
And just for chuckles, the prior developer set all this up with sagas. So I am still coming up to speed on how it all works together.
So when the select box is checked, the function checkFunction is called that calls the function createItem in the saga file. These functions are below:
in Repositories.jsx
checkFunction = (data) => {
const {createItem} = this.props;
// data holds the info that we need to send to the action
const created = createItem(data);
// once data comes back, redirect the user ot the details of the created item
// need id of created item
// navigateTo(`/item-details/${created.id}`);
}
in Repositories.saga.js
export function* createItem(action) {
try {
const {payload: newItem} = action;
// call api to create item
const created = yield call(requestAPI.post, ITEMS_URL, newItem);
// send message that its been done
yield put(actions.repositories.createItem.ok(created));
// send message to refresh item list
yield put(actions.inventories.fetchItems.start());
} catch (e) {
yield put(actions.repositories.createItem.fail(e));
}
}
I don't understand how to return the id of the created item once its created. I feel like I am missing something basic here. Can anyone point me in the right direction?
Actually getting data from saga back to react component is not trivial. There are multiple approaches to do what you need although each has its downside.
1. Call navigateTo in the saga.
export function* createItem(action) {
...
const created = yield call(requestAPI.post, ITEMS_URL, newItem);
yield call(navigateTo, `/item-details/${created.id}`)
}
This would be my recommended solution if you can get the navigateTo function into the saga. Navigation is a side effect and sagas are there to deal with side effects. Make sure to use the call effect, changing the url by directly calling the function can lead to some issues.
2. Store the latest created item id in redux store
In your reducer, when action actions.repositories.createItem.ok(created) is dispatched, store the created item info and then send the latest created item to the component. Finally you can use componentDidUpdate or useEffect to call navigateTo when the prop changes.
render() {
const created = Redux.useSelector(state => state.created);
useEffect(() => navigateTo(`/item-details/${created.id}`), [created])
...
}
This has the disadvantage that the component will rerender becuase of the changed created value.
3. Send callback in the createItem action
You can put a function into your action and then call it from the saga and essentially using it as a callback.
Component:
checkFunction = (data) => {
const {createItem} = this.props;
// data holds the info that we need to send to the action
const created = createItem(data, (created) => {
navigateTo(`/item-details/${created.id}`);
});
}
Saga:
export function* createItem(action) {
...
const created = yield call(requestAPI.post, ITEMS_URL, newItem);
action.callback(created)
...
}
The problem with this approach is that functions are not serializable and so you ideally should avoid them in your actions. Also, technically, there could be multiple sagas handling the same action and then it gets kind of confusing who should call the callback.

How to handle long async operations in Next.js to avoid slow page loading?

When using next-redux-wrapper how do I start a long asynchronous task so that it only runs on the client? I don’t want to use await on the server side since it would delay the initial page load. I’d rather set a loading flag as the task starts and show a loading indicator until it completes.
Let’s say my async operation looks like this:
function async takesLong(store) {
store.dispatch({type: “LOADING”, payload: true});
const result = await longOperation();
store.dispatch({type: “SETDATA”}, payload: data);
return store.dispatch({type: “LOADING”, payload: false});
}
I can call this in my Next page’s getInitialProps function like this:
MyPage.getInitialProps = async ({ store, isServer }) => {
const loader = takesLong(store)
if (isServer) await loader; // <-- will delay client response
return {
someprop: "some value"
};
};
This works well if the page loads on the client side. The operation starts, and my page can display a loading-spinner until the operation completes. But when started on the server side I have a long delay before the page displays at all. I’ve tried a number of approaches but can’t find one that works properly:
Starting the process on the server and not using await renders the page without the results being written to the store, so it has only set “loading” to true in the store and it never gets the data.
Passing store in my props doesn’t work - it ends up being an empty object ({ }) in the client.
Trying to run it inside my component doesn’t seem to work for a few reasons:
a) I don’t have the store object accessible in the React Component (only in getInitialProps which won’t get called on the client).
b) Even if I use actions instead of store.dispatch in the Component, when can I call it safely? I can’t do it during render since it will change the Redux state, and componentWillReceiveProps won’t get called before the first client-side render
Is there a well defined pattern for deferring a long operation to the client-side when using Next?
Do your long async task on componentDidMount, it will run only on client side.
React in SSR not runs componentDidMount lifecycle hook.
Using bound actions during componentDidMount works. Thanks to #eenagy for the suggestion. Doing things in this order seems to do what is needed:
import { bindActionCreators } from "redux";
import { setLoading, setError, setData } from "../actions";
componentDidMount() {
if (!this.props.haveData && !this.props.loading && !this.props.error) {
this.props.setLoading(true);
loadSomeData() // <-- this takes a while to complete
.then( data => {
this.props.setData(data);
this.props.setLoading(false);
})
.catch( err => {
this.props.setError(err);
this.props.setLoading(false);
});
}
}
render() {
if (this.props.loading) return (<Loading/>);
return (/*regular page*/);
}
export const mapDispatchToProps = dispatch => {
return bindActionCreators({ setLoading, setError, setData }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(Component);
This way if the initial data is not already loaded (say by another page) it will get kicked off when the
component mounts and run asynchronously until the operation completes and
calls the actions in redux to cause the page to re-render.

console.log(..) of 'reselect' selector displays f()

I'm using react-boilerplate that uses reselect for reading the redux state and maps it to props. I am trying to read the redux state outside a react component (say in custom function defined in myUtil.js).
There is selector in selectors.js:
const makeSelectLoading = () => createSelector(selectGlobal, globalState => globalState.get('loading'));
I import makeSelectLoading into myUtil.js and try to print the value using console.log(makeSelectLoading()). I am expecting the value of the Loading state variable, instead I'm getting the below:
myUtil.js?d403:14 ƒ () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments);
}
Should I expect this to work? If not, any ideas where I'm going wrong? Thanks!
makeSelectLoading is not a selector itself but a selector factory: a function which returns an actual selector.
In order to use it you should get a selector instance from it and then call the returned instance with the expected arguments.
const getSelectLoading = makeSelectLoading();
const loadingState = getSelectLoading(state);

reducer goes into a loop when returning a new array created from state

I am using react-redux 5.0.6 and have a reducer with the following code:
export default (state = [], action) => {
switch(action.type) {
case 'ADD_ENGAGEMENT':
let newArr = state.slice();
newArr.push(action.payload);
return newArr;
case 'UPDATE_ENGAGEMENT':
console.info('UPDATE_ENGAGEMENT')
return state.slice();
// return state;
default:
return state;
}}
The issue occurs within the 'UPDATE_ENGAGEMENT' case -- the actual logic has been removed and replaced with the simplest example to demonstrate the problem.
When a new array created from state via state.slice() is returned, a loop is triggered, causing the action to be dispatched until an 'Uncaught RangeError: Maximum call stack size exceeded' error is raised. Screenshot of the browser console during the issue's occurrence
The issue is not limited to 'slice()' and occurs whenever an array containing any element of state is returned e.g. return [state[0]].
When the original state is returned, the issue does not occur.
I am completely baffled by this behavior and cannot fathom how anything in my application could be causing it. Any insight would be immensely appreciated.
To provide some additional context, below is the code involved in the action's being dispatched:
componentWillReceiveProps(newProps) {
let engagementTemplateData = newProps.selectedEngagementTemplate;
let engagements = newProps.engagements;
if (engagementTemplateData && engagementTemplateData.engagementUuid === this.props.uuid) {
let template = engagementTemplateData.template;
this.updateEngagementTemplate(template);
}
}
updateEngagementTemplate(template) {
let url = `/engagements/${this.props.uuid}`;
let requestHelper = new AjaxRequestHelper(url);
let data = {template_uuid: template.uuid};
this.props.updateEngagement({uuid: this.props.uuid, template: template});
// requestHelper.put(data, response => {
// this.props.updateEngagement({uuid: this.props.uuid, template: template});
// });
}
Basically, the function which triggers the action is called in componentWillReceiveProps as a result of another action. However, I am not sure how helpful this information is, since the reducer itself appears to be working properly when responding to the action -- it's just that something strange is happening with the state, which prevents its elements from being returned.
From the sounds of it (and from the react callstack), I imagine the array changing (by reference) in the store is being picked up by a react component props, which in its should/did update logic is calling that action without a guard. This is often a mistake when calling actions or setState from componentDidMount/Update -
It works when the original state is returned as the reference is the same so React does not continue with its update logic, and hence call your code that publishes the action
Consider this pure component that will cause an endless loop with your reducer code...
export interface IMyProps {
myArray: any[],
updateEngagementAction: () => void
}
export class EndlessLoopFromArrayPropComponent extends React.PureComponent<IMyProps> {
// PureComponent compares props by reference only
// so will cause update if this.props.myArray reference has changed in store (ie from slice())
render() {
// blahblah...
}
componentDidUpdate() {
// this will call action after every update
// as every time this action is called it passes in a new reference this.props.myArray to this component
// so react will update this component again, causing the action to be called again
// ... endless loop
this.props.updateEngagementAction()
}
}
Your implementation will differ of course but this will be the principal that is causing it to happen, so you need to add a guard condition in whatever code path leads to your action being called.
For the code above you would need to check an escape condition before sending the action OR implement shouldComponentUpdate or similar to do deeper prop comparison to guard against unnecessary updates, and hence it would not reach that action code in the componentDidUpdate method
EDIT This was written before the react code was added to question. Here I refer to the action being called without guard in componentDidUpdate however the same applies when called in any of the other lifecycle methods triggered by a prop change, in this case componentWillRecieveProps. To be fair it did have a guard already, but never returned false as a more in-depth props check was needed, so caused a loop to occur via willreceive -> true -> action -> reducer -> willreceive -> true ........

Resources