Component works even with state mutation redux - reactjs

I know it can sound weird, but I would like to create an incoherent state in my Redux application as an example for students, and generate a bug.
So I made a state mutation on purpose but the component render correctly and everything is fine !
My example is just a simple counter app, you click on the button and the value displayed will be incremented.
My action :
export function incrementCounter() {
return {
type: INCREMENT_COUNTER,
};
}
My reducer with state mutation :
export default function ressourceReducer(state = initialState, action) {
switch (action.type) {
case INCREMENT_COUNTER:
// here it is ( redux invariant even throw and error if activated )
state.counter++;
return {
counter: state.counter
}
}
return state;
}
My component :
const mapStateToProps = state => ({
counter: state.ressource.counter,
});
Root reducer :
const rootReducer = combineReducers({
ressource: ressourceReducer
});
Problem, the props this.props.counter is coherent, no rendering problem in the class...
Can you help to make a state mutation that will have a real impact ?

Related

Redux state has changed, why doesn't it trigger a re-render? (Redux-saga)

I'm using react + redux + redux saga
I'm facing the issue that when I'm rendering the page (GET call)
The calls should be like:
constructor
render()
componentDidMount
render()
But I'm just reaching up to componentDidMount, mapDispatchToProps is dispatching the action, API call is working by which getting the response from the server and the data is updated into the state.
BUT somewhere it gets lost and my component is not even re rendering.
Up to the reducer, I'm getting the data where I'm returning action.items.
itemReducer.js
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_ALL_ITEMS_SUCCESS:
console.log("itemReducer-----", action.items); //getting the data over here
return action.items;
default:
return state;
}
};
itemPage.js (component)
class ItemsPage extends React.Component {
componentDidMount() {
this.props.loadItems();
}
render() {
const { items } = this.props; // not even it renders, so not getting data
...
return (<div>...</div>);
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
};
};
const mapDispatchToProps = (dispatch) => {
return {
loadItems: () => dispatch(loadAllItemsAction()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ItemsPage);
Please give some suggestions, Thanks in advance :D
There isn't issue in the code that you posted. To make sure I pretty much copy pasted the code you shared, filled in the missing parts and it works just fine:
https://codesandbox.io/s/3tuxv?file=/src/index.js
My guess what might be wrong is that your items are not stored in state.items but under some different path or you might be missing the react-redux Provider, but it is impossible to say for sure without seeing more of your code.
You need to understand that the calls of render/componentDidMount are not so linear as it could be expected. componentDidMount fires when all children were mount. And it doesn't means that render() was finished already.
Read here for more info:
https://github.com/facebook/react/issues/2659

React Redux store update doesn't trigger component rerender

I'm new in Redux and have a problem with rerendering after the store changed. I have found many similar problems here on SO but still can't solve my issue.
I have a monthly task(event) calendar with multiple tasks. The Calendar is the main component and some level deeper there are multiple TaskItem components. At the first render, the calendar and the tasks are rendered fine (In this case without employee names). In the Calendar component I trigger loading employees with a useEffect hook. I can see the network request on my console. Besides this, the console logs in the action, and in the reducer also show the employee list. And the Redux devtool also shows the loaded employees. Still the mapStateToProps on TaskItem shows a completly empty state.
What I'm doing wrong?
Here is my related code:
Calendar:
const Calendar = ({startDay, tasks, loadEmployeesAction}) => {
useEffect(()=>{
loadEmployeesAction();
},[]);
...
}
export default connect(null, {loadEmployeesAction})(Calendar);
TaskItem:
const TaskItem = ({task, onTextEdit, onTaskView, saveTask, employees }) => {
...
}
const mapStateToProps = (state) => {
console.log('Actual state is: ', state);
return {
employees: state.employees
}
}
export default connect(mapStateToProps)(TaskItem);
Reducer:
export const employeeReducer = (state = [], action) => {
switch (action.type) {
case actionType.EMPLOYEES_LOADED:
console.log('Reducer - Employees loaded:', action );
return action.payload.employees;
default :
return state;
}
}
Actions:
const employeesLoaded = (employees) => {
return {type: actionType.EMPLOYEES_LOADED, payload: {
employees
}
}
}
export const loadEmployeesAction = () => {
return (dispatch) => {
return employeeApi.getAllEmployees().then(emps => {
console.log('Action - Employees loaded: ', emps);
dispatch(employeesLoaded(emps));
})
}
}
Root reducer:
export const rootReduxReducer = combineReducers({
employees: employeeReducer
});
I found the error. It was a very clumsy mistake.
All of my posted code was fine, but I put the store creation in a component that was rerendered again and again so my store was recreated again and again.
The reducer code seems to be not as the redux pattern. So usually the state object is not directly replaced with a different object. Instead only the part of the state that needs to be changed is only with some non-mutating operation like spread operator.
So I think the reducer code should be changed like
export const employeeReducer = (state = [], action) => {
switch (action.type) {
case actionType.EMPLOYEES_LOADED:
return {...state,employees:action.payload.employees}
default :
return state;
}
}
if the response from the API is in the form
[{"employee_name":"name","employee_age":24},.....]

Reducer: getting information about value changes at the store

I don't understand how to get an updated value from the reducer store. For example, I have a React component. In this component after some actions, for example, after a few clicks on a button, I call the action from my reducer actions script like this.props.PostSomethingToServer(). Then the action sends some data to the node express server. The server makes some changes with data and then sends a response to the client store reducer. How can I get the updated data from the reducer store? I need to call another function in this React component with updated values.
By the way, I use mapStateToProps and export default connect() in the React component. As I know, mapStateToProps and export default connect() help to get data from the store before render(). But I still don't understand how to get updated data from the store after some actions.
A couple of code:
React component:
ChangeArrayData(){
let something = [];
// filling this array and modifying values
//then I call the action
this.props.postSomethingToServer(something);//something will be changed by server logic and will be returned with updated data by a response.
//then I wanna export the data to .excel, for example
this.ExportToExcel(); // here I have to get updated data from the reducer, but I don't have any information about changes in the reducer.
}
Reducer action:
export const postSomethingToServer= rankedElements => dispatch => {
axios
.post("/api/postData", elements)
.then(response => {
dispatch({
type: POST_SOMETHING_SUCCESSFUL,
status : "success",
payload: response.data
});
//... etc.
Reducer:
const initialState = {
something: {},
status: "",
error : ""
};
export default function(state = initialState, action) {
switch (action.type) {
case POST_SOMETHING:
return {
...state,
status: action.status,
}
case POST_SOMETHING_SUCCESSFUL:
return {
...state,
status: action.status,
something: action.payload
}
case GET_ERRORS:
return {
...state,
status: action.status,
error: action.error
}
default:
return state;
}
}
You Should assign reducer state values to some local state like following:
`const mapStateToProps = state => ({
contacts: state.data
});
export default connect(mapStateToProps, { actions })
(withStyles(contactStyle)(Contact));`
Here 'contacts' is a local state name we are using in the class and 'data' is a state name that we return from reducer after updating a state.
You can access the updated data using componentWillReceiveProps method like,
`componentWillReceiveProps(nextProps) {
if(nextProps.contacts !== undefined) {
//Handle updated states here
}
}`
After you dispatch the action, your reducer state something should have the data you expect. Given that you have mapped the data in your mapStateToProps function you can access it via props.
ChangeArrayData() {
let something = [];
this.props.postSomethingToServer(something);
this.ExportToExcel();
console.log(this.props.somethingReducerState);
}
const mapStateToProps = (state) => ({
somethingReducerState: state.yourReducerName.something,
});
I have the solution: I'm using componentWillReceiveProps(nextProps) and can receive a result from the reducer.
Thanks all for the answers.

React component not re-rendering with connect()

My reducer:
export default function summary(state = {
"summary":null
}, action = null) {
switch (action.type) {
case GET_SUMMARY_REQUEST_SUCCESS:
const newState = Object.assign({}, state);
newState.summary = action.data;
return newState;
break;
case GET_SUMMARY_REQUEST_ERROR:
return Object.assign({}, state, {
sumary:null
});
break;
default: return state;
}
};
Root reducer:
import summary from './Summary.js'
const rootReducer = combineReducers({
summary
});
Inside my component, I am using connect to map state to props>
My components render function is something like this:
render() {
const summary = this.props.summaryContent || [];
return (
<div className={cx(styles['loading'])} >
<Loader width="4" />
{"Loading\u2026"}
</div>
);
}
function mapStateToProps(state, ownParams) {
return {
summaryContent: state.summary
};
}
export default connect(mapStateToProps)(Summary);
On componentWillMount, I am dispatching an action to update in the state in summary. Now, my componentWillReceiveProps is showing me the updated state in summary, but the component is not rendering.
I can see a few issues here:
You use summary both as a reducer key and as an index in your reducer state. That means summaryContent should map to state.summary.summary. (Though I'd suggest changing it to adopt a less confusing naming convention.)
Your render function doesn't utilise this.props.summaryContent in any useful way. It simply assigns it to a variable and then returns a loading output.
You've misspelled summary in the GET_SUMMARY_REQUEST_ERROR case.
I strongly recommend configuring ESLint which will alert you to issues like unused and misspelt variables.

Prop always null in react

I have a component which has to display the object details. This is instantiated from a TableComponent on click of a row.
The object id is passed:
<ObjectDetails objectID = {id}/>
This is my ObjectDetails component:
class ObjectDetails extends Component {
componentWillMount() {
this.props.dispatch(loadObjectDetails(this.props.objectID));
}
render() {
console.log("props------" + JSON.stringify(this.props));
....
}
let select = (state) => ({objectDetails: state.objectDetails});
export default connect(select)(ObjectDetails);
}
loadObjectDetails populates the store with objectDetails. I can see that the store does have the details.
But in render(), props always has objectDetails as null.
Not sure what I'm doing wrong, any help please?
Edit:
Adding few more details
export function loadObjectDetails(objectID) {
return function (dispatch) {
Rest.get('/rest/objects/' + objectID).end(
(err, res) => {
if(err) {
dispatch({ type: 'FETCH_OBJECT_DETAILS_FAILURE', error: res.body})
} else {
dispatch({ type: 'FETCH_OBJECT_DETAILS_SUCCESS', objectDetails: res.body})
}
}
)
}
}
export default function objectDetailsReducer(state={
objectDetails: null,
error: null,
}, action) {
switch (action.type) {
case "FETCH_OBJECT_DETAILS_SUCCESS": {
return {...state, objectDetails: action.objectDetails, error: null}
}
case "FETCH_OBJECT_DETAILS_FAILURE": {
return {...state, error: action.error }
}
}
return state
}
const middleware = applyMiddleware(promise(), thunk, logger())
export default compose(middleware)(createStore)(combineReducers({ reducer1, reducer2, objectDetailsReducer}))
The object inside your mapStateToProps function is what gets passed as props:
{objectDetails: state.objectDetails}
So in your component, you can access: this.props.objectDetails. And whatever properties are on that. If you just wish to pass the objectID, update your mapStateToProps function to change that.
if you want component get new props when store mutates, you have to use mapStateToProps can see many samples in docs: http://redux.js.org/docs/basics/UsageWithReact.html
In your sample code you have extra spaces around = sign:
<ObjectDetails objectID = {id}/>.
componentWillMount function is fired once when component will mount and that's it, so you dispatch this action, it updates the store, but you don't have mapStateToProps so this component has no reaction to store updates.
Component can react to store by mapStateToProps or you can leave this component presentational but then upper component has to have mapStateToProps.
You can read about presentational component on same page: http://redux.js.org/docs/basics/UsageWithReact.html
Component will try to re-render only when props or state is changed. If props/state is not changed - no re-render happens. Your props don't change. You don't use state on this component at all, so nothing changes too.
The render() method was throwing an error on the first render which is why it was not rendering on props change.
It is re-rendering after I fixed that error.
Sorry for wasting your time!

Resources