Reputation: 5895
I have an App
component, a Login
component.
In my login page when user successfully gives his credential, the API returns the user data as response. I want to store that user data as a global state. That's why I used redux
.
My index.js file content:
const saveUserData = (state = {
user: {}
}, action) => {
switch (action.type) {
case "UPDATE_USER":
state = {
...state,
user: action.payload
};
break;
}
return state;
};
const store = createStore(saveUserData);
store.subscribe( () => {
console.log(state.getState());
});
render(
<Provider store={store}><App /></Provider>
, window.document.getElementById('app'));
In my App.js file:
class App extends React.Component {
render() {
return (
<BrowserRouter>
<Root>
<Route exact path="/" component={Home}/>
<Route exact path="/login" component={Login}/>
<Route exact path="/register" component={Register}/>
<Route exact path='/books' component={Books}/>
<Route path='/books/:id' component={BookDetail}/>
</Root>
</BrowserRouter>
);
};
}
const mapStateToProps = (state) => {
return {
userData: state.saveUserData
}
};
const mapDispatchToProps = (dispatch) => {
return {
updateUser: (user) => {
dispatch({
type: "UPDATE_USER",
payload: user
})
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App)
In my Login.js file:
handleSubmit(event) {
event.preventDefault();
if (!event.target.checkValidity()) {
return;
}
const {email, password, remember_me} = this.state.formData;
const url = api.api_url + "auth/login";
fetch(url, {
method: 'POST',
headers: new Headers({
Accept: 'application/json',
'Content-Type': 'application/json',
}),
body: JSON.stringify({
email: email,
password: password,
remember_me: remember_me
}),
})
.then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => {
if (typeof response.access_token == "undefined")
this.setState({display_errors: true, errors: response.error.user_authentication});
else{
localStorage.setItem('jwtToken', response.access_token);
this.props.updateUser(response.user); // Here I want to call the dispatcher to save the response user data in global store
this.props.history.push({
pathname: '/',
state: {message: response.message}
});
}
});
}
In login component i want to call the App
component's dispatcher method updateUser
with this.props.updateUser(response.user);
. But it's not working.
Does anyone have any idea how can I achieve that? You can see full code in github
Upvotes: 0
Views: 6740
Reputation: 26
interface YourComponentProps {
dispatch: Dispatch<any>
}
class YourComponentPanel extends Component< YourComponentProps, YourComponentProps > {
constructor(props: YourComponentProps) {
super(props);
this.state = { ...props };
}
componentDidMount(): void {
this.state.dispatch( someAction( "someday" ) );
}
render(): React.ReactNode {
return (<Text>Im a Component</Text>);
}
}
function YourComponentPanelDispatchWrapper(props) {
const dispatch = useDispatch();
return (<YourComponentPanel dispatch={dispatch}/>);
}
Typescript. React-native but this will work in react. Maybe it's naive. but it for sure works.
Upvotes: 0
Reputation: 2768
A few things first.
saveUserData
is your reducer as it seems, why it should be exposed in props
? Only the data from state that is relevant to your component should be made available to props by using mapStateToProps
. So, for example, if you had a user data like currentUser
in your state that actually is used in your component, then you should do-
const mapStateToProps = (state) => {
return {
currentUser: state.currentUser
};
};
Now, inside the component, you could do- this.props.currentUser
to get the data.
The next important bit is, you need to use bindActionCreators
from redux
. Define the action in actions.js
and then in your component file-
import {updateUser} from 'path/to/actions.js'
import {bindActionCreators} from 'redux';
...
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({updateUser}, dispatch);
};
And finally-
export default connect(mapStateToProps, mapDispatchToProps)(Login);
EDIT:
Checked your github repo. I think the first problem is importing Login
component as {Login}
in App.js
, which should be import Login
instead as it's exported as default from Login.js
The second thing is using the component in render attribute of Route
, like below-
<Route exact path="/login" render={() => (<Login/>)}/>
This solved the issue for me, so should do the same for you.
Upvotes: 0
Reputation: 749
this is How I would do it. First of all, you don't need to connect your app.js to store as it is not using any state or dispatching action. Make it a functional component.
const App = () => {
render() {
return (
<BrowserRouter>
<Root>
<Route exact path="/" component={Home}/>
<Route exact path="/login" component={Login}/>
<Route exact path="/register" component={Register}/>
<Route exact path='/books' component={Books}/>
<Route path='/books/:id' component={BookDetail}/>
</Root>
</BrowserRouter>
);
};
}
export default App;
It is the best practice to add a default case to your switch statement.
const saveUserData = (state = {
user: {},
display_errors: false,
errors: {}
}, action) => {
switch (action.type) {
case "UPDATE_USER":
state = {
...state,
user: action.payload
};
case "DISPLAY_ERRORS":
state = {
...state,
errors: action.payload,
display_errors: true
};
default:
return state;
}
};
Create a separate action file and move your fetch request inside the actions.js
export default updateUser = (email, password, remember_me) => dispatch => {
fetch(url, {
method: 'POST',
headers: new Headers({
Accept: 'application/json',
'Content-Type': 'application/json',
}),
body: JSON.stringify({
email: email,
password: password,
remember_me: remember_me
}),
})
.then(res => res.json()).
.then(response => {
if (typeof response.access_token == "undefined")
dispatch({type: DISPLAY_ERRORS, payload: response.error.user_authentication });
else{
localStorage.setItem('jwtToken', response.access_token);
}
});
.catch(error => console.error('Error:', error))
}
Then you can import this action file and can access the updateUser where ever you want to use.
import { updateUser } from "action.js";
//now you have access to this function
this.props.updateUser(email, password, remember_me);
const mapStateToProps = state => ({
//state.....
})
export default connect(mapStateToProps, { updateUser })(Login);
Upvotes: 1
Reputation: 305
you can add and change the following in app.js
import { bindActionCreators } from 'redux';
import * as actionCreators from "../yourActionFile"
class App extends React.Component {
handleSubmit(e){
e.preventDefault();
this.props.updateUser(("yourArgumentsHere"))
}
render() {
return (
<BrowserRouter>
<Root>
<Route exact path="/" component={Home}/>
<Route exact path="/login" component={Login}/>
<Route exact path="/register" component={Register}/>
<Route exact path='/books' component={Books}/>
<Route path='/books/:id' component={BookDetail}/>
</Root>
</BrowserRouter>
);
};
}
const mapStateToProps = (state) => {
return {
userData: state.saveUserData
}
};
const mapDispatchToProps = (dispatch) => {
return {
updateUser: bindAtionCreators(actionCreators.updateUser, dispatch)
//the logic for updating your store or "global state" should be done in the reducer
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App)
you can either pass the handleSubmit function down to the child as props or you can add the mapDispatchToProps and handleSubmit to the child component. Either way, if you are relying on the store for all dynamic data in your app the changes will trickle down once the the store has been updated by the reducer.
Upvotes: 0
Reputation: 970
As the user above states, it is better to consider separation of concern. This is what I would do:
login
. That includes actions, sagas, and reducers.login
, I would route its link to a container
which has access to global state
with connect()
. That component alone has access to dispatch and its children components should only refer to it for dispatches keeping them dumb and maintain source of truth.NOTE: as long as you use the redux life cycle, it doesn't matter where you are calling the action creator from it will be available to your global state as long as it has connect()
Upvotes: 0
Reputation: 1627
Ideally you should store your updateUser
method inside an actions file, and import that method where ever you need to use it.
actions.js
export const updateUser = (user) => {
dispatch({
type: "UPDATE_USER",
payload: user
})
}
Then, where ever you want to use it you can use:
import { updateUser } from 'path/to/actions.js'
import { bindActionCreators } from 'redux';
...
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
updateUser
}, dispatch);
};
When you find you have a whole bunch of actions, which you probably will when your app expands in size and complexity, this approach will help you to manage your actions properly.
Always consider Separation of Concerns - the purpose of your actions.js file is to contain all of your actions, nothing else, and it is much better placed here than in the app.js file. However, further still I would personally have an actions folder, and contain this method inside a user.js file in that directory.
I hope this helps.
I have added use of bindActionCreators
to mapDispatchToProps
. Sorry its been a while since I did it this way - I personally export store.dispatch
from my app.js and import and use that where I need to, rather than using mapDispatchToProps
. I can explain further if you like, but really it is good practice to use the mapDispatchToProps
method provided.
Upvotes: 0