Store doesn't update after GET - reactjs

After DELETE a resource via a Button I update the same resource with a GET, i get the response with the correct data but the redux store doesn't update (apparently the redux action see no diff), and so the props are not updated.
But when I do a CREATE and then a GET like before (same call) this time the redux store see the diff and update the props.
The resource are the same between the 2 calls (after DELETE or CREATE), and the calls are even the same. Why in one case the redux store see the diff and don't in another case ?
crudCreate(`${CAMPAIGNS}/${id}/${LINE_ITEMS}`, data, () =>
crudGetAll(LINE_ITEMS, {}, { campaignId: id }, 1000, () => this.displayModal()),
);
crudDelete(LINE_ITEMS, lineItem.id, { id }, lineItem, () =>
crudGetAll(LINE_ITEMS, {}, { campaignId: id }, 1000, ({ payload: { data } }) =>
this.updateLineItems(data),
),
);
I don't any error on Redux debugger or on Console. I have a custom DataProvider, in that case it only redirect for the good routes
case LINE_ITEMS: {
if (type === 'DELETE') {
return { urn: `${apiUrl}/campaigns/${filter.id}/${resource.toLowerCase()}/${id}` };
}
if (filter) {
if ('campaignId' in filter) {
return {
urn: `${apiUrl}/campaigns/${filter.campaignId}/${resource.toLowerCase()}`,
filter: {
...filter,
campaignId: undefined,
},
};
}
}
return {
urn: `${apiUrl}/campaigns/${id.campaignId}/${resource.toLowerCase()}/${data.lineItemId}`,
};
}
And I only use the reducer from react-admin, here my index.js:
import { connect } from 'react-redux';
import { crudDelete, crudGetAll, translate } from 'react-admin';
export default connect(
state => ({
lineItems: Object.values(state.admin.resources.lineItems.data) || {},
swordData: Object.values(state.admin.resources.sword.data),
}),
{ crudDelete, crudGetAll },
)(withRouter(WithPermissions(translate(MyComponent))));
Does anybody have an idea? Thanks for your help

Related

update values in the Reducer in Redux

i got two values i.e.company and id from navigation.
let id = props.route.params.oved;
console.log("id-->",id);
let company = props.route.params.company;
console.log("company--->",company);
i got two values as a integer like this:--
id-->1
comapny-->465
Description of the image:---
if i am giving input 1 in that textInput and click on the card(lets say first card i.e.465 then i am getting those two values in navigation as in interger that i have mention above.so each time i am getting updated values.
i am getting updated values from navigation.
so i want to store those values in redux.
action.js:--
import { CHANGE_SELECTED_COMPANY } from "./action-constants";
export const changeCompany = (updatedCompany, updatedId) => {
return {
type: CHANGE_SELECTED_COMPANY,
updatedCompany,
updatedId,
};
};
reducer.js:--
import { CHANGE_SELECTED_COMPANY } from "../actions/action-constants";
const initialState = {
company: "",
id: "",
};
const changeCompanyReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_SELECTED_COMPANY:
return {
company: {
company: action.updatedCompany,
id: action.updatedId,
},
};
}
return state;
};
export default changeCompanyReducer;
congigure-store.js:--
import changeCompanyReducer from "./reducers/change-company-reducer";
const rootReducer = combineReducers({changeCompanyReducer});
How can i store the update values getting from navigation in Redux?
could you please write code for redux??
in the component create a function that updates the values
const updateReducer = () => {
dispatch(changeCompany(props.route.params.oved, props.route.params.company))
}
then call the function in react navigation lifecycle event
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
updateReducer()
});
return unsubscribe;
}, [navigation])
its possible that a better solution would be to update the reducer before the navigation happens and not pass the data in the params but rather pull it from redux but this is the answer to the question as asked

My redux thunk Axios calls return empty objects in my Redux store

