React-router's history in react-redux provider's connect function - javascript

I've connected presentational-functional component to the redux store by means of connect function.
export default connect(
state => ({tasks: state.tasks}),
dispatch => ({
getTasks: () => apiGetTasks(dispatch),
getTask: (id) => apiGetTask(dispatch, id),
})
) (TaskView)
And now I want to redirect on the other page when getTask has triggered (or even better when apiGetTask has finished). So I tried to use history in the next way:
export default connect(
state => ({tasks: state.tasks}),
dispatch => ({
getTasks: () => apiGetTasks(dispatch),
getTask: (id) => apiGetTask(() => {
const history = useHistory()
dispatch(id)
history.push('/otherPage')
}, id),
})
) (TaskView)
But it does not work with the message:
Unhandled Rejection (Error): Invalid hook call. Hooks can only be called inside of the body of a function component....
I see that it is wrong pattern probably... So, what is the best one? Do I need to pass callback from parent component and make redirect in it? Or may be I should redirect in presentational component (but it looks strange for presentational component)
Or may be I should call dispatch(switchToPage()) in apiGetTask's promise and implement redirection in the application component, basing on the value?
What is the best solution in such case?
And one related question yet: Is it regular to use api calls in such manner as above?
Thank you, in advance!

Well.
Now you can assess my solution:
---index.js----
export let history = null;
const DefaultComponent = ()=> {
history = useHistory()
return <div style={{visibility: 'hidden'}}/>
}
ReactDOM.render(
<React.StrictMode>
<Provider store={createAppStore()}>
<Router>
<DefaultComponent/>
<Dashboard />
</Router>
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
---Tasks.js----
import {history} from './index.js'
export default connect(
state => ({tasks: state.tasks}),
dispatch => ({
getTasks: () => apiGetTasks(dispatch),
getTask: (id) => apiGetTask(dispatch, id, ()=>history.push('/CreateTask')),
})
) (TaskView)
Don't hesitate to criticise. Any proposals are welcome!

Related

How to set up redux in react native?

My code:
function App({ isAuthenticated }) {
const dispatch = useDispatch();
const [token, setToken] = useState()
const [appLoaded, setAppLoading] = useState(false)
const routes = buysellRoutes(isAuthenticated, token);
const [fontsLoaded] = useFonts({
Inter_900Black,
})
return (
<Provider store={store}>
<ApplicationProvider {...eva} theme={eva.light}>
{ appLoaded && fontsLoaded ? routes : <PreLoadScreen />}
</ApplicationProvider>
</Provider>
)
}
function mapStateToProps(state) {
return {
isAuthenticated: state.auth.isAuthenticated,
};
}
export default connect(mapStateToProps)(App);
Error: Could not find "store" in the context of "Connect(App)". Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(App) in connect options.
Here the main issue is you cannot connect the main component without a provider, so create one HOC and provide the store to the whole app and then after you can connect.
Another way is, you can use context in inner components so you don't need to connect the component just provide the store to the main HOC.
You can refer to this project for the initial react native app setup
react native starter project with redux
There are some problems with your implementation. I'll try to explain them step by step:
First, you need a store.js file to create your store outside of your App.js. for more information take a look at its documentation.
Second, the App component is considered as the root component in the react structure so avoid passing the props to the root component which is clearly wrong.
Third, you need to pass the store through of <Provider store={store} > and consume the store (get store's data as a state) in the components or pages with useSelector method, and no more need to mapStateTopProps.
Note: you must define and pass the store to the Top-level component and use it inside of the children.
Note: to check the isAuthenticated property, separate your ApplicationProvider and create a component for it.
function SomeComponent() {
const isAuthenticated = useSelector(state => state.auth.isAuthenticated)
const dispatch = useDispatch();
const [token, setToken] = useState()
const [appLoaded, setAppLoading] = useState(false)
const routes = buysellRoutes(isAuthenticated, token);
return(
<ApplicationProvider {...eva} theme={eva.light}>
{ appLoaded && fontsLoaded ? routes : <PreLoadScreen />}
</ApplicationProvider>
)
}
Optionally:
You may need to dispatch some actions in your component, with react hooks it's easy with useSelector and useDispatch without mapStateToProps or mapDispatchToProps:
function SomeExampleComponent () {
const dispatch = useDispatch();
const handleToggle = () => {
// ...
dispatch({type: "TOGGLE", payload: undefiend})
// or you can dispatch some actions that you defined before:
dispatch(onToggleAction())
}
return (
<div onClick={handleToggle}>
// ...
</div>
)}

Using redux in NEXT.JS

In my application on Next.Js i use redux and redux saga. I want to use ssr making http requests:
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
store.dispatch(getDataRequest());
store.dispatch(END);
await store.sagaTask.toPromise();
});
In the same time i want to get data of the above result:
const selector = useSelector((s) => s);
console.log(selector);
The issue is that, when i run the application i get an error:
Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
I used the provider but the data doesn't appear. Question: How to solve the issue in my application?
demo: https://codesandbox.io/s/awesome-butterfly-f7vgd?file=/pages/store/saga.js
this is your _app component:
function App({ Component, pageProps }) {
return (
<div>
<Provider store={makeStore}>
<Component {...pageProps} />
</Provider>
</div>
);
}
you dont need to wrap it with Provider. this is the only thing you need to do in _app.
export default wrapper.withRedux(App)
this is getStatisProps in your pages/index.js
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
store.dispatch(getDataRequest());
store.dispatch(END);
await store.sagaTask.toPromise();
});
see store is already passed here. You will be accesing state via store.getState(). this is how it should be
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
store.dispatch(getDataRequest());
store.dispatch(END);
await store.sagaTask.toPromise();
const state = store.getState();
return {props:{data:state}} // since you have only one reducer
});
now data will be passed as a prop to your component. if you console.log(props.data) inside the component, you should be seeing your dataReducer

