Access state present in functional component in reducer - javascript

I have two states to maintain:
An array which contains the list of items to display
An integer(called index) that represents which elements within the array is currently displayed.
For the array, I am using the useReducer hook, to add, delete, edit elements within the array.
The reducer function is written outside the functional component.
Within this reducer, I want to access the state value of the "index" state to know which element to modify. However, since this state is within the functional component. How to achieve this?
Here is the sample code:
function reducer(state, action){
// Reducer code here
}
function SomeComponent(props){
[allItems, dispatch] = useReducer(reducer,[])
[index,setIndex] = useState(null)
// Use the above index within the reducer
}

You need to pass in dispatch function something like this:
switch (action.type) {
case SET_VISIBILITY_FILTER:
return state.filter(data => data.id === action.index) //check here
default:
return state
}
And you can dispatch this event from your component:
function SomeComponent(props){
[allItems, dispatch] = useReducer(reducer,[])
[index,setIndex] = useState(null)
useEffect(() => {
dispatch({ type: 'SET_VISIBILITY_FILTER',index:index })
},[index]] //it will run when index changes
}
I would suggest set index in reducer as well as it will easy track all data from single source

const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case 'increment': return state + 1;
case 'decrement': return state - 1;
case 'reset': return 0;
default: throw new Error('Unexpected action');
}
};
const YourComponent = () => {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<div>
{count}
<button onClick={() => dispatch('increment')}>+1</button>
<button onClick={() => dispatch('decrement')}>-1</button>
<button onClick={() => dispatch('reset')}>reset</button>
</div>
);
};

Related

Redux connect() higherordercomponent not working while i pass action creater throwing mapDispatchToProps

Action creater not working in react-redux while using the mapDispatchToProps function return object to pass in connect HOC. It working while passing Action creater directly in connect HOC something below. Not able to understand the difference.
Not Working:
const mapDispatchToProps = () => {
return {
increment,
decrement
}
}
const WrappedCounter = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(Counter);
Working:
const WrappedCounter = ReactRedux.connect(mapStateToProps, {
increment,
decrement
})(Counter);
Detailed Code:
<script type="text/babel" data-plugins="proposal-class-properties" data-presets="env,react">
// Action Creators - You don't need to change these
const increment = () => ({ type: 'increment' });
const decrement = () => ({ type: 'decrement' });
const Counter = (props) => {
return (
<div>
<button className="increment" onClick={props.increment}>Increment</button>
<button className="decrement" onClick={props.decrement}>Decrement</button>
Current Count: <span>{props.count}</span>
</div>
);
};
const mapStateToProps = (state) => {
return { count: state.count }
}
const mapDispatchToProps = () => {
return {
increment,
decrement
}
}
const WrappedCounter = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(Counter);
// Only change code *before* me!
// -----------
const store = Redux.createStore(Redux.combineReducers({
count: (count = 0, action) => {
if (action.type === 'increment') {
return count + 1;
} else if (action.type === 'decrement') {
return count - 1;
} else {
return count;
}
}
}));
ReactDOM.render(
<ReactRedux.Provider store={store}>
<WrappedCounter />
</ReactRedux.Provider>,
document.querySelector('#root')
);
There are two valid syntaxes for writing mapDispatchToProps. What you have here is kind of a combination of the two, but it's not correct for either one.
Function Syntax
The "classic" (largely outdated) form of mapDispatchToProps is a function which takes dispatch as an argument and returns props of your component. Those props are just normal functions so they need to make the dispatch call themselves.
It is very verbose and I don't recommend this:
const mapDispatchToProps = (dispatch) => {
return {
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement())
}
}
Object Syntax
The simpler way to define mapDispatchToProps is with the object shorthand notation. Here, you return a dictionary of action creator functions and react-redux handles binding them to dispatch automatically.
That looks like this:
const mapDispatchToProps = {
increment,
decrement
}
When using this syntax, mapDispatchToProps is not a function, it's just an object.
In your semi-working solution where you defined mapDispatchToProps as a function and then called the function with connect(mapStateToProps, mapDispatchToProps())(Counter), you using this syntax but with an extra step. You defined a function that returns the object. Then called the function to get the object.
Just Use Hooks
The most modern way of writing this is to ditch the connect higher-order component entirely and use the react-redux hooks.
const Counter = () => {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
return (
<div>
<button className="increment" onClick={() => dispatch(increment())}>Increment</button>
<button className="decrement" onClick={() => dispatch(decrement())}>Decrement</button>
Current Count: <span>{props.count}</span>
</div>
);
};
The Below solution is working, while i call the function
const mapStateToProps = (state) => {
return { count: state.count }
}
const mapDispatchToProps = () => {
return {
increment,
decrement
}
}
const WrappedCounter = ReactRedux.connect(mapStateToProps, mapDispatchToProps())(Counter);
but still i have doubt
for state I'm declaring and plased in HOC connect it working but mapDispatchToProps is not working without calling it.
mapStateToProps // just passing in HOC
mapDispatchToProps() // here I'm passing and Calling the function

