Is it a good practice to use async/await directly in React component then store the result in the store ? For example:
class User extends Component {
render() {
return <div>{this.props.user.name}</div>
}
componentWillMount() {
this.getUser();
}
async getUser() {
try {
const user = await userAction.get();
this.props.storeUser(user);
} catch (err) {}
}
}
const state2props = (state) => ({
user: state.User.user
});
const dispatch2props = dispatch => ({
storeUser: (user) => dispatch(userReducer.store(user)),
});
export default connect(state2props, dispatch2props)(User);
It seems more flexible than the classic react/redux pattern.
Yes, you can use async/await in react components. It's not a bad practice
It's just architecture question.
There are lots of ways to implement async logic in applications. In small application you can implement async logic in react components. When your application grow up, you will get some issues like duplicated code (for example you want to fetch user in several react components), code composition and code splitting.
You can use redux-thunk https://github.com/gaearon/redux-thunk, redux-saga https://github.com/redux-saga/redux-saga, redux-logic https://github.com/jeffbski/redux-logic or any other solution.
Moreover you can create your own custom middleware such as:
const reactions = {};
export const addReactions = signals => {
reactions = { ...reactions, ...signals };
};
export default (signalMiddleware = ({ getState, dispatch }) => next => action => {
if (!action.signal) {
return next(action);
}
if (!reactions[action.signal]) {
throw new Error(`There is no handler for ${action.signal} signal`);
}
reactions[action.signal]({ getState, dispatch, payload: action.payload });
});
Such middleware allows you implement business logic into separate layer. For example:
import { addReactions } from './path/to/signalMiddleware';
// Describe your Actions for middleware:
const fetchUser = (id) => ({
signal: 'FETCH_USER',
payload: id
});
const anotherAction = () => ({
signal: 'SOME_ANOTHER_ACTION_WITH_USER',
});
// Describe your business logic using middleware:
addReactions({
FETCH_USER: async ({dispatch}, {id}) => {
const user = await fetcher.get(id);
dispatch({
type: 'RECEIVE_USER',
payload: user,
});
},
SOME_ANOTHER_ACTION_WITH_USER: () => {
// do some awesone job :)
}
})
So our react component could be:
class User extends Component {
render() {
return <div>{this.props.user.name}</div>
}
componentDidMount() {
this.props.dispatch(fetchUser(123));
}
}
export default connect(state2props, dispatch2props)(User);
Now you can divide your application architecture into 3 layer:
1) View — react-components
2) Business logic — your middleware
3) Data logic — your reducer
Between view and business layer we use specific actions with signal field and without type field.
Between business and data logic we use actions with type field.
This architecture allows you to get a strict separation of layers. This architecture is useful in big applications.
In small application it's ok to use redux-thunk or write async logic in react-components.
Related
How can I use redux store inside the mapDispachToProps(), for example to log the redux state in the onLogin function?
const mapDispachToProps = (dispatch) => {
return {
onLogin: (e) => {
console.log();
e.preventDefault();
},
onSetValue: (e) => {
if (e.target.name==="password"){
dispatch({ type: "Set Password", value: e.target.value })
} else if (e.target.name==="username") {
dispatch({ type: "Set User", value: e.target.value })
}
}
};
};
Here I am assuming you have implemented store and other parts of the redux framework.You can use connect API provided by react-redux in the component.You can also read this blog about react redux https://blog.logrocket.com/react-redux-connect-when-and-how-to-use-it-f2a1edab2013/ in case needed.
import { connect } from 'react-redux';
//Your code
export default connect(mapStateToProps => in your case it will be null, mapDispatchToProps)(Component Name);
There is no direct API in mapDispachToProps available to access the store.
If you need the store state to dispatch actions, you typically use some sort of Redux middleware like Redux Thunk. The concrete library isn't important, you even could apply your own middleware to Redux, whose sole purpose is to additionally receive the state in a callback. What matters is that you decouple the concrete store instance from the UI layer in order to abstract complex logic involving state and action creation away and make the system better testable.
Simple example with Redux Thunk
You can return a callback function as return of the dispatch that receives getState besides dispatch as arguments.
const mapDispachToProps = (dispatch) => {
return {
...
onSetValue: e => dispatch(setPasswordOrUser(e));
};
};
const setPasswordOrUser = e => (dispatch, getState) => {
// do something with getState
if (e.target.name==="password"){
dispatch({ type: "Set Password", value: e.target.value })
} else if (e.target.name==="username") {
dispatch({ type: "Set User", value: e.target.value })
}
}
Other approaches
Alternative 1: receive the store via React Context (ReactReduxContext Consumer) directly. React Redux is based on Context API which can pass down the store in the component tree, e.g to mapDispatchToProps via ownProps argument.
Alternative 2: expose store/state as singleton module export like this:
// store.js
import { createStore } from 'redux'
let store = createStore(rootReducer)
export const getState = store.getState
// client.js
import { getState } from "./store"
Big disadvantage here is, that you couple your store instance to React/UI layer, it's easy to mix up architecture layer responsibilities and distribute code dealing with the store in many code locations.
The whole purpose of mapDispatchToProps is to abstract dispatch away from the components. I would really encourage you to use some form of middleware for action dispatching that involves the store state. The other approaches come along with architecture costs.
I have a redux saga setup which works fine. One of my dispatches is to create a new order, then once that has been created I want to do things with the updated state.
// this.props.userOrders = []
dispatch(actions.createOrder(object))
doSomethingWith(this.props.userOrders)
Since the createOrder action triggers a redux saga which calls an API, there is a delay, so this.props.userOrders is not updated before my function doSomethingWith is called. I could set a timeout, but that doesn't seem like a sustainable idea.
I have read the similar questions on Stack Overflow, and have tried implementing the methods where relevant, but I can't seem to get it working. I'm hoping with my code below that someone can just add a couple of lines which will do it.
Here are the relevant other files:
actions.js
export const createUserOrder = (data) => ({
type: 'CREATE_USER_ORDER',
data
})
Sagas.js
function * createUserOrder () {
yield takeEvery('CREATE_USER_ORDER', callCreateUserOrder)
}
export function * callCreateUserOrder (newUserOrderAction) {
try {
const data = newUserOrderAction.data
const newUserOrder = yield call(api.createUserOrder, data)
yield put({type: 'CREATE_USER_ORDER_SUCCEEDED', newUserOrder: newUserOrder})
} catch (error) {
yield put({type: 'CREATE_USER_ORDER_FAILED', error})
}
}
Api.js
export const createUserOrder = (data) => new Promise((resolve, reject) => {
api.post('/userOrders/', data, {headers: {'Content-Type': 'application/json'}})
.then((response) => {
if (!response.ok) {
reject(response)
} else {
resolve(data)
}
})
})
orders reducer:
case 'CREATE_USER_ORDER_SUCCEEDED':
if (action.newUserOrder) {
let newArray = state.slice()
newArray.push(action.newUserOrder)
return newArray
} else {
return state
}
This feels like an XY Problem. You shouldn't be "waiting" inside a component's lifecycle function / event handler at any point, but rather make use of the current state of the store.
If I understand correctly, this is your current flow:
You dispatch an action CREATE_USER_ORDER in your React component. This action is consumed by your callCreateUserOrder saga. When your create order saga is complete, it dispatches another "completed" action, which you already have as CREATE_USER_ORDER_SUCCEEDED.
What you should now add is the proper reducer/selector to handle your CREATE_USER_ORDER_SUCCEEDED:
This CREATE_USER_ORDER_SUCCEEDED action should be handled by your reducer to create a new state where some "orders" property in your state is populated. This can be connected directly to your component via a selector, at which point your component will be re-rendered and this.props.userOrders is populated.
Example:
component
class OrderList extends React.PureComponent {
static propTypes = {
userOrders: PropTypes.array.isRequired,
createOrder: PropTypes.func.isRequired,
}
addOrder() {
this.props.createOrder({...})
}
render() {
return (
<Wrapper>
<Button onClick={this.addOrder}>Add Order</Button>
<List>{this.props.userOrders.map(order => <Item>{order.name}</Item>)}</List>
</Wrapper>
)
}
}
const mapStateToProps = state => ({
userOrders: state.get('userOrders'),
})
const mapDispatchToProps = {
createOrder: () => ({ type: 'CREATE_ORDER', payload: {} }),
}
export default connect(mapStateToProps, mapDispatchToProps)(OrderList)
reducer
case 'CREATE_USER_ORDER_SUCCEEDED':
return state.update('userOrders',
orders => orders.concat([payload.newUserOrder])
)
If you really do need side-effects, then add those side-effects to your saga, or create a new saga that takes the SUCCESS action.
This is how I've been organizing my React / Redux projects because it's how they did it in the tutorial I followed. Is this what Flux architecture is and if not what would you call this?
First I call a function in my component that's defined in the action file
This function does an ajax request to get info from an API
Then it fires off an action creator
The reducer listens for action creators and once one is detected it executes a function that updates the state
Here's an example:
Component
class List extends React.Component {
componentDidMount() {
this.props.getPosts();
}
// etc...
}
const mapStateToProps = state => {
return {
posts: state.posts
};
};
const mapDispatchToProps = dispatch => {
return {
getPosts: () => dispatch(actions.getPosts())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(List);
Action
const postsLoaded = posts => {
return {
type: actionTypes.POSTS_LOADED,
posts: posts
};
};
export const getPosts = () => {
return dispatch => {
axios
.get('http://api.something.com/posts', {})
.then(response => {
dispatch(postsLoaded(response.posts));
})
.catch(e => {
console.error(e);
});
};
};
Reducer
const setPosts = (prevState, action) => {
return {
...prevState,
...action.posts
};
};
const reducer = (prevState = {}, action) => {
switch (action.type) {
case actionTypes.POSTS_LOADED:
return setPosts(prevState, action);
default:
return prevState;
}
};
export default reducer;
Flux is a design pattern. Redux is one of several libraries that implement Flux. The intent is NOT for you to "use Redux to implement Flux", but rather "use the Flux pattern by using Redux".
You can find a much better description in the docs below, but in simplest terms, the Flux architecture is based on a unidirectional data flow, which means that each piece receives data from one place, and outputs changes to another. The intent of this pattern is to eliminate "spaghetti code", where various parts of the application pass data in many different directions, which can eventually become very difficult to trace.
In other words, your components are the "View" in the diagram below.
Redux store gives state to your component
Your component renders something, and when a user performs an action, the component creates an action and gives it to the dispatcher.
The dispatcher finds the reducer that can handle your action, and gives the result to the store.
And the cycle repeats.
This image and an in-depth overview of Flux can be found here.
I have a React app that uses React-Router/React-Router-dom for page navigation and redux to store some global state info (jwt token for django rest framework for example). The state also stores info about the currently viewed page, such as the serialized django model.
But what is the best way to load the django model into the redux store when the route changes? I'm having trouble wrapping my head around where logic should be going.
If you view the repo below you can see where I'm having trouble figuring it out.
In this example when someone navigates to /spells/:id, it should load the spell django model into the redux store so information about it is globally accessible.
But how do I go about doing that? Where do I call the actions and reducers to properly handle the state?
Any guidance would be appreciated.
You can view the full project here. The component in question here is LayoutSpellView (/frontend/src/components/LayoutSpellView). That's where the model information is stored, displayed, etc.
Edit: Adding relevant code
Called in componentDidMount:
axios
.get("http://localhost:3000/api/spells/" + spellId)
.then(response => {
let spell = Object.assign({}, spellView.state.spell);
spell.id = response.data.id;
spell.owner = response.data.owner;
...blahblah other fields
this.setState({
spell
});
})
.then(response => {
this.props.dispatch({
type: 'FETCH_SPELL_SUCCESS',
payload: this.state.spell,
});
})
.catch(function(error) {
console.error('[API]\t', error);
});
In LayoutSpellView (same component as above)
import {loadSpell} from "../src/reducers";
const mapStateToProps = (state) => ({
spell: loadSpell(state.spell.id),
});
const mapDispatchToProps = (dispatch) => ({
getSpell: (state.spell.id) => {
dispatch(loadSpell(state.spell.id))
}
});
Actions spell.js:
export const FETCH_SPELL = '##spell/FETCH_SPELL';
export const FETCH_SPELL_SUCCESS = '##spell/FETCH_SPELL_SUCCESS';
export const FETCH_SPELL_FAILURE = '##spell/FETCH_SPELL_FAILURE';
export const loadSpell = (spellId) => ({
[RSAA]: {
endpoint: '/api/spell/${spellId}',
method: 'GET',
types: [
FETCH_SPELL, FETCH_SPELL_SUCCESS, FETCH_SPELL_FAILURE
]
}
});
Reducers spell.js:
const initialState = {
spell: {
id: 0,
owner: 0,
Name: 'Name',
School: 'unknown',
Subschool: 'unknown',
}
};
export default (state=initialState, action) => {
switch(action.type) {
case spell_action.FETCH_SPELL_SUCCESS:
return {
spell: {
id: action.payload.spell.id,
owner: action.payload.spell.owner,
Name: action.payload.spell.Name,
School: action.payload.spell.School,
Subschool: action.payload.spell.Subschool,
}
};
default:
return state;
}
}
export function loadSpell(state) {
if (state) {
return state.spell
}
}
Let's look at the question in a different way. Instead of asking "How do I dispatch an action when routes change", let's ask "What is the actual source of truth: Redux or URL?"
If we go with redux being the Single Source of Truth, then that would mean that we need to dispatch some action that would cause some side-effect ( maybe redux-saga or redux-observable or even redux-thunk? ) that changed the url:
Comp -> dispatch(action) -> store updates -> URL changes
If we go with the URL being the Single Source of Truth, we change the flow to:
URL changes -> dispatch(action) -> store updates
If we go this route, which is what it sounds like you are wanting, you will need to probably hook up middleware, which are functions of the following signature:
store => next => action => next(action)
Depending on the router that you are using, you can either hook into their actions or you can hook into window.onpopstate and check the next url. Either way, the overall middleware function would look something like
const middleware = store => {
return next => action => {
if (actionWillCauseSpellToBeNeeded(action)) {
makeAPICall()
.then(transformAPIToAction)
.catch(transformError)
.then(store.dispatch)
}
return next(action)
}
}
Maybe I am overthinking this, but so far most of the stuff I've read on redux-thunk handles async calling from API, etc.
Ideally I would like to have the same behavior but for UI's transition.
For instance, let's say I have a game that for simplicity, it requires two players, and each player has a turn to guess a name.
If the player's guess is matched, then I want to display the dialog for 5 seconds, and then reset the game.
Otherwise, display a dialog that indicates it's the next player's turn for 5 seconds.
I have the following code:
class Game extends Component {
constructor(props) {
super(props);
}
componentWillReceiveProps(nextProps) {
const { isMatchedNumbers } = nextProps
if (isMatchedNumbers) {
this.props.showDialog('You win!')
this.props.resetGame()
} else {
this.props.showDialog('next turn')
this.props.nextTurnPlayer()
}
}
render() {
...
}
}
const mapStateToProps = (state, props) => ({
isMatchedNumbers: state.isMatchedNumbers
})
const mapDispatchToProps = dispatch => ({
nextTurnPlayer: () => {
dispatch({ type: NEXT_TURN_PLAYER })
},
showDialog: message => {
dispatch({ type: MESSAGE, message })
},
resetGame: () => {
dispatch({ type: RESET_GAME })
},
})
export default connect(mapStateToProps, mapDispatchToProps)(Game)
How would I be able to achieve this?
I thought about adding setTimeOut inside mapDispatchToProps, but I feel that it is not the right way.
There is no reason you can't use redux-thunk for this, in fact, on the official documentation for it, they even use a setTimeout as a way to emulate that async nature.
function showDialogAsync() {
return dispatch => {
setTimeout(() => {
dispatch(showDialog());
}, 5000);
};
}
You can utilise this simple pattern where ever you want, be it for resetting the game or displaying dialogues.
Repo with Documentation
Redux-saga
Great for more complex async behaviour than redux-thunk