Axios Action in Redux not "thenable"

I have a fully working setup of React with Redux. In my action file, I use axios to make calls to 3rd party API.
After connecting my form component to the Redux store and trying to call the POST action, I am presented with an error saying that I cannot call then on undefined...Funny thing is that the action is still called i.e. the form is submitted to the 3rd party's api right after this console error is thrown.
I have looked at numerous S/O questions as well as the axios & redux-thunk docs, but can't see what I'm missing. Here is some of the relevant code:
actions.js
import axios from 'axios';
import _ from 'lodash'
import runtimeEnv from '#mars/heroku-js-runtime-env';
import {apiErrorCatcher} from "../../../utils/errorCatcher"
import {toastr} from 'react-redux-toastr'
const env = runtimeEnv();
export function createProfile(data) {
return dispatch => {
axios.post( env.REACT_APP_API_URL + '/api/profile/', data).then(
(res) => {
toastr.success('Profile added', 'Congratulations on adding your profile')
return res
}
)
.catch(function (error) {
apiErrorCatcher(error)
toastr.error('Oops...', 'Unfortunately an error occurred on our side. Please try again later.')
return error
})
}
}
Profile.js
...
handleCreateProfile=()=>{
this.props.actions.createProfile(this.state.data).then(
(res) => {console.log(res)}
)
}
...
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators({createProfile}, dispatch) }
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Profile))
index.js
...
const store = createStore(
rootReducer,
composeWithDevTools(
applyMiddleware(thunk, oidcMiddleware)
)
);
...
ReactDOM.render(
<Provider store={store}>
<OidcProvider store={store} userManager={userManager}>
<BrowserRouter>
<MuiThemeProvider>
<App />
</MuiThemeProvider>
</BrowserRouter>
</OidcProvider>
</Provider>,
document.getElementById('root')
);
I think that the error might have something to do with promises but I am not 100% confident. Also not sure how to resolve this as I can't see where I'm going wrong.
Problem is in your component method:
this.props.actions.createProfile(this.state.data).then(
(res) => {console.log(res)}
)
becouse action don't return promise, so if you want log your data, or dispatch action you should do it in action createProfile.

What's the point of mapDispatchToProps in a redux-react app? [duplicate]