local storage problem when trying to fetch data from localstorage using useEffect() in ReactJS

I am trying to fetch data in localstorage using ReactJS. Can some one please help me here is my sample code.
let [rows,setRows] = useState([]);
React.useEffect(() => {
rows = localStorage.getItem("my_tier_list");
if(rows){
setRows(JSON.parse(rows));
}
},[]);
React.useEffect(() => {
localStorage.setItem("my_tier_list", JSON.stringify(cart));
});
Can some one please help me and thanks in advance
There are three problems to your above code.
You can't directly assign values to your state variable using =, you must do it using the setter functions.
You have not added the dependency list in the second useEffect.
You have not used the correct name to set the localStorage.
let [rows,setRows] = useState([]);
React.useEffect(() => {
// you can't directly set a state variable. Create a new local variable
const localRows = localStorage.getItem("my_tier_list");
if(localRows){
setRows(JSON.parse(localRows));
}
},[]);
React.useEffect(() => {
localStorage.setItem("my_tier_list", JSON.stringify(rows)); // corrected it to rows
}, [rows]); // added the array as dependency list. This will trigger this only when "rows" gets changed
Update
Based on your code shared through code sandbox, you need to update your Reducer.js.
const updateLocalStorage = (cart) => {
localStorage.setItem("my_tier_list", JSON.stringify(cart));
};
export const cartReducer = (state, action) => {
switch (action.type) {
case "ADD_TO_CART": {
const updatedState = {
...state,
cart: [...state.cart, { ...action.payload, qty: 1 }]
};
updateLocalStorage(updatedState.cart);
return updatedState;
}
case "REMOVE_FROM_CART": {
const updatedState = {
...state,
cart: state.cart.filter((c) => c.id !== action.payload.id)
};
updateLocalStorage(updatedState.cart);
return updatedState;
}
case "CHANGE_CART_QTY": {
const updatedState = {
...state,
cart: state.cart.filter((c) =>
c.id === action.payload.id ? (c.qty = action.payload.qty) : c.qty
)
};
updateLocalStorage(updatedState.cart);
return updatedState;
}
default:
return state;
}
};
And in Header.js
let [rows,setRows] = useState([]);
React.useEffect(() => {
const localRows = localStorage.getItem("my_tier_list");
if(localRows){
setRows(JSON.parse(localRows));
}
},[cart]); // adding cart will ensure any changes you make is reflected.
Please look into following sandbox: https://codesandbox.io/s/heuristic-rain-n97hf3
Set local storage item on some event handler: localStorage.setItem("value", value);
Get local storage item with: const localStorageValue = localStorage.getItem("value");

How to delete a specific item from localstorage in react redux

