I'm going to save in localstorage some data, but only after call UPDATE_POST action. Now i'm apply localstorage in index.js via:
store.subscribe(throttle(() => {
post: saveState(store.getState().post);
}, 1000))
and it save data in localstorage for every second. But my goal is to save it only after updatePost action. Can I achieve it using middleware, and how to write it?
My reducer:
const Post = (state = {}, action) => {
switch (action.type) {
case 'INIT_POST':
..... some code
case 'UPDATE_POST':
... some code
default:
return state
}
};
My action:
export const updatePost = (...items) => ({
type: 'UPDATE_POST',
items
});
I use Redux-thunk for this (https://github.com/gaearon/redux-thunk) - it lets you write action creators that return a function instead of an action - allowing you to perform async tasks in the action, then hit the reducer.
With redux-thunk you can call an async function (performSomeAsyncFunction() in example below), get the response, deal with it (such as the saveDataToLocalStorage() dummy function below), then hit the reducer to update your state:
export const startUpdatePost = (...items) => {
return (dispatch) => {
return performSomeAsyncFunction(...items).then((response) => {
dispatch(updatePost(...items));
saveDataToLocalStorage()
});
};
};
Don't forget to also handle the failure of the async function above
Related
I have been using Redux on some of my projects by following some tutorials, in order to make API calls(write action creators that return a function instead of an action) we use redux-thunk.
I don't understand why inside this action creators functions I can run dispatch() without having to use an instance of the store store.dispatch()?
Example on the redux documentation:
import { createStore } from 'redux'
const store = createStore(todos, ['Use Redux'])
function addTodo(text) {
return {
type: 'ADD_TODO',
text
}
}
store.dispatch(addTodo('Read the docs'))
store.dispatch(addTodo('Read about the middleware'))
Tutorial Code:
const loadRockets = () => async (dispatch) => {
const res = await fetch(URL);
const data = await res.json();
const state = data.map((rocket) => ({
id: rocket.id,
name: rocket.rocket_name,
image: rocket.flickr_images[0],
type: rocket.rocket_type,
description: rocket.description,
reserved: false,
}));
dispatch({ type: LOAD, state });
};
You can do that because the redux-thunk middleware is designed to work that way. If you dispatch a function, redux-thunk will call that function and pass the dispatch function in to you. If you're curious, here is the code where they implement that (in particular, the return action(dispatch, getState, extraArgument) part):
function createThunkMiddleware<
State = any,
BasicAction extends Action = AnyAction,
ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
// Standard Redux middleware definition pattern:
// See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
({ dispatch, getState }) =>
next =>
action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
return middleware
}
https://github.com/reduxjs/redux-thunk/blob/master/src/index.ts#L30
I am having difficulty updating my store after calling an API. I am using reduxjs/toolkit. Here is the structure of the project:
react/
store/
api/
dataconsumer/
dataSlice.js
notifications/
notificationsSlice.js
app.js
Here, api contains non-component API calls to the server. They are bound to thunk functions within dataSlice and a successful query updates data just fine.
The following are relevant parts to my reducers.
notificationSlice.js
const slice = createSlice({
...,
reducers: {
// need to call this from api
setNotifications: (state, action) => state.notification = action.payload
}
})
dataSlice.js
export const fetchInitialData = createAsyncThunk(
'chart/fetchInitialData',
async (data) => {
return API.candles.initialData({
...data
})
}
const slice = createSlice({
...
extraReducers: {
...
[fetchInitialData.success]: (state, action) => state.action = action.payload
}
})
And the api
const fetchInitialData = () => {
return fetch(url, {
...
}).then(data => data.json())
.then(data => {
if(data.status === 200) { return data } // works great!
else {
// doesn't work, but functionally what I'm looking for
store.dispatch(setNotifications(data.status))
}
})
}
The problem is when the response is other than 200, I need to update notifications, but I don't know how to get the data to that reducer.
I can't useDispatch because it is outside a component, and if I import the store to my api files it is outside the context provider and my state is uninitialized.
I'm sure I could use localStorage to solve the problem or some other hack, but I feel I shouldn't have to and I'm wondering if there is a key principle I'm missing when organizing my react-redux project? or if there are standard solutions to this problem.
Thanks - I'm new to redux.
Well, if you are using thunk, then the best solution will be to use it in order to dispatch your action after you get the error.
You do it like this:
export const fetchInitialData = () => {
return dispatch => {
...your logic
else {
// now you can dispatch like this
dispatch(setNotifications(data.status))
}
}
};
I have a React / Redux / Meteor app in which I dispatch an action, that calls a method to get a value from the server, and the method has a callback in which I dispatch an action to save the returned value in the Redux store.
I'm also using Redux thunk.
Although my original action is only dispatched once, it runs twice. It seems that dispatching an action from inside a method callback, is causing the original action to be dispatched again.
In my React component:
class MyComponent extends Component {
....
render() {
...
}
}
function mapStateToProps(state, ownProps) {
return { value: state.myPartialState.value }
}
const Tracker = withTracker(({dispatch}) => {
const state = store.getState();
const isLoading = getIsLoading(state);
...
const handle = Meteor.subscribe('myData'), {
onReady: () => {
'onReady': () => {
secondaryPatternSubscriptions(patterns);
},
});
if (isLoading && handle.ready()) {
console.log('about to dispatch original action');
dispatch(getValue());
dispatch(setIsLoading(false));
} else if (!isLoading && !handle.ready()) {
dispatch(setIsLoading(true));
}
return { ... }
)(MyComponent);
export default connect(mapStateToProps)(Tracker);
In my actions file:
export const SET_VALUE = 'SET_VALUE';
export function setValue(value) {
return {
'type': 'SET_VALUE',
'payload': value,
};
}
export const getValue = () => (dispatch, getState) => {
console.log('about to call');
Meteor.call('getValue', (error, result) => {
console.log('about to dispatch second action');
dispatch(setValue(result)); // this causes the action to be dispatched again
});
// dispatch(setValue(10)); // this only runs once
};
const initialState = {
value: 0,
}
export default function myPartialState(state = initialState, action) {
switch (action.type) {
case SET_VALUE: {
return updeep({ 'value': action.payload }, state);
}
}
}
On the server, the method is like this:
Meteor.methods({
'getValue': function () {
...
return value;
},
})
I can see from the console logs that getValue is only dispatched once, but runs twice. I have checked this again and again, and I'm pretty near 100% sure that getValue is not dispatched twice.
I think it's something to do with calling an action from inside the method callback; if I comment out dispatch(setValue(result)); and replace it with a dispatch outside the method call, then getValue only runs once.
If I dispatch a different action instead of setValue, or change the setValue action so that it doesn't alter the 'value' property in the store, then again getValue only runs once. But I can't see why changing 'value' would cause the action to be run twice, when it is only dispatched once...
I've searched online and haven't found anything about this issue.
Can anybody think why my action is running twice, and a way to have it run only once? Thanks!
I've multiple action after which I have to call other 2 action.
My first approach would be to write a middleware that looks like this:
some-middleware.js
const actions = {
[POST_CATEGORIES_SUCCESS]: 1,
[SET_READING_TIME_SUCCESS]: 1
}
export default store => next => action => {
// console.log("calling auth middleware", action)
// Reset any session data before SIGNUP or LOGIN request
if (actions[action.type] === 1) {
store.dispatch(resetStories())
store.dispatch(getStories())
}
return next(action)
}
Another approach would be to put
store.dispatch(resetStories())
store.dispatch(getStories())
inside two redux-thunk like
export const postCategories = (categories) => (dispatch) => {
dispatch(fetchPostCategories(categories))
store.dispatch(resetStories())
store.dispatch(getStories())
}
export const setReadingTime = (readingTime) => (dispatch) => {
dispatch(fetchReadingTime(readingTime))
store.dispatch(resetStories())
store.dispatch(getStories())
}
This means repeated code per every time I need those two actions to be dispatched.
Am I missing something about all this? Is middleware approach correct?
You can setup a base dispatch function with redux-thunk. Still a little extra code, and if you have multiple action files you'll need to import the function into all of them, but I think the app will be more robust since you might encounter a situation when you don't want to dispatch those actions.
export const baseDispatch = (action) => (dispatch) => {
dispatch(action)
dispatch(resetStories())
dispatch(getStories())
}
export const postCategories = (categories) => (dispatch) => {
dispatch(baseDispatch(fetchPostCategories(categories)));
}
is it possible to dispatch an action in a reducer itself? I have a progressbar and an audio element. The goal is to update the progressbar when the time gets updated in the audio element. But I don't know where to place the ontimeupdate eventhandler, or how to dispatch an action in the callback of ontimeupdate, to update the progressbar. Here is my code:
//reducer
const initialState = {
audioElement: new AudioElement('test.mp3'),
progress: 0.0
}
initialState.audioElement.audio.ontimeupdate = () => {
console.log('progress', initialState.audioElement.currentTime/initialState.audioElement.duration);
//how to dispatch 'SET_PROGRESS_VALUE' now?
};
const audio = (state=initialState, action) => {
switch(action.type){
case 'SET_PROGRESS_VALUE':
return Object.assign({}, state, {progress: action.progress});
default: return state;
}
}
export default audio;
Starting another dispatch before your reducer is finished is an anti-pattern, because the state you received at the beginning of your reducer will not be the current application state anymore when your reducer finishes. But scheduling another dispatch from within a reducer is NOT an anti-pattern. In fact, that is what the Elm language does, and as you know Redux is an attempt to bring the Elm architecture to JavaScript.
Here is a middleware that will add the property asyncDispatch to all of your actions. When your reducer has finished and returned the new application state, asyncDispatch will trigger store.dispatch with whatever action you give to it.
// This middleware will just add the property "async dispatch" to all actions
const asyncDispatchMiddleware = store => next => action => {
let syncActivityFinished = false;
let actionQueue = [];
function flushQueue() {
actionQueue.forEach(a => store.dispatch(a)); // flush queue
actionQueue = [];
}
function asyncDispatch(asyncAction) {
actionQueue = actionQueue.concat([asyncAction]);
if (syncActivityFinished) {
flushQueue();
}
}
const actionWithAsyncDispatch =
Object.assign({}, action, { asyncDispatch });
const res = next(actionWithAsyncDispatch);
syncActivityFinished = true;
flushQueue();
return res;
};
Now your reducer can do this:
function reducer(state, action) {
switch (action.type) {
case "fetch-start":
fetch('wwww.example.com')
.then(r => r.json())
.then(r => action.asyncDispatch({ type: "fetch-response", value: r }))
return state;
case "fetch-response":
return Object.assign({}, state, { whatever: action.value });;
}
}
Dispatching an action within a reducer is an anti-pattern. Your reducer should be without side effects, simply digesting the action payload and returning a new state object. Adding listeners and dispatching actions within the reducer can lead to chained actions and other side effects.
Sounds like your initialized AudioElement class and the event listener belong within a component rather than in state. Within the event listener you can dispatch an action, which will update progress in state.
You can either initialize the AudioElement class object in a new React component or just convert that class to a React component.
class MyAudioPlayer extends React.Component {
constructor(props) {
super(props);
this.player = new AudioElement('test.mp3');
this.player.audio.ontimeupdate = this.updateProgress;
}
updateProgress () {
// Dispatch action to reducer with updated progress.
// You might want to actually send the current time and do the
// calculation from within the reducer.
this.props.updateProgressAction();
}
render () {
// Render the audio player controls, progress bar, whatever else
return <p>Progress: {this.props.progress}</p>;
}
}
class MyContainer extends React.Component {
render() {
return <MyAudioPlayer updateProgress={this.props.updateProgress} />
}
}
function mapStateToProps (state) { return {}; }
return connect(mapStateToProps, {
updateProgressAction
})(MyContainer);
Note that the updateProgressAction is automatically wrapped with dispatch so you don't need to call dispatch directly.
You might try using a library like redux-saga. It allows for a very clean way to sequence async functions, fire off actions, use delays and more. It is very powerful!
redux-loop takes a cue from Elm and provides this pattern.
Since anything is technically possible, you can do it. But you SHOULD NOT do it.
Here is a quote from Dan Abramov (the creator of Redux):
"Why would you want to dispatch inside a reducer? It's grossly
misusing the library. It's exactly the same as React doesn't allow you
to setState inside render."
From "Forbid dispatch from inside a reducer" Github ticket that he himself created
Dispatching and action inside of reducer seems occurs bug.
I made a simple counter example using useReducer which "INCREASE" is dispatched then "SUB" also does.
In the example I expected "INCREASE" is dispatched then also "SUB" does and, set cnt to -1 and then
continue "INCREASE" action to set cnt to 0, but it was -1 ("INCREASE" was ignored)
See this:
https://codesandbox.io/s/simple-react-context-example-forked-p7po7?file=/src/index.js:144-154
let listener = () => {
console.log("test");
};
const middleware = (action) => {
console.log(action);
if (action.type === "INCREASE") {
listener();
}
};
const counterReducer = (state, action) => {
middleware(action);
switch (action.type) {
case "INCREASE":
return {
...state,
cnt: state.cnt + action.payload
};
case "SUB":
return {
...state,
cnt: state.cnt - action.payload
};
default:
return state;
}
};
const Test = () => {
const { cnt, increase, substract } = useContext(CounterContext);
useEffect(() => {
listener = substract;
});
return (
<button
onClick={() => {
increase();
}}
>
{cnt}
</button>
);
};
{type: "INCREASE", payload: 1}
{type: "SUB", payload: 1}
// expected: cnt: 0
// cnt = -1