I was reading the documentation for the Redux library and it has this example:
In addition to reading the state, container components can dispatch actions. In a similar fashion, you can define a function called mapDispatchToProps() that receives the dispatch() method and returns callback props that you want to inject into the presentational component.
This actually makes no sense. Why do you need mapDispatchToProps when you already have mapStateToProps?
They also provide this handy code sample:
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
What is this function and why it is useful?
I feel like none of the answers have crystallized why mapDispatchToProps is useful.
This can really only be answered in the context of the container-component pattern, which I found best understood by first reading:Container Components then Usage with React.
In a nutshell, your components are supposed to be concerned only with displaying stuff. The only place they are supposed to get information from is their props.
Separated from "displaying stuff" (components) is:
how you get the stuff to display,
and how you handle events.
That is what containers are for.
Therefore, a "well designed" component in the pattern look like this:
class FancyAlerter extends Component {
sendAlert = () => {
this.props.sendTheAlert()
}
render() {
<div>
<h1>Today's Fancy Alert is {this.props.fancyInfo}</h1>
<Button onClick={sendAlert}/>
</div>
}
}
See how this component gets the info it displays from props (which came from the redux store via mapStateToProps) and it also gets its action function from its props: sendTheAlert().
That's where mapDispatchToProps comes in: in the corresponding container
// FancyButtonContainer.js
function mapDispatchToProps(dispatch) {
return({
sendTheAlert: () => {dispatch(ALERT_ACTION)}
})
}
function mapStateToProps(state) {
return({fancyInfo: "Fancy this:" + state.currentFunnyString})
}
export const FancyButtonContainer = connect(
mapStateToProps, mapDispatchToProps)(
FancyAlerter
)
I wonder if you can see, now that it's the container 1 that knows about redux and dispatch and store and state and ... stuff.
The component in the pattern, FancyAlerter, which does the rendering doesn't need to know about any of that stuff: it gets its method to call at onClick of the button, via its props.
And ... mapDispatchToProps was the useful means that redux provides to let the container easily pass that function into the wrapped component on its props.
All this looks very like the todo example in docs, and another answer here, but I have tried to cast it in the light of the pattern to emphasize why.
(Note: you can't use mapStateToProps for the same purpose as mapDispatchToProps for the basic reason that you don't have access to dispatch inside mapStateToProp. So you couldn't use mapStateToProps to give the wrapped component a method that uses dispatch.
I don't know why they chose to break it into two mapping functions - it might have been tidier to have mapToProps(state, dispatch, props) IE one function to do both!
1 Note that I deliberately explicitly named the container FancyButtonContainer, to highlight that it is a "thing" - the identity (and hence existence!) of the container as "a thing" is sometimes lost in the shorthand
export default connect(...)
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
syntax that is shown in most examples
It's basically a shorthand. So instead of having to write:
this.props.dispatch(toggleTodo(id));
You would use mapDispatchToProps as shown in your example code, and then elsewhere write:
this.props.onTodoClick(id);
or more likely in this case, you'd have that as the event handler:
<MyComponent onClick={this.props.onTodoClick} />
There's a helpful video by Dan Abramov on this here:
Redux: Generating Containers with connect() from React Redux (VisibleTodoList)
mapStateToProps() is a utility which helps your component get updated state(which is updated by some other components),
mapDispatchToProps() is a utility which will help your component to fire an action event (dispatching action which may cause change of application state)
mapStateToProps, mapDispatchToProps and connect from react-redux library provides a convenient way to access your state and dispatch function of your store. So basically connect is a higher order component, you can also think as a wrapper if this make sense for you. So every time your state is changed mapStateToProps will be called with your new state and subsequently as you props update component will run render function to render your component in browser. mapDispatchToProps also stores key-values on the props of your component, usually they take a form of a function. In such way you can trigger state change from your component onClick, onChange events.
From docs:
const TodoListComponent = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
const TodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
Also make sure that you are familiar with React stateless functions and Higher-Order Components
Now suppose there is an action for redux as:
export function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
When you do import it,
import {addTodo} from './actions';
class Greeting extends React.Component {
handleOnClick = () => {
this.props.onTodoClick(); // This prop acts as key to callback prop for mapDispatchToProps
}
render() {
return <button onClick={this.handleOnClick}>Hello Redux</button>;
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: () => { // handles onTodoClick prop's call here
dispatch(addTodo())
}
}
}
export default connect(
null,
mapDispatchToProps
)(Greeting);
As function name says mapDispatchToProps(), map dispatch action to props(our component's props)
So prop onTodoClick is a key to mapDispatchToProps function which delegates furthere to dispatch action addTodo.
Also if you want to trim the code and bypass manual implementation, then you can do this,
import {addTodo} from './actions';
class Greeting extends React.Component {
handleOnClick = () => {
this.props.addTodo();
}
render() {
return <button onClick={this.handleOnClick}>Hello Redux</button>;
}
}
export default connect(
null,
{addTodo}
)(Greeting);
Which exactly means
const mapDispatchToProps = dispatch => {
return {
addTodo: () => {
dispatch(addTodo())
}
}
}
mapStateToProps receives the state and props and allows you to extract props from the state to pass to the component.
mapDispatchToProps receives dispatch and props and is meant for you to bind action creators to dispatch so when you execute the resulting function the action gets dispatched.
I find this only saves you from having to do dispatch(actionCreator()) within your component thus making it a bit easier to read.
React redux: connect: Arguments

With react-router, redux will not work

I have read lots of similar questions,And no one fit for me.
about
this.props.children doesn't always inherit the context from its parent
Could not find "store" in either the context or props
//store config
const createHistoryWithBasename = (historyOptions) => {
return useBasename(createHistory)({
basename: '/user_home'
})
}
const finalCreateStore = compose(
applyMiddleware(thunk, createLogger()),
reduxReactRouter({
createHistory: createHistoryWithBasename }),
devTools()
)(createStore);
export function configureStore(initialState) {
const store = finalCreateStore(rootReducer, initialState);
return store;
}
the index file, all route nested in Provider.
//entry file
let routes = (
<Route path="/" component={UserHome}></Route>
);
ReactDom.render(
<Provider store={store} >
<ReduxRouter>
{routes}
</ReduxRouter>
</Provider>,
rootElement
);
the Component. I logged the this.props, there are't the dispatch property. And the parameter of context is an empty object.
//user_home.js
class UserHome extends React.Component {
constructor(props, context){
//context is empty Object
super(props, context);
}
render() {
//this.props.dispatch is undefined
return (
<div className="container">
test
</div>
);
}
}
function mapStateToProps(state){
return state;
}
export default connect(mapStateToProps, {
pushState
})(UserHome);
Maybe, it's a simple question, but It took me a lot of time.Hope to get your help. Thank you!
Maybe we can inject the action creator to connect
import * as ActionCreators from './actions/user_actions';
//we can use the actions in ActionCreators to dispatch the action.
export default connect(mapStateToProps, {
pushState: pushState,
...ActionCreators
})(Component);
the actions
//action file
export function xxAction(){
return dispatch =>{
dispatch({
type: 'XX_ACTION'
});
}
}
this solution fit for dispatching actions in children component.There are some limitations, Looking forward to a better solution.

Categories