I've run into an odd issue, where my redux store seems to be returning a duplicate of a different value? (Still learning terms so sorry if I mixed them up!)
I have 2 states. Users, and Added. I want to show to lists, one using the data from each one of them. currently, fetchUsers works fine, but fetchAdded shows Users for an unknown reason so both lists show the same data.
If I switch fetchUsers to use refAdded then it shows Added, so now it only shows the added array in both lists. I figured that means the actual calls are working cause it can get the data from Firebase, but I don't know why this would happen.
FetchUsers which gets a list of users from firebase looks like this:
export function fetchUsers() {
return (dispatch) => {
refUsers.on('value', snapshot => {
dispatch({
type: 'FETCH_USER',
payload: snapshot.val()
});
});
}
}
FetchAdded looks like this:
export function fetchAdded() {
return (dispatch) => {
refAdded.on('value', snapshot => {
dispatch({
type: 'FETCH_ADDED',
payload: snapshot.val()
});
});
}
}
The reducers look like this:
export default function(state = [], action) {
switch (action.type) {
case 'FETCH_USER':
return [action.payload];
case 'ADDED_USER':
return [action.payload, ...state];
case 'MOVE_USER':
const newState = [...state];
newState.splice(action.payload.index, 1);
return newState;
case 'MOVE_ITEM':
return [action.payload.user, ...state];
default:
return state
}
}
and fetch Added is:
export default function(state = [], action) {
switch (action.type) {
case 'FETCH_ADDED':
return [action.payload];
case 'MOVE_ITEM':
const newState = [...state];
newState.splice(action.payload.index, 1);
return newState;
case 'MOVE_USER':
return [action.payload.user, ...state]
default:
return state
}
}
I combine them both here:
const rootReducer = combineReducers({
users: UserReducer,
added: AddedReducer
});
and my firebase client exporting looks like this:
firebase.initializeApp(config);
export const refUsers = firebase.database().ref("users")
export const refAdded = firebase.database().ref("added")
export const auth = firebase.auth
export const provider = new firebase.auth.GoogleAuthProvider();
In my actual page where I display the 2 lists, this is what I have:
function mapStateToProps(state) {
return {
users: state.users,
added: state.added
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addUser, moveUser, moveItem, fetchUsers, fetchAdded }, dispatch);
}
Related
I have a useEffect() hook that I'd like to only update when the data in a state-full array changes. I know I can add that array as a dependency, but my issue is the array is not declared in the same file, it's coming from a redux store. Right now I removed the dependency and It works, but the dev tools show me that it is constantly sending new requests to the server when it should send a request only when submitted.
This is the function in the redux store:
/client/src/store/utils/thunkCreators.js
export const fetchConversations = () => async (dispatch) => {
try {
const { data } = await axios.get("/api/conversations");
dispatch(gotConversations(data));
} catch (error) {
console.error(error);
}
};
How do I import the conversations from the redux store as a dependency?
Home.js
useEffect(() => {
fetchConversations();
},[X]);
/client/src/store/utils/conversations.js
const GET_CONVERSATIONS = "GET_CONVERSATIONS";
export const gotConversations = (conversations) => {
return {
type: GET_CONVERSATIONS,
conversations,
};
};
const reducer = (state = [], action) => {
switch (action.type) {
case GET_CONVERSATIONS:
return action.conversations;
case SET_MESSAGE:
return addMessageToStore(state, action.payload);
case ADD_ONLINE_USER: {
return addOnlineUserToStore(state, action.id);
}
case REMOVE_OFFLINE_USER: {
return removeOfflineUserFromStore(state, action.id);
}
case SET_SEARCHED_USERS:
return addSearchedUsersToStore(state, action.users);
case CLEAR_SEARCHED_USERS:
return state.filter((convo) => convo.id);
case ADD_CONVERSATION:
return addNewConvoToStore(
state,
action.payload.recipientId,
action.payload.newMessage
);
default:
return state;
}
};
I am like in a strange problem. The problem is that I am trying to make an API hit (in service file) which in turn provides some data (it is working), this data is to be updated in my reducer1.js and then returned. Now, my issue is though the value is coming in reducer file, but is not returned, so in turn, state is not changed, and in turn my end component is not rerendered.
Now, when my service file is successfully hitting and then returning data to my reducer1.js, why in the world the updated-state is not returned by "GET_List" action type? Can someone see any problem?
index.js (service file)
const global = {
getActressList: async function(){
const response = await fetch("http://localhost:2000/api/actressList");
const data = await response.json();
return data;
}
}
export default global;
reducer1.js
import global from '../../services/index';
const initialState = {
data: [
{
id: 1,
name: "Aishwarya Rai",
src: "/assets/img/aishwarya.png"
}
]
};
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
const data = global.getActressList();
data.then((res)=> {
return {
...state,
data: res
}
})
}
default:
return state;
}
}
export default reducer1;
Result:
You are returning from a promise not from a reducer function:
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
const data = global.getActressList();
data.then((res) => {
// here you are returning from a promise not from a reducer function
return {
...state,
data: res,
};
});
}
default:
return state;
}
}
The code in reducer should be sync like this:
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
return {
...state,
data: action.payload,
};
}
default:
return state;
}
}
And your data fetching should be moved to component effect like this:
function YourComponent() {
const dispatch = useDispatch();
const data = useSelector(state => state.data)
useEffect(() => {
const data = global.getActressList();
data.then((res) => {
dispatch({type: 'GET_LIST', payload: res});
});
}, [])
...
}
EDIT
If you use class components the fetching logic should be placed in componentDidMount lifecycle hook like this:
class YourComponent extends Component {
state = { data: [] };
componentDidMount() {
const data = global.getActressList();
data.then((res) => {
dispatchYourAction({type: 'GET_LIST', payload: res});
});
}
...
}
Im currently using redux to manage my state. The scenario is as such , Upon successful creation of a new object , i would like to append the response data into my existing state container as i don't wish to make a new API call to render it.
initial State:
const initialState = {
workflowobject:{},
};
SAGA:
export function* workerCreateTransitionApproval(action) {
const data = yield call(() => axiosInstance.post(`/transition-approval-meta/create/`, action.data))
yield put({ type: "STORE_WORKFLOW_DATA", payload: data.data.data, fetch: 'workflowobject' , label: 'transition_approvals'})
}
So over here , i upon recieving the "signal" so to speak to create a transition approval , i will catch that event and create make an axios post request to my backend , which will then return a response of the transition_approval . I will then store this transition_approval as the payload which i will use later on.
Reducer
const loadWorkflowObject = (state, action) => {
return updateObject(state, {
workflowobject: { ...state.workflowobject, [action.label]: action.payload }
})
}
const storeData = (state, action) => {
switch (action.fetch) {
case 'workflowobject': return loadWorkflowObject(state, action)
}
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'STORE_WORKFLOW_DATA': return storeData(state, action);
case 'CLEAR_CLASS_STATES': return clearClassStates(state, action);
case 'CLEAR_OBJECT_STATES': return clearObjectStates(state, action);
default:
return state;
}
}
export default reducer;
So in my reducer , it will first go into the case STORE_WORKFLOW_DATA which will then return the reducer function loadWorkflowObject . This is where i wish to 'append' the data back to the state tree.
The problem
The tricky part here is that im using this loadWorkflowObject reducer for fetching data too , and im already using the spread operator here.
The code that i have shown above will override my preexisting data that i have in the transition_approvals object , if possible , i would like to append the data in instead.
you can do this:
const loadWorkflowObject = (state, action) => {
return updateObject(state, {
workflowobject: { ...state.workflowobject, [action.label]: state. transition_approvals.concat(action.payload) }
})
}
If I fetch this array of restos with redux:
[{
res_id: Int,
res_name: String,
res_category: String,
res_category_id: Int,
city_id: Int
}]
My action looks something like this:
export const getrestos = () => {
const resData = await response.json();
dispatch({
type: GET_RESTOS,
payload: resData
});
};
};
export const setFilters = filterSettings => {
console.log(filterSettings);
return { type: SET_FILTERS, filters: filterSettings };
};
And this is my reducer:
import { GET_RESTOS, SET_FILTERS } from '../actions/restos';
const initialState = {
restoList: [],
filteredRestos: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_RESTOS:
return {
restoList: action.payload
}
case SET_FILTERS:
const appliedFilters = action.filters;
const updatedFilteredRestos = state.restoList.filter(resto => {
if (appliedFilters.cityID || resto.city_id) {
resto => resto.city_id.indexOf(cityID) >= 0
return { ...state, filteredRestos: updatedFilteredRestos };
}
});
return { ...state, filteredRestos: updatedFilteredRestos };
default:
return state;
}
};
I have touchable categorys in a page, and when i touch one i want to fetch the corresponding restos for that category and show them in a flatlist. Apart from that i want to have a search bar that when I type I want to show restos by res_name and/or by res_category.
Ive tried to create selectors, but I dont understand how, i dont need an specific approach, but the most clean or efficient as possible.
Thanks in advance if anyone can give me a hint or solution!
EDIT
The problem is im getting undefined in updatedFilteredRestos.
Your reducers should be clean, dumb and all they do should be returning objects. This makes your components more testable and errors easier to catch. In my opinion, this is a perfect use-case for reselect. Here's a medium article: https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c But the true beauty of reselect is that it will memoize for you, i.e. if your states don't change, it uses a cached version of the data.
Anyway, you should clean up your restoReducer to something to this effect.
import { GET_RESTOS, SET_FILTERS } = "../actions/restos";
const initialState = {
restoList: [],
filteredRestos: []
};
const restoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_RESTOS:
return { ...state, restoList: action.payload };
case SET_FILTERS:
return { ...state, filteredRestos: action.payload };
default:
return state;
}
}
Then write your filtered resto selector:
// ../selectors/restos
import { createSelector } from "reselect";
// First, get your redux states
const getRestos = (state) => state.restos.restoList;
const getFilteredRestos = (state) => state.restos.filteredRestos;
// Next, create selectors
export const getFilteredRestoList = createSelector(
[getRestos, getFilteredRestos],
(restoList, filteredRestos) => {
// need to check for non-empty filters
// if it is, simply return the unfiltered `restoList`
if(!Array.isArray(filteredRestos) || !filteredRestos.length)
return restoList || [];
// If you do have valid filters, return filtered logic
return restoList.filter(r => filteredRestos.some(f => f.cityID === r.city_id));
);
Then, use this selector in your components:
// ../components/my-app
import { getFilteredRestoList } from "../selectors/restos";
// hook it up to your `mapStateToProps` as you would a normal state
// except this time, it's a special selector
const mapStateToProps = (state, ownProps) => {
restoList: state.restos.restoList,
filteredRestos: state.restos.filteredRestos,
filteredRestoList: getFilteredRestoList(state) //<-- this is your selector
}
Then inside your component, just reference it: this.props.filteredRestoList.
I am trying to develop an application, that is showing photos from Unsplash given a keyword. I managed to fetch specific photos using unsplash.js:
actions:
export function fetchPhotos(term) {
const unsplash = new Unsplash({
applicationId:
"id",
secret: "secret",
callbackUrl: "callback"
});
const response = unsplash.search
.photos(term, 1, 20)
.then(toJson)
.then(json => json);
return {
type: FETCH_PHOTOS,
payload: response
};
}
export function setCategory(term) {
return {
type: SET_CATEGORY,
categories: [term]
};
}
export function sortPhotos(attribute) {
return {
type: SORT_PHOTOS,
attribute
}
}
Component that renders the photos:
import React, { Component } from "react";
import { connect } from "react-redux";
import SinglePhoto from "../components/SinglePhoto";
class PhotoList extends Component {
renderPhotos() {
const { photos } = this.props;
console.log(photos);
if (!photos) {
return <p>Loading...</p>;
}
return photos.map(photo => {
const url = photo.urls.full;
const id = photo.id;
const alt = photo.description;
return <SinglePhoto url={url} key={id} alt={alt} />;
});
}
render() {
return <div>{this.renderPhotos()}</div>;
}
}
function mapStateToProps(state) {
return {
photos: state.photos,
categories: state.categories
};
}
export default connect(mapStateToProps)(PhotoList);
And reducers:
import { FETCH_PHOTOS, SORT_PHOTOS } from "../actions/types";
export default function(state = [], action) {
switch (action.type) {
case FETCH_PHOTOS:
return [...action.payload.results];
case SORT_PHOTOS:
break;
default:
return state;
}
}
What I am struggling to do is to actually sort the array of data I receive from the API according to a specific term. The response is an array of objects that makes it impossible to call it in an external component I've called Buttons that I have wanted to set the logic in:
class Buttons extends Component {
render() {
const { created_at: date } = this.props.photos;
console.log(this.props);
return (
<div className="buttons">
{/* <button onClick={() => this.props.sortPhotos(date)}>Sort by creation date</button> */}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
photos: state.photos
}
}
const mapDispatchToProps = (dispatch) => bindActionCreators({sortPhotos}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Buttons);
As I would need to loop over the photos to actually receive their created_at props.
I would like to sort them, for example, taking created_at into account. This would be handled by a button click (there would be other buttons for let's say likes amount and so on). I tried to do this in mapStateToProps until the moment I realized it would be impossible to call this with onClick handler.
As I have read this post, I thought it would be a great idea, however, I am not sure, how can I handle this request by an action creator.
Is there any way that I could call sorting function with an onclick handler?
One approach you can take is using a library such as Redux's reduxjs/reselect to compute derived data based on state, in this case sorted items based on some object key and/or direction. Selectors are composable and are usually efficient as they are not recomputed unless one of its arguments changes. This approach is adding properties to the reducer's state for sort key and sort order. As these are updated in the store via actions/reducers, the selector uses state to derive the elements in the resulting sorted order. You can utilize the sorted items in any connected component.
I've tried my best to recreate a complete example including actions, reducers, selectors, and store structure.
Actions - Created actions for setting sort key/direction. My example is using redux-thunk for handling async actions, but that is in no way necessary:
export const SET_SORT = 'SET_SORT';
const setSort = (sortDirection, sortKey) => ({
type: SET_SORT,
sortDirection,
sortKey
});
export const sort = (sortDirection = 'desc', sortKey = 'created_at') => dispatch => {
dispatch(setSort(sortDirection, sortKey));
return Promise.resolve();
};
Reducer - Updated initial state to keep track of a sort key and/or sort direction with photo objects being stored in a child property such as items:
const initialState = {
isFetching: false,
sortDirection: null,
sortKey: null,
items: []
};
const photos = (state = initialState, action) => {
switch (action.type) {
case FETCH_PHOTOS:
return {
...state,
isFetching: true
};
case RECEIVE_PHOTOS:
return {
...state,
isFetching: false,
items: action.photos
};
case SET_SORT:
return {
...state,
sortKey: action.sortKey,
sortDirection: action.sortDirection
};
default:
return state;
}
};
Selector - Using reselect, create selectors that retrieves items/photos, sortOrder, and sortDirection. The sorting logic can obviously be enhanced to handle other keys/conditions/etc:
import { createSelector } from 'reselect';
const getPhotosSelector = state => state.photos.items;
const getSortKeySelector = state => state.photos.sortKey;
const getSortDirectionSelector = state => state.photos.sortDirection;
export const getSortedPhotosSelector = createSelector(
getPhotosSelector,
getSortKeySelector,
getSortDirectionSelector,
(photos, sortKey, sortDirection) => {
if (sortKey === 'created_at' && sortDirection === 'asc') {
return photos.slice().sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
} else if (sortKey === 'created_at' && sortDirection === 'desc') {
return photos.slice().sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
} else {
return photos;
}
}
);
Component - Utilize selector to render items. Trigger dispatch of sort action via button click passing in a sort key and/or sort order. The linked example uses dropdowns in combination with the button click to set sort key/order:
import { getSortedPhotosSelector } from './selectors';
// ...
handleClick() {
this.props.dispatch(sort('desc', 'created_at'));
}
render() {
const { sortDirection, sortKey, items } = this.props;
<ul>
{items.map(item => <li key={item.id}>{item.created_at}</li>)}
</ul>
<button type="button" onClick={this.handleClick}>SORT</button>
}
const mapStateToProps = state => ({
items: getSortedPhotosSelector(state),
sortKey: state.photos.sortKey,
sortDirection: state.photos.sortDirection
});
export default connect(mapStateToProps)(PhotoList);
Here is a StackBlitz, demonstrating the functionality in action. It includes controlled components such as and to trigger dispatch of a sort action.
Hopefully that helps!