How can I remove a specific item (by id) from localstorage using react (redux - persist)? handleSubmit is working fine, but handleDelete, is not. I have this:
handleSubmit = event => {
event.preventDefault();
this.props.addWeather(this.state.weatherCity);
this.setState({ weatherCity: "" });
};
handleDelete = (event, id) => {
this.props.deleteWeather(this.state.weatherCity);
this.setState({ weatherCity: "" });
}
const mapStateToProps = state => ({
allWeather: state.allWeather
});
const mapDispatchToProps = dispatch =>
bindActionCreators(WeatherActions, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(WeatherList);
And button in form to call handleDelete:
<form onSubmit={this.handleDelete}><button type="submit" id="add" onClick={this.handleDelete}>Remove City</button></form>
My localstorage:
allWeather: "[{\"id\":0.5927975642362653,\"city\":\"Toronto\"},{\"id\":0.8124764603718682,\"city\":\"Fortaleza\"},{\"id\":0.9699736666575081,\"city\":\"Porto\"},{\"id\":0.852871998478355,\"city\":\"Tokio\"},{\"id\":0.8854642571682461,\"city\":\"New York\"}]"
My reducer:
export default function allWeather(state = [], action) {
switch (action.type) {
case "ADD_WEATHER":
return [...state, { id: Math.random(), city: action.payload.city }];
case "DELETE_ITEM":
return [...state, state.weatherCity.filter((event, id) => id !== action.payload.id)];
default:
return state;
}
}
And actions:
export const deleteWeather = id => ({
type: "DELETE_ITEM",
payload: { id }
});
I appreciate any help.
Your problem is that you are using the spread operator, which copies the content of the current state first. Then you are adding the items that were returned from the filter method. So you aren't deleting but adding. To delete from an array use the filter method only, without the spread operator like that:
return state.filter( (city) => city.id !== action.payload.id )
Also the state is an array, not an object, so this is invalid state.weatherCity.

Update state props without firing a dispatch() - React Redux

I have a modal containing a button that fires a HTTP request, at which point the displayed html will change depending on a successful/error response from the server, where the response changes a state prop that is dealt with in the mapStatesToProps function.
The issue I have now is that I am wanting to reset the modal to its initial state pre-request when I close it.
I had previously done this by using local component state but have since updated the functionality to use the request mapped state props shown above.
I am curious if it possible to reset the state without firing a dispatch to a random URI?
Component.jsx
const mapStatesToProps = ({myState}) => ({
response: myState.response,
success: !!(myState.success),
fail: !!(myState.fail)
});
const mapDispatchToProps = dispatch => ({
doReq: () => {
dispatch(doMyRequest());
}
});
class MyComponent extends Component {
toggleModal = () => // modal toggle code
render() {
const {response, success, fail} = this.props;
<div className="myModal">
// Modal stuff here
{!success && !fail && (
<button onClick="() => toggleModal()">Close modal</button>
)}
{success && !fail && (
<h1>Some success message</h1>
)}
{!success && fail && (
<h1>Some fail message</h1>
)}
</div>
}
}
req-actions.js
export const MY_REQUEST;
export const MY_REQUEST_SUCCESS;
export const MY_REQUEST_ERROR;
export const doMyRequest = () => ({
type: MY_REQUEST,
agent: agent.req.doRequest
})
req-reducer.js
import { deepEqual, deepClone } from '../McUtils';
import {
MY_REQUEST,
MY_REQUEST_ERROR,
MY_REQUEST_SUCCESS
} from "../actions/req-actions";
export default (state = {}, action) => {
let newState = deepClone(state);
switch (action.type) {
case MY_REQUEST:
console.log('SENDING REQUEST');
newState.response = null;
newState.success = false;
newState.fail = false;
break;
case MY_REQUEST_SUCCESS:
console.log('SUCCESS');
newState.response = action.payload;
newState.success = true;
newState.fail = false;
break;
case MY_REQUEST_ERROR:
console.log('FAIL');
newState.response = action.payload;
newState.success = false;
newState.fail = true;
break;
default:
return state;
}
return newState;
}
Just use another action:
case MY_REQUEST_RESET:
return {} // only putting {} in here because this is what you have defined your initialState to be according to your reducer.
Personal preference is to clearly define your initial state like this.
const initialState = {};
export default (state = initialState, action) => {
switch(action.type) {
... your existing handlers
case MY_REQUEST_RESET:
return initialState
}
}
Wiring it up:
const mapDispatchToProps = dispatch => ({
doReq: () => {
dispatch(doMyRequest()),
},
reset: () => {
dispatch(resetMyRequest());
}
});
// types
const MY_REQUEST_RESET = 'MY_REQUEST_RESET';
// action creator (may be referred to as "actions")
const resetMyRequest = () => ({ type: MY_REQUEST_RESET })
EDIT: While I'm here, this is really gross:
let newState = deepClone(state);
and reeks of "I don't really know what I'm doing" and can lead to performance issues. You are deepCloning the state on every action fired through redux, even if the actions aren't one's this reducer is interested in.
If you are changing the state in the reducer, just change the part you are concerned with, don't change "all" of it.
e.g.
export default (state = {}, action) => {
switch (action.type) {
case MY_REQUEST:
console.log('SENDING REQUEST');
return {
success: false,
fail: false,
response: null
}
case MY_REQUEST_SUCCESS:
console.log('SUCCESS');
return {
...state, // this will contain "fail: false" already
success: true,
response: action.payload
};
case MY_REQUEST_ERROR:
console.log('FAIL');
return {
...state, // this will contain "success: false" already
error: true,
response: action.payload
};
default:
return state;
}

Issues with useReducer not synchronously updating the state

According to React docs :
useReducer is usually preferable to useState when you have complex
state logic that involves multiple sub-values or when the next state
depends on the previous one.
1. can somebody explain me why useReducer is not updating the state synchronously ?
const reducer = (state, action) => {
if( action.type === 'ADD_VALUE') {
console.log(`STATE IN REDUCER`, [...state, action.path]) // => ["1.1"]
return [...state, action.path]
}
}
const [state, dispatch] = useReducer(reducer, [])
<input type="button" onClick={() => {
dispatch({ type: 'ADD_VALUE', path: "1.1"})
console.log(`STATE`, state) // => []
// here i want to do some stuff based on the lastest updated state (["1.1"] and not [])
// for example dispatch an action with redux
}}/>
2. How can I do some stuff (dispatch a redux action) based on the lastest updated state (["1.1"] and not []) ?
Use useEffect to access the state correctly. You could add some safe-guarding if you want something invoking if a certain criterion is hit.
If you want to access your reducer across components, you can store the reducer using Context API. Look below for an example. You can see the reducer being injected into the Context on the parent component and then two child components that a) dispatches an action b) receives the update from the action.
1. Example of context reducer to use across multiple components
import React from "react";
import ReactDOM from "react-dom";
const Application = React.createContext({
state: null,
dispatch: null
});
function ActionComponent() {
const { dispatch } = React.useContext(Application);
return (
<div>
<div>Action Component</div>
<button onClick={() => dispatch("lol")}>Do something</button>
</div>
);
}
function ListenerComponent() {
const { state } = React.useContext(Application);
React.useEffect(
() => {
console.log(state);
},
[state]
);
return <div>Listener Component</div>;
}
function App() {
const [state, dispatch] = React.useReducer(function(state = [], action) {
return [...state, action];
});
return (
<Application.Provider value={{ state, dispatch }}>
<div className="App">
<ActionComponent />
<ListenerComponent />
</div>
</Application.Provider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
2. Example of local reducer without using Application Context
const reducer = (state, action) => {
if( action.type === 'ADD_VALUE') {
return [...state, action.path]
}
}
const [state, dispatch] = useReducer(reducer, [])
React.useEffect(() => {
console.log(state);
}, [state]);
<input type="button" onClick={() => {
dispatch({ type: 'ADD_VALUE', path: "1.1"})
}}/>

Categories