I am building a form where in some instances form elements are injected from an AJAX call (Duplicate text input for example).
Everything is working great and updating my form however I can't seem to get any default values back into the initial form state in my redux store. Below is my custom reducer that keeps track of the form elements. Can I push my new values into the initial state again?
//Schema Reducer
case "UPDATE_SCHEMA_FULFILLED":{
let s = {...state.schema}
for (let key in action.payload){
if(s.hasOwnProperty(key)){
if(key == 'values'){
s[key] = {...s[key], ...action.payload[key]}
}else{
s[key] = [...s[key], ...action.payload[key]]
}
}
}
state = { ...state,
loaded: true,
schema: {...s},
}
break;
}
My form is adding the initial values on first load as per the docs:
CustomForm = connect(
state => ({
initialValues: state.schema.schema.values
}),
dispatch => ({
onSubmit: data => dispatch(saveForm(data))
})
)(CustomForm)
This is what is generating the action:
import React from 'react'
import { addSchema } from '../actions/schemaActions'
export default class VirtualButton extends React.Component {
constructor(){
super();
this.generateNewLayout = this.generateNewLayout.bind(this)
}
generateNewLayout(e){
e.preventDefault();
this.props.dispatch(addSchema(this.props.owner));
}
render(){
return <div className="cf__virtual-action"><a href="" onClick={this.generateNewLayout}>Create New</a></div>
}
}
This seems to be working but I'm not sure if it's performant? Adding the initialize values function to the actions via props:
//My button that dispatches the initial call:
this.props.dispatch(addSchema(this.props.owner, this.props.initialize, this.props.initialValues));
export function addSchema($id, initialize, initial){
return function(dispatch) {
axios.post(config.api+'forms/schema/virtual/'+$id)
.then((response) => {
dispatch({type: 'UPDATE_SCHEMA_FULFILLED', payload: response.data})
initialize({...initial, ...response.data.values});
})
.catch((error) => {
console.log(error);
})
}
}
Related
I am trying to create a component that allows detecting changes in the redux store. Once the shouldUpdateData flag is set in the store, the component responsible for updating should fetch the data by using an async action creator. In my case, either the error "Maximum updates have reached" occurs or the update never happens.
Depending on the dispatch function stopFetching() (turns off the shouldUpdateData flag), the error or outcome changes. If I do the dispatch inside the action creator there are endless updates. If the code is used as it is below, no update occurs.
The reason I used the useSelector() hook from 'react-redux' is to detect a change in the store for the loading attribute.
Thank you in advance.
Here is the action creator:
export function updateDataAsync(id) {
return function (dispatch) {
// dispatch(fetchDataRequest());
return fetch(`/api/user/${id}/data`, {
method: "GET",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(
(result) => {
let {projects, notes} = result;
// New data and dispatch function
dispatch(fetchDataSuccess({projects, notes}));
},
(error) => { dispatch(fetchDataFailure(error)) }
)
}
}
Here is the reducer for this action creator:
export function savedData(state = DATA_INITIAL_STATE, action) {
switch(action.type) {
case FETCH_STATES.FETCH_DATA_REQUEST:
return {
...state,
loading: true
}
case FETCH_STATES.FETCH_DATA_SUCCESS:
return {
loading: false,
data: action.data,
error: ''
}
case FETCH_STATES.FETCH_DATA_FAILURE:
return {
loading: false,
data: {},
error: action.error.message
}
default:
return state;
}
}
The React component that is doing the update:
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
let reqSent = useRef(false);
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
})
return loading ? <LoadingAnimation /> : children;
}
const mapStateToProps = (state) => {
return {
userId: state.user.id,
shouldUpdate: state.shouldUpdateData // The flag that should trigger the update
}
}
const mapDispatchToProps = (dispatch) => {
return {
stopFetch: () => { dispatch(setShouldFetchData(false)) },
update: (id) => { dispatch(updateDataAsync(id)) },
startFetch: () => dispatch(fetchDataRequest()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(StoreUpdater);
You dint pass any dependency to useEffect so it will be called on every render which is causing infinite renders
change useEffect to
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
},[])
For complete information regarding useEffect refer this link
The reference I created inside the component responsible of the updates, was causing the problem. The reference was preventing the update dispatch to occur due to the if statement being false.
mapStateToProps and mapDispatchToProps were react-redux higher order functions to connect classes components into the store. there equalants at functional components are useSelector and useDispatch. re-write your HOC redux adaption into hooks, and add [ dependency ] at useEffect usage
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
const userId = useSelector(state => state.user.id);
const shouldUpdate = useSelector(state => state.shouldUpdateData);
let reqSent = useRef(false);
const dispatch = useDispatch() // import from 'react-redux'
useEffect(()=>{
if(!reqSent && shouldUpdate) {
dispatch(startFetch());
dispatch(update(userId));
reqSent.context = true;
}
}, [reqSent, shouldUpdate, startFetch, dispatch, update, userId])
return loading ? <LoadingAnimation /> : children;
}
export default StoreUpdater ;
My react component is not re-rendering despite its props being updated and I don't understand why.
Here's my component
import { fetchLocations } from 'state/locations/actions';
class Event extends React.Component {
componentDidMount() {
this.props.fetchLocations();
}
render() {
const { locations } = this.props;
return <span>{locations.map((l) => {return <span>{l}</span>;})}</span>;
}
}
const mapStateToProps = (state) => ({
locations: state.locations
})
export default connect(
mapStateToProps,
{ fetchLocations },
)(Event);
Here is my locations action file
export const fetchLocations = () = (dispatch) => {
axios.get('/api/locations')
.then(response => {
const locations = response.data;
dispatch({ type: FETCH_LOCATIONS_SUCCESS, payload: locations });
});
}
And my entities reducer
function entities(state = { locations: {} }, action) {
switch (action.type) {
case FETCH_LOCATIONS_SUCCESS:
return Object.assign({}, state, {
locations: action.payload
})
default:
return state
}
}
After this, my Event component should re-render. It doesn't. Using the react dev tools chrome extension I see that the locations are indeed there as props, but they do not show on the UI.
If I unmount the component by going to a different page and re-mount it, the locations show up properly.
It looks like everything works fine except the re-render is not triggering. componentDidUpdate is never fired.
If I manually do a setTimeout to forceUpdate an arbitrary second later, they show up.
Why isn't my component re-rendering?
Please, try to add key prop to span element of the render method. locations.map((l,key)=> <span key={key} >{l} </span>
I have a component that makes an API call and then updates the state through a reducer. The problem is, this doesn't work so well cause the data don't get updated in the component, it's like the react didn't notice a state change a never re-rendered the component, but I'm not sure if that's the real issue here. So the component looks like this:
class MyComponent extends Component {
componentDidMount() {
// ajax call
this.props.loadData(1);
}
render() {
return (
<Grid>
<MySecondComponent
currentData={this.props.currentData}
/>
</Grid>
);
}
}
const mapStateToProps = state => ({
reducer state.myReducer,
currentData: state.myReducer.currentData
});
const mapDispatchToProps = dispatch => {
return {
loadData: () => {
HttpClient.getData(id, (data) => {
dispatch(
action_loadCurrentData(
data
)
);
});
},
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
I am doing 2 things here: issuing an API call as soon as component is mounted, and then after data is fetched, dispatching action_loadCurrentData
This action looks like this:
//Action
export function action_loadCurrentData(
data
) {
return {
type: 'LOAD_CURRENT_DATA',
payload: {
currentData: data,
}
};
}
and the reducer:
//Reducer
const defaultState = {
};
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
state = {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
export default myReducer;
So the issue here is that the this.props.currentData that I'm passing to MySecondComponent will end up empty, as if I didn't set the data at all. However, If I stop the execution in the debugger and give it a few seconds, the data will be populated correctly, so I'm not sure what I'm doing wrong here?
Don't reassign state, return the newly created object instead
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
return {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
Your reducer needs to return the new state object, which needs to be a different instance from the previous state to trigger components update.
According to redux documentation:
The reducer is a pure function that takes the previous state and an action, and returns the next state.
And
Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
I have a variable attackHero whose initial value is Ironman. Its showing correctly on the page. On button click I am changing it to Hulk via Redux dispatch. But the change is not reflecting in the page. Am I missing something here?
Please see the code below.
import React, { Component } from 'react';
import { createStore } from 'redux';
class ReduxDemo extends Component {
constructor(props) {
super(props);
this.initNukeAttack = this.initNukeAttack.bind(this);
this.store = null;
}
initNukeAttack() {
console.log("I am inside initNukeAttack");
this.store.dispatch({type:"GREEN-ATTACK", hero: "Hulk"});
}
render() {
let attackHero = "attack hero";
// step 2 -> reducer => require state and action
const reducer = function(state, action) {
if(action.type === "ATTACK") {
return action.hero;
}
if(action.type === "GREEN-ATTACK") {
return action.hero;
}
return {p:"peace"};
}
//Step 1
this.store = createStore(reducer, "PEACE");
//step 3 -> subscribe
this.store.subscribe(() => {
//console.log("Store is now", store.getState());
//console.log(store.getState());
attackHero = this.store.getState();
})
//step 4 -> dispatch action
this.store.dispatch({type:"ATTACK", hero: "Ironman"});
//this.store.dispatch({type:"GREEN-ATTACK", hero: "Hulk"});
return(
<div>
<div>{attackHero}</div>
<button onClick={this.initNukeAttack}>Initiate Green Attack</button>
</div>
);
}
}
export default ReduxDemo;
The rendered screen look like this.
Hi first i would recommend to properly follow react with redux with middleware for action cretors. There are abundant resources available.
Anyways you were dispatching an action in render which is wrong. Second, for updating the the variable you need to setState to re-render your component.
Here is your working code :
class ReduxDemo extends Component {
constructor(props) {
super(props);
this.initNukeAttack = this.initNukeAttack.bind(this);
this.store = null;
this.state = {
attackHero: "IronMan"
};
}
initNukeAttack() {
console.log("I am inside initNukeAttack");
this.store.dispatch({ type: "GREEN-ATTACK", hero: "Hulk" });
}
render() {
// step 2 -> reducer => require state and action
const reducer = function(state = "ironman", action) {
if (action.type === "ATTACK") {
return action.hero;
}
if (action.type === "GREEN-ATTACK") {
return action.hero;
}
return state;
};
//Step 1
this.store = createStore(reducer, "PEACE");
//step 3 -> subscribe
this.store.subscribe(() => {
//console.log("Store is now", store.getState())
const attackHero = this.store.getState();
this.setState({
attackHero
})
});
//step 4 -> dispatch action
//this.store.dispatch({ type: "ATTACK", hero: "Ironman" });
// this.store.dispatch({type:"GREEN-ATTACK", hero: "Hulk"});
return (
<div>
<div>{this.state.attackHero}</div>
<button onClick={this.initNukeAttack}>Initiate Green Attack</button>
</div>
);
}
}
But the change is not reflecting in the page
this is because React is not re-rendering the page.
To re-render you need to use state and set the state variable via setState method.
or
you need to force render the page like this.forceUpdate(); after you set the state variable
I am new to redux and react. I have React container and component which gets data from the api request call. My question in basically, what is the best way to handle asyc functons of redux in react. I need help to get the data in react component.
Container.js: (incomplete, here I need help)
class Container extends React.Component {
state = {
userList: ''
}
componentDidMount() {
this.props.loadUserDetails();
}
render() {
return (
<div className="app">
<Component userList={this.state.userList}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
userList: state.auth.userList
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
loadUserDetails
}, dispatch);
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(Container));
Componet.js: (Pure component, here I need to render the data)
class Component extends React.Component {
render() {
return (
<div className="component">
{this.props.userList}
</div>
);
}
}
modules/auth/index.js
export const loadUserDetails = () => {
return dispatch => {
dispatch({
type: types.LOAD_USER_REQUEST
});
request.get('/api/v1/auth', dispatch)
.then(({ data }) => {
if (data.success) {
dispatch({
type: types.LOAD_USER_SUCCESS
payload: data.data
});
} else {
dispatch({
type: types.LOAD_USER_FAILURE,
payload: data.message
});
}
})
.catch((err) => {
dispatch({
type: types.LOAD_USER_FAILURE,
payload: 'Something went wrong, please try again.'
});
});
};
};
modules/auth/actions.js:
export const LOAD_USER_REQUEST = 'auth/LOAD_USER_REQUEST';
export const LOAD_USER_SUCCESS = 'auth/LOAD_USER_SUCCESS';
export const LOAD_USER_FAILURE = 'auth/LOAD_USER_FAILURE';
modules/auth/reducers.js:
state ={
loading: false,
error: null,
userList: null
}
case types.LOAD_USER_REQUEST:
return Object.assign({}, state, {
loading: true
});
case types.LOAD_USER_REQUEST:
return Object.assign({}, state, {
loading: false,
userList: payload,
});
case types.LOAD_USER_REQUEST:
return Object.assign({}, state, {
loading: flase,
error: payload
});
I actually need help to get the userList in Container and pass it to the Component. because it's an asyc function I am not able to get the data in Container before Component renders. How to handle such situations?
As I am passing userList in Child component, for the first time I don't have the userList data. So my problem is with the cycles of Reactjs, Where should I call loadUserList ? In componentDidMount? Or componentDidUpdate? If so, how can I get the data?
I am not able to render the userList it's value is null when the Component mounts. How to solve this?
a good option is to make use of redux-saga, easy and simple to implement:
https://github.com/redux-saga/redux-saga