I am using tmdb and redux-thunk to make async calls and return the data. I've tried every way possible and only empty objects show up in my store even though when I log the results I see the data there.
I tried to map through the results with no luck. If I return it by the index for example (example[0]) it does show the results of the first index.
Screenshots of store and console - https://imgur.com/a/zrv0Sjm
export const fetchVideoKeys = urlArray => {
return dispatch => {
dispatch(isLoading(true));
axios
.all(urlArray)
.then(
axios.spread((top, pop, up, now) => {
console.log(top)
dispatch(getVideoKeys(top));
})
);
};
};
const initialState = {
videoKeys: {
topRated: [],
popular: [],
upcoming: [],
nowPlaying: [],
}
};
export default function VideoTrailerReducer(state=initialState, action) {
switch (action.type) {
case VideoTrailerActionTypes.GET_VIDEO_KEYS:
return {
videoKeys: {
topRated: [action.payload]
}
}
default:
return state;
}
}
useEffect(() => {
movieIds.popular.length > 1 &&
movieIds.topRated.length > 1 &&
movieIds.upcoming.length > 1 &&
movieIds.nowPlaying.length > 1 &&
setTimeout(() => {
dispatch(
fetchVideoKeys([
createUrls(movieIds.topRated, videoUrls),
createUrls(movieIds.popular, videoUrls),
createUrls(movieIds.upcoming, videoUrls),
createUrls(movieIds.nowPlaying, videoUrls)
])
);
}, 1000);
}, [
movieIds.topRated,
movieIds.popular,
movieIds.nowPlaying,
movieIds.upcoming
]);
export const getVideoKeys = data => {
return {
type: VideoTrailerActionTypes.GET_VIDEO_KEYS,
payload: data
}
}
I expect the store to show the results but is only returning empty objects.
Based on your screengrab, I expect you wish to dispatch data.results from your ajax response. Right now you are dispatching the promise.
Try playing around with console.log(top.data.results)
Your code example assumes that your url list will have just 4 array items, so where you have this:
axios.spread((top, pop, up, now) => {
top will be the response from the first url passed.
A codepen calling your api might help.
[Also you're exposing your api key in the image]

React/Redux and API data object

My app successfully gets API data and puts it to Redux state tree.
{
"coord":{
"lon":-0.13,
"lat":51.51
},
"weather":[
{
"id":311,
"main":"Drizzle",
"description":"drizzle rain",
"icon":"09d"
},
{
"id":501,
"main":"Rain",
"description":"moderate rain",
"icon":"10d"
}
],
//--------
//--------
"id":2643741,
"name":"London",
"cod":200
}
Props.data has been passed to components but in reality I have an access only to the
first key. For example props.data.name, props.data.id are accesiible. But props.data.coord.lon
and props.data.weather.map(---), are undefined.
Please, what's wrong with my understanding of using API dataset?
Component
export const DayItem = (props) => {
return (<MuiThemeProvider>
<Paper zDepth={2}>
{props.data.coord.lon} // No way!
{props.data.name} // OK!
</Paper>
</MuiThemeProvider>)}
Saga that gets data and dispatches an action. Puts data to Redux store.
function* getPosition() {
const getCurrentPosition = () => new Promise(
(res, rej) => navigator.geolocation.getCurrentPosition(res, rej))
// Gets user's current position assigned to const
const pos = yield call(getCurrentPosition);
const {latitude, longitude} = pos.coords;
// Yields the forecast API by user coordinates
const data = yield call(getForecastByCoords, latitude, longitude)
// Yields user's local forecast to the reducer
yield put({
type: LOAD_DATA_SUCCESS,
data
});
}
And mapStateToProps
function mapStateToProps(state) {
return {
chips: state.chipsReducer,
data: state.dataReducer
}
};
dataReducer
export const dataReducer = (state = {}, action) => {
switch (action.type) {
case LOAD_DATA_SUCCESS:
return action.data;
default:
return state;
}
};
Eventually, I got the point.
The problem was in the difference of speed React rendering vs Data loading.
Loading is always behind rendering. So, the complete set of data had no existence.
Just conditional rendering made my day {this.state.isLoading ? <div>Loading</div> : <DayItem {...this.props}/>}
you must use MapStateToProps, then use componentWillReceiveProps(nextProps)
Something like this
function mapStateToProps(state) {
return {
data:state.toJS().yourReducer.data,
}
then do next:
componentWillReceiveProps(nextProps) {
if (nextProps.data) {
if (nextProps.data.coord.lon != undefined) {
this.setState({yourData:nextProps.data.coord.lon}
}
}

Apollo update after a mutation isn't triggering a rerender

I am having troubles with a mutation in graphQL apollo. When a page loads, it will run a query lectureResponseQuery and if the query == null a mutation fetchLectureResponseMutation is run to create a new document. This mutation returns the new result and I do an update to the query and I expect that the component will re-render with the new data, but it doesn't. Does anyone know why that is? Thanks!
#graphql(fetchLectureResponseMutation, {
options: ownProps => ({
variables: { lectureName: ownProps.match.params.lectureName },
update: (proxy, { data: { fetchLectureResponse } }) => {
const data = proxy.readQuery({
query: lectureResponseQuery,
variables: { lectureName: ownProps.match.params.lectureName },
});
data.lectureResponse = fetchLectureResponse;
proxy.writeQuery({
query: lectureResponseQuery,
data,
});
},
}),
name: 'fetchLectureResponse',
})
#graphql(lectureResponseQuery, {
options: ownProps => ({
variables: { lectureName: ownProps.match.params.lectureName },
}),
})
class LecturePage extends React.PureComponent {
componentWillUpdate(nextProps) {
if (nextProps.data.lectureResponse === null) {
this.props.fetchLectureResponse();
}
}
render() {
const { data } = this.props;
if (data.loading || data.lectureResponse === null) {
return <Loading />;
}
return <LectureLayout lectureResponse={data.lectureResponse} />
}
}
For anyone looking into this issue in the future- the central issue is that I wanted to do a find OR create operation. This works much better when the query just returns the new object if it doesn't exist because then you only make 1 backend call which means that you don't have to synchronize the timings between a query and a mutation.
TLDR: Use a query for a findOrCreate operation!

React Redux how to store http response in Reducer

I have a datatable with "View" Link. Upon clicking, it must fetch data from the backend. But I am having troubles storing the response in React-Redux style.
This is the snippet of the datatable:
$("#personTable").dataTable({
"columns" : [
{ "data" : "id", "name":"id" ,
"render": (data, type, full,meta) => {
return 'View;
}
}
In my routes.jsx, I defined it to forward to PersonForm.jsx
<Route name='personForm' path='/person/view' component={PersonForm}/>
In my PersonForm component, I have this:
componentWillMount() {
let personId = this.props.location.query.id
this.props.onInit(personId)
}
In my PersonFormContainer.jsx:
export const mapDispatchToProps = (dispatch) => {
return {
onInit: (personId) => {
dispatch(init(personId))
}
}
}
This is my PersonActions.jsx:
export function init(personId) {
return function (dispatch) {
httpService.request('/person/view/' + personId, {method: 'get'})
.then((response) => {
console.log(response.data) // response.data is correctly returned
dispatch({
type: "PERSON_INIT",
data: response.data
})
}).catch(err => console.log(err))
}
}
In my PersonReducer.js:
var Immutable = require('immutable');
let initialState =
Immutable.fromJS({
data: {},
fields: {
name: field.create('name', [validation.REQUIRED])
}
})
export default function (state = initialState, action) {
switch(action.type) {
case PERSON_INIT:
return state.set("data", action.data)
//return state.set("fields", Immutable.fromJS(action.fields))
default:
return state
}
}
Now, the problem is back to my PersonForm.jsx. Upon calling render(), data (in my reducer) has some values, but not the fields. I am not sure how to transform the response.data (in my PersonActions) to the fields in my Reducer. Something like this:
if (data) {
for (let key in fields) {
fields[key].value = data[key]
}
}
This is my PersonForm component:
render() {
let store = this.props.person
let fieldsMap = store.get("fields")
<ImmutableInputText label="Name" field={fieldsMap.get("name")}/>
Note: ImmutableInputText is from our templates, something like:
<input id={field.name} className="form-control" name={field.name}
value={this.state.value} onBlur={this.handleBlur} onChange={changeHandler}
disabled={disabled}/>
I am trying to answer this without knowing the structure of the response object, So i will update this answer based on your response.
For now, let's assume this is the response you get from server
{
"code": 200,
"message": "success",
"person": {
"name": "John Doe",
"email": "john.doe#gmail.com",
"dob": "1980-01-01",
"gender": "male",
"status": "active",
}
}
In your PersonReducer.js you can do something like
export default function (state = initialState, action) {
switch(action.type) {
case PERSON_INIT:
return state.set("fields", Immutable.fromJS(action.data.person) )
default:
return state
}
}
but doing this will replace the existing fields object with the data received from server.
If you want to keep all the existing data and only update the data that has been changed or is new.., you can do something like
case PERSON_INIT:
return state.set("fields", Immutable.fromJS({ ...state.get('fields').toObject(), ...action.data.person }) )
If you only want to update the name field, you can do something like
case PERSON_INIT:
return state.setIn(['fields', 'name'], action.data.person.name );
But then you will have to do this for every field and that wont be very effective, So you can make this dynamic by doing
in PersonActions.jsx file (or wherever you want this dynamic field update functionality), you can change the dispatch code to
dispatch({
type: "PERSON_UPDATE_FIELD",
payload: { field: 'name', value: response.data.person.name }
})
Note: I used payload here instead of data, I think its best to follow redux naming conventions, but there's nothing wrong in what you're doing as long as you stick with the same conventions throughout your app.
And in your PersonReducer.js you can have something like
case PERSON_UPDATE_FIELD:
return state.setIn(['fields', action.payload.field ], action.payload.value );
Good luck.

Resources