Currently I'm having to reconfigure my store to create selector. Is there a better way to do this.
import { configureStore } from "../configureStore";
const { store } = configureStore();
export const getSession = () => store.getState().session.data || false;
export const getToken = () => store.getState().session.data.token || false;
Selector functions should take the store state as the argument, not capture the store reference. As an example:
export const getSession = (state) => state.session.data || false;
export const getToken = (state) => state.session.data.token || false;
// use like:
const session = getSession(store.getState());
See my post Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance for more details.
If you are going to use Redux in React world, then you should definitely attach the redux store to the props of a React component by using connect (https://redux.js.org/basics/usage-with-react).
That way, whenever the values in the store changes, you get updated props provided to your component, re-rendering the component with the correct values.
With that, you generally don't ever need store.getState(). You do something like:
// selectors.js
const getSession = state => state.session.data || false
// component.js
const MyComponent = ({ session }) => <div>{session}</div>
const mapStateToProps = state => ({
session: getSession(state),
})
export default connect(mapStateToProps)(MyComponent)
Related
What am I trying to do?
Using Redux Toolkit, I'm trying to access the "store" for a value, specifically "username" which I've created a "slice" for, from a non-React file called SomeFile.js.
What is the code that currently tries to do that?
// userMetadataSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
username: "",
};
const userMetadataSlice = createSlice({
name: "userMetadata",
initialState,
reducers: {
updateUsername: (state, action) => {
const username = action.payload;
state.username = username;
},
},
});
export const { updateUsername } = userMetadataSlice.actions;
export default userMetadataSlice.reducer;
export const selectUsername = (state) => {
return state.userMetadata.username;
}
// SomeFile.js
import { selectUsername } from "../redux/userMetadataSlice";
import { useSelector } from "react-redux";
export const displayUsername = () => {
const username = useSelector(selectUsername);
console.log("Username:", username); // Error.
}
What do I expect the result to be?
To be able to pull the username from the "store".
What is the actual result?
When I try to access the value via "useSelector" from the non-react file an error occurs: React Hook "useSelector" is called in function "selectUsername" which is neither a React function component or a custom React Hook function
What I think the problem could be?
SomeFile.js does not have anything React related within it because it just pulls data from the store and outputs the data.
A solution I've tried that worked was to do this:
// userMetadataSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
username: "",
};
const userMetadataSlice = createSlice({
name: "userMetadata",
initialState,
reducers: {
updateUsername: (state, action) => {
const username = action.payload;
state.username = username;
},
},
});
export const { updateUsername } = userMetadataSlice.actions;
export default userMetadataSlice.reducer;
export const selectUsername = (state) => {
return state.userMetadata.username;
}
// New code here!
export function SelectUsername() {
const username = useSelector(selectUsername);
return username;
}
// SomeFile.js
import { SelectUsername } from "../redux/userMetadataSlice";
export const displayUsername = () => {
console.log("Username:", SelectUsername); // No errors, shows correct output.
}
The solutions I'm looking for is this:
Is my proposed solution the "proper" way to receive info from the "store" in non-React files?
Is there a custom hook solution for this?
Is my proposed solution the "proper" way to receive info from the
"store" in non-React files?
No, it's abusing the Rules of Hooks and React functions. You are directly invoking the SelectUsername React function.
Is there a custom hook solution for this?
No, React hooks work only in React functions and custom React hooks.
You can access your state from your Redux store object.
Store
From your created store object you'll have a getState method to invoke.
getState()​
Returns the current state tree of your application. It is equal to the
last value returned by the store's reducer.
Returns​
(any): The current state tree of your application.
You can export your created store object for import into non-React JS files and they can invoke the getStore method.
import store from '../path/to/store';
...
const state = store.getState();
The useSelector React hook from react-redux won't work outside a React component, but the selectUsername state selector function will.
// SomeFile.js
import store from '../path/to/store';
import { selectUsername } from "../redux/userMetadataSlice";
...
export const displayUsername = () => {
const state = store.getState();
const username = selectUsername(state);
console.log("Username:", username);
return username;
};
See the other Store Methods for subscribing to state changes and dispatching actions to your store from outside React.
I have simple input which will be available in two different components so to share the state of this input, I decided to use reducer.
Here is my solution:
index.js
.....
const Index = () => {
const store = createStore(rootReducer, applyMiddleware(thunk));
.............
Reducer
const initialState = {
inputValue: "Testing.com"
}
const nameReducerr = (state = initialState, action) => {
switch (action.type) {
case "INPUT_CHANGE":
return Object.assign({}, state, {inputValue: action.text})
default:
return state
}
}
export default nameReducerr
here is my component
import React, {useState} from 'react'
import {useSelector, useDispatch } from "react-redux"
function inputData() {
const [name, setName] = useState('');
const inputValue = useSelector(state => state.inputValue);
const dispatch = useDispatch();
const handleKeyDown = (event) => {
if (event.key === "Enter") {
dispatch(setName(event.target.value));
}
};
console.log('input value', inputValue);
return (
<div>
<input
onKeyDown={handleKeyDown}
type="text"
className="form-control address"
name=""
/>
<h1>Name: {name}</h1>
<h1>Input Value: {inputValue}</h1>
</div>
)
}
export default input data
Unfortunately, I get the following error.
Error: Actions must be plain objects. Use custom middleware for async actions.
What am I doing wrong here? thx
setName is your setter for your local react state and has nothing to do with redux - so it's result cannot be dispatched. You will need to write an action creator instead.
Generally, useState is local component state. So it also does not select the value from the redux store in any way, which will be your next problem. You will need to use useSelector instead.
Also, you are using a very outdated style of redux here which we do not really recommend any more. To learn modern redux, please follow the official tutorials over at https://redux.js.org/tutorials/index - you will write a lot less and more secure code in the end.
I have an object "user" in reduxReducer and i want to use it in a functional component.
I use this object by using mapStateToProps as following:
const mapStateToProps = (state) => ({
login: state.login,
});
Follwing is the component in which i wanted to use this object.
const ProfilesAdministration = (props, { login: { user }}) => {
// Code Here
}
const mapStateToProps = (state) => ({
login: state.login,
});
export default connect(mapStateToProps , null)( ProfilesAdministration );
Please Help.. Thanks
You should access it from props. Redux connect argument mapStateToProps will add the result from your selector to the components props:
const ProfilesAdministration = (props) => {
// or const user = props.login.user
const { user } = props.login
// Code Here
}
Connect makes your component like having additional props from the redux store.
Currently I'm trying to store a subset of my redux-state to localstorage. I was using the documentation from PWA Starter Kit to implement a basic storage, which works fine for now, but this only saves the complete state to the localstorage.
This isn't exactly what I want, because as mentioned, I only want to store a subset, like some specific action results (e.g. state.settings instead of state).
Every documentation and examples only store the complete state, I haven't found any comment that fits my need.
My current implementation
redux-store.js
import {
createStore,
applyMiddleware,
compose as origCompose,
combineReducers
} from 'redux';
import thunk from 'redux-thunk';
import { lazyReducerEnhancer } from 'pwa-helpers/lazy-reducer-enhancer';
import { loadState, saveState } from './redux-localstorage';
import applicationReducer from './reducers/application-reducer';
export const store = createStore(
(state, action) => state,
loadState(),
compose(lazyReducerEnhancer(combineReducers), applyMiddleware(thunk))
);
export default store;
store.addReducers({
applicationReducer
});
store.subscribe(() => {
saveState(store.getState());
});
redux-localstorage.js
const STORAGE = '__RDX_STORAGE_TEST__';
export const saveState = state => {
const json = localStorage.getItem(STORAGE) || '{}';
const stringifiedNewState = JSON.stringify(state);
if (stringifiedNewState !== json && stringifiedNewState !== '{}') {
localStorage.setItem(STORAGE, stringifiedNewState);
}
};
export const loadState = () => {
const json = localStorage.getItem(STORAGE) || '{}';
const state = JSON.parse(json);
return state || undefined;
};
So, my question is: is this even possible? If yes, how can I achive this?
Thanks a lot.
Using the basic PWA Starter Kit as a base, if for example you wanted to store the shop state and the counter state but not the app state, you could do something like this:
const STORAGE = '__RDX_STORAGE_TEST__';
export const saveState = state => {
const json = localStorage.getItem(STORAGE) || '{}';
// take only the parts we need
const {shop, counter} = state;
const stringifiedNewState = JSON.stringify({shop, counter});
if (stringifiedNewState !== json && stringifiedNewState !== '{}') {
localStorage.setItem(STORAGE, stringifiedNewState);
}
};
export const loadState = () => {
const json = localStorage.getItem(STORAGE) || '{}';
const state = JSON.parse(json);
return state || undefined;
};
This way only those two sections will be written to localStorage
So let's suppose I have a store, with a redux-thunk middleware in it. I created the store and exported it like this:
import myOwnCreateStoreMethod from './redux/createStore';
export const store = myOwnCreateStoreMethod();
I can now access it anywhere in my app. But what if I want to dispatch an action from anywhere? I have them declared e.g. in myAction.js:
export const myAction = () => (dispatch, getState) =>
dispatch({ type: 'SOME_TYPE', payload: ... })
Now I can import them and connect to my store/component like this:
import * as actions from './myActions.js';
const MyComponent = () => <div>Hello World</div>;
const mapStateToProps = () => ({});
export default connect(mapStateToProps, actions)(MyComponent);
My question is - what if I do not have a component and still want to dispatch actions declared like the one above?
You can dispatch actions from the store directly
import store from './myStore';
import { myAction } from './myAction';
store.dispatch(myAction());
Redux is a library by itself.
It has nothing to do with React, they just work well together based on React single source of truth and Redux one global store as the state of our application.
You can use redux in every JavaScript application.
Ah, so easy after #Asaf Aviv wrote his simple answer. So I implemented it like this:
import * as yourActions from './redux/actions/yourActions';
import { store } from './path/to/your/store';
const connectActions = (store, actions) => {
const { dispatch } = store;
return Object.keys(actions).reduce((acc, key) => {
const action = props => dispatch(actions[key](props));
acc[key] = action;
return acc;
}, {});
};
const connectedActions = connectActions(store, yourActions);