I need to get Redux store data but not always and different data for each component instantiate.
How can I use useSelector with Conditional statement?
The component should get data from store only when some child components are rendered and also different data each time depending on the child component.
useSelector takes in a callback that has access to the redux state as an argument
so assuming you control whether the child component is rendered in a boolean flag called isComponentRendered, and want to select data when it is true and else nothing, you could try the following:
const data = useSelector(state => {
if(state.isComponentRendered) {
return state.data
}
return null;
})
Related
How can I get values from local storage in next.js?When i give localStorage.getItem() in console,it is prnting the values.But when I assign this to a variable it is giving LocalStorage is not defined error.I have also added redux-persist in my localstorage
localStorage.getItem('id')
Local Storage is a Web API native to modern web browsers. It allows websites/apps to store data in the browser, making that data available in future browser sessions.
There are two React lifecycle methods we can use in our component to save/update the browsers localStorage when the state changes:
componentDidMount()
componentDidUpdate()
componentDidMount will run once your component has become available and loaded into the browser. This is when we gain access to localStorage. Since localStorage doesn’t reside in Node.js/Next.js since there is no window object, we will have to wait until the component has mounted before checking localStorage for any data. So If you want to assign the local storage value into a variable, please do this inside the componentDidMount method.
componentDidMount() {
const data = localStorage.getItem('id')
console.log(data);
if(data) {
//here you can set your state if it is necessary
}
}
And If we want to update our local storage value through the state we can easily update the localStorage value with our changes value by using componentDidUpdate. This method gets run each time the state changes so we can simply replace the data in localStorage with our new state.
componentDidUpdate() {
localStorage.setItem('id', JSON.stringify(this.state))
}
localStorage is a property of object window. It belongs to the browser, not next.js nor React, and accessing localStorage is not possible until React component has been mounted. So you need to ensure that your React app is mounted before calling localStorage, e.g. calling localStorage.getItem inside componentDidMount.
When working with a framework like Next.js that executes code on the server side, using localStorage produces an error like "localStorage is not defined" or "window is not defined"
To fix this, check to see if window is defined so that the code will run only when it's available.
This is a great article that explains more: https://blog.logrocket.com/using-localstorage-react-hooks/
See the section called, "Problems accessing localStorage for an SSR application"
You can create a file called "useLocalStorage.tsx" or whatever, and it would contain something like this:
import { useState, useEffect } from "react";
function getStorageValue(key, defaultValue) {
// getting stored value
if (typeof window !== 'undefined') {
const saved = localStorage.getItem(key);
return saved || defaultValue;
}
}
export const useLocalStorage = (key, defaultValue) => {
const [value, setValue] = useState(() => {
return getStorageValue(key, defaultValue);
});
useEffect(() => {
// storing input name
localStorage.setItem(key, value);
}, [key, value]);
return [value, setValue];
};
Then you can just import it into the file you want to use it in like this:
import { useLocalStorage } from './useLocalStorage'
Then you can call it to get the "id" from localStorage:
const [id, set_id] = useLocalStorage("id", "");
First think to take a note is, localStorage has nothing to do with next.js or redux-persist. localStorage is the internal window object and can be directly accessible without any definition.
I think you are trying to access the localStorage before it is being set, so you get that error.
Simple solution to this is to use Conditional (ternary) operator
,
const id = localStorage.getItem('id') ? localStorage.getItem('id') : "set your own default value";
console.log(id);
I have 2 reducers that are combined in a Root Reducer, and used in a store.
First reducer 'AllTracksReducer" is supposed to return an object and the second 'FavoritesReducer' an array.
When I create a container component and a mapStateToProps method in connect, for some reason the returned state of the store is an object with 2 reducer objects which hold data, and not just an object containing correposding data, as expected.
function mapStateToProps(state) {
debugger:
console.dir(state)
//state shows as an object with 2 properties, AllTracksReducer and FavoritesReducer.
return {
data: state.AllTracksReducer.data,
isLoading: state.AllTracksReducer.isLoading
}
}
export default connect(mapStateToProps)(AllTracksContainer);
so, in mapStateToProps, to get to the right state property, i have to say
state.AllTracksReducer.data... But I was expecting the data to be available directly on the state object?
Yep, this is a common semi-mistake. It's because you're using likely using ES6 object literal shorthand syntax to create the object you pass to combineReducers, so the names of the imported variables are also being used to define the state slice names.
This issue is explained in the Redux docs, at Structuring Reducers - Using combineReducers.
Create some selectors that receive the whole state (or the reducer-specific state) and use it in your mapStateToProps function. Indeed the name you define when you combineReducers will be the topmost state keys, so your selectors should take that into account:
const getTracks = (state) => state.allTracks.data
const isLoading = state => state.allTracks.isLoading
This assumes you combine your reducers with allTracks as they key like here:
combineReducers({
allTracks: allTracksReducer
})
And then you can use those selectors in your mapper, like
const mapStateToProps = state => ({
isLoading: isLoading(state),
tracks: getTracks(state)
})
There's a delicate link between your combineReducers call and your selectors. If you change the state key name you'll have to update your selectors accordingly.
It helps me to think of action creators as "setters" and selectors as "getters", with the reducer function being simply the persistence part. You call your setters (dispatching action creators) when you want to modify your state, and use your selectors as shown to get the current state and pass it as props to your components.
Well, that's how it supposed to work. When you're using combineReducers, you're literally mapping the name of a reducer to the reducer function.
If it bothers you, I would suggest a little syntactical magic if you're using es2016 (though it seems you're not) like so:
function mapStateToProps(state) {
const { data, isLoading } = state.allTracksReducer;
return {
data: data,
isLoading: isLoading
}
}
export default connect(mapStateToProps)(AllTracksContainer);
Remember, state is the one source of truth that possesses all your reducers.
I have a Redux app that is displaying a list of Properties based on a set of Filters (user input).
Quick description of my state:
filters – Filters values object...
properties – Repo of all properties available on page
visibleProperties – List of properties with current filters applied
The problem is when I dispatch & set a new filter value, I need to filter properties based on filters new state and to store the result in visibleProperties.
So I came up with this solution:
export function setBedroomFilter (value) {
return (dispatch, getState) => {
// 'SET_FILTER' action
dispatch(setFilter('bedroom', parseInt(value)))
// New state
const { filters, properties } = getState()
// 'FILTER_PROPERTIES' action (Depending on new state)
dispatch(filterProperties(properties, filters))
}
}
And visibleProperties reducer can do its work:
// case 'FILTER_PROPERTIES'...
visibleProperties = action.properties.filter(item => item.bedroom >= action.filters.bedroom)
Is this approach totally fine?
From the documentation of dispatch:
Dispatches an action. This is the only way to trigger a state change.
The store's reducing function will be called with the current
getState() result and the given action synchronously. Its return value
will be considered the next state. It will be returned from getState()
from now on, and the change listeners will immediately be notified.
It's a synchronous function and it's totally fine to use in the way you've described(as long as setFilter is synchronous). However, if you're doing asynchronous operation in setFilter,(assuming that Promise returned from setFilter) you should chain your dispatch calls like this:
dispatch(setFilter('bedroom', parseInt(value))).then(() => {
// New state
const { filters, properties } = getState()
// 'FILTER_PROPERTIES' action (Depending on new state)
dispatch(filterProperties(properties, filters))
}
Another option might be using selectors. Please check it out:
https://github.com/reactjs/reselect
store.select() emits previous store state.
Is it possible to subscribe to changes from "this point forward" without getting the previous store value?
If you are not interested in the first emitted value, you should be able to use the skip operator:
store.select(...).skip(1)...
skip operators need piping now, you can use skip like this:
store.pipe(select(...), skip(1));
In terms of the 'hacky' part, it is a standard practice in ngrx to set an initial state with properties set to null. and that value gets emitted initially. so the first value you get will be null in these cases.
Alternatively you could also consider skipwhile(https://www.learnrxjs.io/learn-rxjs/operators/filtering/skipwhile) and use it like this:
store.pipe(select(...), skipWhile(val => val === undefined));
where undefined is the initial value of the property you are interested in. Rather than setting the initial value of the property to undefined, you could use null as the initial value as well, and change the above skipwhile() accordingly.
Just sharing my thoughts (and solution) after reading #Niz's answer.
This is a perfect, practical example of how to utilize the difference between null and undefined. When you initialize your state with null, you're basically saying:
I don't care about differentiating the nullable future state from the
initial one. I don't care if the user is null because he has signed
out or because he just didn't sign in
However, in some cases this could be insufficient. Think about a case when you need an asynchronous call (implemented in effects) in order to know if you have an active user session. Based on the selection result, you should determine whether to show a login modal or redirect to a content page. With initial user state set to null, you'd pop up that modal and then immediately hide it when that asynchronous call returns a session value.
With initial state set to undefined you can make that differentiation, saying:
Initially, I know nothing about my state, then it's undefined. When I know it should be empty, then I'll set it to null.
Therefor, as a practical solution, I set everything on the app's initialState to undefined. In the example above, I need to know if the login modal should be displayed after the asynchronous call resolves. skipWhile(val => val === undefined) will do the job for sure, but repeating it over and over again feels a little tedious. Plus, it's not really descriptive to our use case. I created a rxjs-custom-operators.ts with a shortened implementation:
import { Observable } from "rxjs";
import { skipWhile } from "rxjs/operators";
export const skipInitial = () => {
return <T>(source: Observable <T>): Observable<T> => {
return source.pipe(skipWhile(value => value === undefined));
};
};
Usage:
navigateOnLoad(): void {
this.store.pipe(select(selectAuthUser), skipInitial()).subscribe((authUser: CognitoUser) => {
// Navigate to login if !authUser, else navigate to content...
});
}
In this example I'm using an action named ADD_TODO
import { createStore, combineReducers } from 'redux';
function todos(state, action) {
state = state || [];
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.text ]);
default:
return state;
}
}
function counter(state, action){
state = state || 0;
switch (action.type){
case 'INCREMENT':
return state+1;
case 'DECREMENT':
return state-1;
case 'ADD_TODO':
return state+100;
default:
return state;
}
}
var app = combineReducers({
todos: todos,
counter: counter
});
var store = createStore(app);
store.dispatch({ type: 'ADD_TODO': text: 'buy eggs' });
This cause both the "todos" and "counter" reducers to trigger.
Should I make all reducers have unique actions unless I actually intended it?
How can we implement this with multiple reducers that almost do the same thing? Multiple counters for example can have "INCREMENT" and a "DECREMENT" actions.
Should name spacing actions solve it?
eg: "POINT_INCREMENT", "POINT_DECREMENT".
There's nothing inherently wrong with having different reducers respond to the same action -- for example, if you refresh the entire state at once. But yeah, if you have two counters that correspond to different things, you probably want to come up with a naming scheme to differentiate. But I would think the action names probably should have some noun to indicate what they apply to.
This cause both the "todos" and "counter" reducers to trigger. Should I make all reducers have unique actions unless I actually intended it?
Yes, probably they should have different unique actions.
From your example it becomes not really clear what you actually intend.
Should the counter count the amount of todo's ?
In that case it can actually be sensible that a "ADD_ITEM" action would both update the counter and also add a todo item. In that case please refer to the answer of acjay
How can we implement this with multiple reducers that almost do the same thing? Multiple counters for example can have "INCREMENT" and a "DECREMENT" actions.
When displaying a list of counters in the same app, each counter can be assigned a unique identifier (id).
An action should pass along the id of the counter.
export const toggleTodo = id => ({
type: 'INCREMENT',
id
})
The reducer should then check by id which counter to update.
See this example of a todo list in the official redux docs.
https://redux.js.org/basics/example
Redux actions are in a way globals. There are different strategies to workaround this problem on a larger scale.
https://kickstarter.engineering/namespacing-actions-for-redux-d9b55a88b1b1