I've got a reducer function like the following:
nextStep: (state) => {
if (state.currentStep < state.totalSteps && state.currentStepValid) {
state.currentStep += 1;
}
},
I'm listening to changes with the "useSelector" hook and I need to trigger a change even if the value doesn't change when dispatch is called. How is it possible to implement this?
Best regards!
At first you must create store and add your reducer to createStore method
import todoApp from './reducers'
const store = createStore(todoApp)
Then implement your reducer as function, which consume two params prevState and actions and return next state based on action, like this in basic example Basic tutorial
import { VisibilityFilters } from './actions'
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
}
function todoApp(state, action) {
if (typeof state === 'undefined') {
return initialState
}
// For now, don't handle any actions
// and just return the state given to us.
return state
}
In your code snippet, you aren't returning next state instead you are modify prev state.
You must write something like that:
nextStep: (state) => {
if (state.currentStep < state.totalSteps && state.currentStepValid) {
return {
...state,
currentStep: state.currentStep + 1
};
} else {
return state;
}
},
Related
I have a redux state that contains an array of objects, for each of these object I call an api to get more data
objects.forEach((obj, index) => {
let newObj = { ...obj };
service.getMoreData()
.then(result => {
newObj.data = result;
let newObjects = [...this.props.objectsList] ;
let index = newObjects.findIndex(el => el.id === newObj.id);
if (index != -1) {
newObjects[index] = newObj;
this.props.updateMyState({ objectsList: newObjects });
}
})
When I get two very close responses the state is not updated correctly, I lose the data of the first response.
What is the right way to update a single element of the array? Thanks!
So since i don't know what service is and there isn't that much here to go off, here is what I would do from my understanding of what it looks like your doing:
So first let's set up a reducer to handle the part of redux state that you want to modify:
// going to give the reducer a default state
// array just because I don't know
// the full use case
// you have an id in your example so this is the best I can do :(
const defaultState = [{ id: 123456 }, { id: 123457 }];
const someReducer = (state=defaultState, action) => {
switch (action.type) {
// this is the main thing we're gonna use
case 'UPDATE_REDUX_ARRAY':
return [
...action.data
]
// return a default state == the state argument
default:
return [
...state
]
}
}
export default someReducer;
Next you should set up some actions for the reducer, this is optional and you can do it all inline in your component but I'd personally do it this way:
// pass data to the reducer using an action
const updateReduxArray = data => {
return {
type: 'UPDATE_REDUX_ARRAY',
data: data
}
}
// export like this because there might
// be more actions to add later
export {
updateReduxArray
}
Then use the reducer and action with React to update / render or whatever else you want
import { useState } from 'react';
import { updateReduxArray } from 'path_to_actions_file';
import { useEffect } from 'react';
import { axios } from 'axios';
import { useSelector, useDispatch } from 'react-redux';
const SomeComponent = () => {
// set up redux dispatch
const dispatch = useDispatch();
// get your redux state
const reduxArray = useSelector(state => state.reduxArray) // your gonna have to name this however your's is named
// somewhere to store your objects (state)
const [arrayOfObjects, updateArrayOfObjects] = useState([]);
// function to get data from your API
const getData = async () => {
// I'm using axios for HTTP requests as its pretty
// easy to use
// if you use map you can just return the value of all API calls at once
const updatedData = await Promise.all(reduxArray.map(async (object, index) => {
// make the api call
const response = axios.get(`https://some_api_endpoint/${object.id}`)
.then(r => r.data)
// return the original object with the addition of the new data
return {
...response,
...object
}
}))
// once all API calls are done update the state
// you could just update redux here but this is
// a clean way of doing it incase you wanna update
// the redux state more than once
// costs more memory to do this though
updateArrayOfObjects(updatedData)
}
// basicity the same as component did mount
// if you're using classes
useEffect(() => {
// get some data from the api
getData()
}, [ ])
// every time arrayOfObjects is updated
// also update redux
useEffect(() => {
// dispatch your action to the reducer
dispatch(updateReduxArray(arrayOfObjects))
}, [arrayOfObjects])
// render something to the page??
return (
<div>
{ reduxArray.length > 0
? reduxArray.map(object => <p>I am { object.id }</p>)
: <p>nothing to see here</p>
}
</div>
)
}
export default SomeComponent;
You could also do this so that you only update one object in redux at a time but even then you'd still be better off just passing the whole array to redux so I'd do the math on the component side rather than the reducer .
Note that in the component I used react state and useEffect. You might not need to do this, you could just handle it all in one place when the component mounts but we're using React so I just showcased it incase you want to use it somewhere else :)
Also lastly I'm using react-redux here so if you don't have that set up (you should do) please go away and do that first, adding your Provider to the root component. There are plenty of guides on this.
I'm studyin this code of a react component:
https://github.com/algolia/react-instantsearch/blob/master/packages/react-instantsearch-core/src/core/createStore.js
export default function createStore(initialState) {
let state = initialState;
const listeners = [];
function dispatch() {
listeners.forEach(listener => listener());
}
return {
getState() {
return state;
},
setState(nextState) {
state = nextState;
dispatch();
},
subscribe(listener) {
listeners.push(listener);
return function unsubcribe() {
listeners.splice(listeners.indexOf(listener), 1);
};
},
};
}
I was surprise this code don't use , but a own store? someone can me explain about this code? this a way to use global store and don't use redux-store?
this lines:
listeners.forEach(listener => listener());
return function unsubcribe() {
listeners.splice(listeners.indexOf(listener), 1);
};
what does mean?
This is simplified implementation of Redux-like store. It doesn't involve other Redux entities like middlewares or reducers.
setState does exactly what it's expected from it, so does subscribe.
listeners.forEach(listener => listener()) notifies listeners that a state was updated.
return function unsubcribe() {...} returns a function to unsubscribe a listener from state updates.
I am setting my redux state through a value I have in localStorage. This works fine when I navigate into my page. However, when I do a hard refresh the state is never set, despite the value in localStorage being passed down.
This is what my code looks like:
class SomeComponent {
componentWillMount() {
if (typeof localStorage !== 'undefined') {
console.log('I get to here...', localStorage.getItem('someValue')) // this comes in as expected always
this.props.setMyReduxState(localStorage.getItem('someValue'))
}
}
render () {
// will have the value of the localStorage item someValue when navigated into the page
// will be an empty string if I do a hard refresh
console.log('this.props.myReduxState', this.props.myReduxState)
return (
<div>
Stuff...
</div>
)
}
}
const mapStateToProps = (state) => {
return {
myReduxState: state.something.myReduxState || ''
}
}
const mapDispatchToProps = (dispatch) => {
return {
setMyReduxState (someValue) {
dispatch(setMyReduxState(someValue))
}
}
}
Any ideas?
Edit: just a small addition to simplify the problem: I also tried it sending a string directly to setMyReduxState function, without the localStorage, the state still isn't being set. So basically something like this:
componentWillMount() {
this.props.setMyReduxState('some string!')
}
From my understanding every time the redux state is set, the component should re-draw, which isn't happening when there is a hard refresh. Are there any reasons for this or something being done incorrectly?
Edit2: Including my action creator and reducer in case needed:
const SET_MY_REDUX_STRING = 'admin/users/SET_MY_REDUX_STRING'
const defaultState = {
myReduxState: ''
}
export function setMyReduxState (value) {
return {
type: SET_MY_REDUX_STRING,
myReduxState: value
}
}
export default function reducer (state = defaultState, action = {}) {
switch (action.type) {
case SET_MY_REDUX_STRING:
return Object.assign({}, state, { myReduxState: action.myReduxState })
default:
return state
}
}
Some of the checklist you need to follow while using redux -
Are you dispatching an action creator that returns an object with 'type' and data? Here 'type' is mandatory.
Does your reducer return the state with the updated data that it received?
Make sure you do not mutate the state in reducer. Always use {...state, someKey: someValue}
I am using a immutable state in my reducer, initially i am dispatching 'REQUESTLOGINDATASUCCEEDED' event which calls state.set() it was refreshing my components, then i want to update the same state variable i dispatched "USERPREFERENCESUCCEEDED" and call the same state.set() my component is not refreshing now. PFB the code.
import { fromJS } from 'immutable';
const initialState = fromJS({
loginData : {}
});
function DataGridContainerReducer(state = initialState, action) {
switch (action.type) {
case 'REQUESTLOGINDATASUCCEEDED':
return state.set('loginData', action.loginData);
case 'USERPREFERENCESUCCEEDED':
return updateUserPreference(state, action.userPreference);
default:
return state;
}
}
function updateUserPreference(state, userPreference) {
debugger;
let loginData = state.get('loginData');
let searchDataIndex = 0;
loginData.userPreferences.splice(searchDataIndex, 1);
loginData.userPreferences.push(userPreference);
return state.set('loginData', loginData);
}
export default DataGridContainerReducer;
Please check and let me know what is the issue here.
Thanks in advance.
I'm using the pattern described here that show us how to reuse reducer logic for other similar purposes.
So, my reducer code is like the code below:
function ContentFilterReducer(entity = ''){
initialState.groupFilter = entity;
return function ContentFilterReducer(state = initialState, action)
{
// is the entity that we want to update?
if (action.item !== undefined && action.item.groupFilter !== entity)
return state;
switch (action.type) {
case ContentFilterTypes.ADD_ITEM:
return {
// we set the
groupFilter: action.item.groupFilter,
listObjects : state.listObjects.push(new Map({
id: action.item.id,
description: action.item.description,
imgSrc: action.item.imgSrc
}))
}
default:
return state;
}
}
}
My combinedReducer describe a reducer for each purpose, as we can see below:
const SearchReducers = combineReducers({
// contains all allowed filters to be selected
UsersContentFilterReducer : ContentFilterReducer(Types.users),
OrganizationsContentFilterReducer : ContentFilterReducer(Types.organizations)
})
Everything is working great, however I'd like to know, how to connect it in a React component using the connect function from React-Redux?
As we can see, I can define the reducer setting an entity (a simple char like 'a', 'o', etc) and, to call the specific reducer, I need only set the entity in my action. And now, the problem is how to connect a specific reducer for a specific presentational component?
The code below is my HOC container that connect the reducer to a specific component, however, the code is the old version, without defining wich reducer should call.
const mapStateToProps = (state, action) => {
return {
contentList: ContentFilterReducer(state.ContentFilterReducer, action)
}
}
/**
*
* #param {contains the action that will be dispatched} dispatch
*/
const mapDispatchToProps = (dispatch) => {
return {
onAddClick: (groupFilter, filterDescription, operator, value) => {
dispatch(AddFilter(groupFilter, filterDescription, operator, value));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ContentFilterField)
You don't connect a reducer. You connect a component to the Redux store. I won't name my state xxxReducer, it's a little bit confusing.
I'm not sure what your app looks like, for a simple case, you just need to: (connect both state)
const mapStateToProps = (state) => {
return {
userContentList: state.SearchReducers.UsersContentFilterReducer,
organizationContentList: state.SearchReducers.OrganizationsContentFilterReducer,
}
}
If you want to switch between usersContent and organizationsContent dynamically based on your component's state, what you need is a selector function.
This is the official redux example: https://github.com/reactjs/redux/blob/master/examples/shopping-cart/src/reducers/index.js#L10-L26
These functions are selectors, you import and use them to get the state you want.
So you will create something like getContentList and it accepts a type like Types.users
const mapStateToProps = (state) => {
return {
// suppose you save current type saved in SearchReducers.type
contentList: getContentList(state.SearchReducers.type)
}
}
Also, the second parameter of mapStateToProps is ownProps not action.