I have a fairly standard React Native functional component that uses a selector to retrieve a view model from my Redux store(Using redux-observable for async actions), and render it on changes, pseudo code below.
I'm implementing deeplinking, and my problem is that the store code works (I parse the URL and dispatch actions to my Redux store depending on route, navigate to the page, and the data should show) but it does not seem to re-render after an asynchronous action.
From what I can see, the action is dispatching in the background, it makes the async call, dispatches a success action and the store is updated successfully, the component simply does not re-render.
Any ideas? It seems to be an issue with the useSelector hook not firing, on that specific component that is loaded, as navigating away and using other pages works fine.
The user flow is:
User presses a notification which contains a deeplink
This navigates to a specific page
Data is loaded in the background
Upon successful fetch, the page should re-render
function MyPage() {
const dispatch = useDispatch();
const vm = useSelector(selectMyPageVM);
return (<View><Text>{vm.someProp}</Text></View>);
}
Related
I am trying to get my head around a scenario where I am dispatching a synchronous redux action (using createAction of typesafe-actions) and soon after that making a network call that relies on updated props from the store.
Scenario:
Inside clearFilters handler function (handler function invoked on click of clear filters button), I am dispatching a synchronous action and then making a network call as below:
clearFilters = (): void => {
this.props.resetFilters(); //action dispatched
this.refreshData; //network call
};
Inside the refreshData function, my component expects updated filter props and based on it, it creates a searchCondition to be passed to the list api call as payload.
refreshData = (): void => {
const { listData, filters } = this.props; //get the filters prop
//Expected filters to be updated from the store post dispatch of action
const SearchCondition: SearchCondition = createSearchConditions(filters);
listData({
SearchCondition,
MaxResults: this.maxRecordsCount,
SortFields: this.getSortFields(),
}),
);
};
My component is subscribed to the filters prop using mapStateToProps:
const mapStateToProps = (state: RootState) => ({
filters: state.common.filter.filters,
});
Given that is the state of the problem I am facing, I tried to debug what happens by placing debug points in the code:
When the action is dispatched (inside clearFilters function)
Inside the reducer, where updated state is returned.
When the network call is invoked (inside clearFilters function)
In the refreshData call.
After reducer returns updated state, as per the debugging knowledge, store did not send the updated props right away. Rather, the control goes back to the next line i.e. this.refreshData() which make network call with old filters data. Only after the clearFilters function call finishes, in my componentDidUpdate, i can see that props update happen.
Does that signifies redux state change back to the store and eventually subscribed prop updates happen in an ASYNC way? If so, how does it happen? Does store sending the updated props executes in the main thread?
Any pointers/documentation would be really helpful.
The dispatch is synchronous, and the queueing of the React updates is synchronous. However, React will not re-render that component until after this whole event processing is completed, and this.props will not be updated until after that render happens. So, no, you cannot access this.props right after dispatching an action and expect that it has been updated. That will never be true.
I would suggest reading these posts that go into extensive detail on both React and React-Redux:
A (Mostly) Complete Guide to React Rendering Behavior
The History and Implementation of React-Redux
I'm having a lot of trouble learning to properly load data into state in my todo app.
I have a next.js page component pages/index.tsx where I load data from my API via getServerSideProps and return it as a page prop called tasksData.
The tasksData is being returned properly and I can access them in my page component just fine via prop destructuring: const Home = ({ tasksData }: Home) => { }
I also have a React Context provider in _app.tsx called BoardProvider. This stores state for my task board, and employs useReducer() from the React Context API to update this state in context consumers, such as pages/index.tsx.
The challenge I am facing is how to make my UI's "source of truth" the state stored in my context provider (eg. const { { tasks }, dispatch } = useBoard();, rather than the page page props returned from my API (eg. the tasksData prop).
One approach I considered was to simply load the data in getServerSideProps and then set the state via a dispatched action in a useEffect hook:
useEffect(() => {
// On first render only, set the Context Provider state with data from my page props.
dispatch({ type: TaskAction.SET_TASKS, payload: tasksData });
});
However, this doesn't seem to be working because sometimes tasksData is undefined, presumably because Next.js has not yet made it available on page mount.
Another suggestion I heard was to fetch the data and pass it as pageProps to my Context Provider in _app.tsx. I believe this means using getInitialProps() in _app.tsx so that my provider's initial state is populated by my API. However, this disabled static optimization and other useful features.
Can anyone help me out with some pseudocode, documentation, or examples of how to use getServerSideProps in combination with React Context API?
Couple of points:
getServerSideProps should be invoked before the page is even rendered. So theoretically your tasksData is undefined is a bug! You can't have a server data to be unavailable unless you really really intend to have that happen in the first place.
Assuming getServerSideProps is always returning the right data, but you want to use your own data to override it. In your context, you can have this logic.
const Home = ({ tasksData }) => {
const value = { tasksData: {
// let me override it
}}
return (
<Context.Provider value={value}>
...
<Context.Provider>
)
}
If you have the context provided under a page, the above code is all you need. But if your context is provided in a root (parent of a page), you can still add the above code to re-provide the same context again with overridden value. Because this is how a context is designed, read https://javascript.plainenglish.io/react-context-is-a-global-variable-b4b049812028 for more info.
Is there a way to update the Redux store before the ReactDOM.render(...) is runned?
Something like store.dispatch(...).then(() => ReactDOM.render(...))
Or a way to just replace the entire "state" of the store?
I am making an API with React components that is used inside another application with another framework, Dojo. Because of this we are using ReactDOM.render(...) and ReactDOM.unmountCompoentAtNode(...) when the component should appear/disappear.
So in the API, I am reusing the redux store, since it is not deleted from the memory/RAM even though a React component is unmounted via ReactDOM.unmountComponentAtNode(...). If I did not reuse the store, but made a new one every time the React component should render, the actions where "fetched by the Redux store" more and more times. Because of this I would like to ensure that I get to refresh the Redux store before I mount the component again.
Given that the rendered state should depend on the redux store, have you seen redux-thunk ?
One way is to have a show a spinner when the API endpoint is being reached out to.
After setting the state from the API response, go ahead and show the real component.
You are approaching the solution in the wrong way. For this use case, we have life cycle methods in components
ReactDOM.render(<App />, document.getElementById('root'))
In this code I am rendering App component, to solve your problem I'll use componentDidMount life cycle method of App and from there I'll dispatch the API call.
class App {
componentDidMount() {
dispatch(MY_ACTION)
}
render() {
return (<h1>dummy</h1>)
}
}
For hooks based functional component, you need to use useEfect
const App = () => {
useEffect(() => {
dispatch(MY_ACTION)
}, [])
return (<h1>dummy</h1>)
}
Having react & redux application that using asynchronous calls via Axios, I need to cancel such request that are still in progress once the user navigate to other page or anything else. Being new to react I came up with an idea to keep the axios cancelToken in redux state and cancel it in componentWillUnmount() of the react component.
example:
componentWillUnmount() {
const (isLoading, cancelToken} = this.props;
if (isLoading) {
cancelToken.cancel("Abort request");
}
}
Are there better patterns or solution to this?
EDIT:
Async call is being fired in redux-thunk i.e. action
It depends on where you have implemented your fetch.
If you have placed it within your component, you can fire the cancel event in the componentWillUnmount method.
If you have placed it in a middleware such as redux-saga, you can create and use the cancel token then and there itself.
If it is within an action creator you can try the method suggested here
I'm building a Front End React JS app that consumes from a back-end API, and I currently working on a basic login form. I'm using Redux and React-Router 4
Here is how it currently works:
User Fills out Login Form: User Inputs are stored as States on the LoginForm Component
Upon Submission, a LoginUser Action is dispatched: LoginForm user input states are submitted to the LoginUser Action. This action is responsible for submitting an AJAX request to the server. Upon successful response, the Redux Store is updated with all the relevant user information
In theory, when my AJAX request successfully receive user information after authentication and then subsequently updates the redux store, the user should be redirected to their dashboard page. Ideally this code should live in my LoginUser Action as a callback to my AJAX request.
My question is this: How do I programmatically redirect the user to another route in my action file? Is there a way to do this with React-Redux 4?
EDIT
So decided to keep the redirect state in my LoginForm component and create a function on the LoginForm component (redirectToDashboard())--this function would then be sent as a parameter to the fetchUser AJAX request that is sitting in my action file. I really feel like this is the best practice at the moment. Let me know if anyone else has other thoughts!
Special thanks to #alexander-borodin for giving me the guidance!
You can find a solution here
First of all, you should provide history to a context of your component:
static contextTypes = {
router: React.PropTypes.shape({
history: React.PropTypes.object.isRequired,
})
};
Then push new location in success callback function
export default YourComponent extends Component {
....
successCallback() {
this.context.router.history.push('/my-new-location')
}
....
}
The cleanest way is for React router to run a callback after route changes. Sadly they don't have that feature. There are 2 libraries which can do that using middleware - Redux-Saga and Redux Observable. The idea to listen for a state change, in this case a location change in history, and run a function.