I have a React app that currently uses around fifty variables stored in the Context API. I would like to try using Recoil as an alternative but I have a question. Do I have to, and is it best practice to, store each variable in its own atom; and then import it individually each time I need to use it?
With Context I can just do:
const [appState, setAppState] = useState({
var1: "string",
var2: "string2",
var3: false,
var4: 23,
...
})
and then use appState and setAppState as the value in my Context provider. Defining and importing fifty individual atoms is a bit daunting. Granted I won't be using all fifty at the same time but it still seems a step back after using Context.
With Recoil I would have to:
export const var1 = atom({key: "var1", default: "string",})
export const var2 = atom({key: "var2", default: "string",})
etc...
and then:
import { var1, var2, var3, ... } from './RecoilAtoms'
Is there a better way to do this?
You can of course just save it in one atom:
const appState = atom({
key: 'appState',
default: {
var1: "string",
var2: "string2",
var3: false,
var4: 23,
// ...
}
})
I am not sure what you mean by "Is there a better way". My guess is, that you are referring to a monolithic solution, but the whole point of recoil is to split up your global monolithic state and just use it in an atomic fashion. Otherwise there is no real benefit in using recoil.
So yes, I would split it up in several atoms.
Related
I have an index file that contains a lot of atoms for a wizard I've created.
I thought about moving all the creations -
export const foo1State = atom<string>({
key: "foo1State",
default: "",
});
export const foo2State = atom<boolean>({
key: "foo2State",
default: false,
});
into one using JSON -
export const fooStates = atom<fooState>({
key: "fooStates",
default: {
foo1State: string = "",
foo2State: boolean = false,
}
});
Is that a better approach?
I'll mention that all that inputs are changing frequently so need to consider the renders.
What do you think?
Thanks
If you have separate atoms for each state, then you can independently subscribe to each one. If you combine them all into one, a component that renders foo1State will re-render every time you update foo2State and vice versa. This can be problematic if the atom gets really big.
If you don't have any particular reason why you would need to hold foo1State and foo2State in single atom, go with your first approach, and keep them in separate atoms.
Is it possible to chain a React hook? If so, how?
A typical application of a hook would look like this:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
b = a.filter(isDairy)
updateInventory(b)
We can also do this, but it's not chained:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
updateInventory(a.filter(isDairy))
What I want is a chained hook in a functional style:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
a.filter(isDairy).updateInventory()
Can a hook can be modified to take state from this?
Proper usage would be:
updateInventory([...a, "cheddar"].quicksort().filter("cheese"))
But if you really want that chaining, look into how to edit the array prototype.
This is really not recommended, as that method will then be available on all arrays.
I think the underlying problem is you're not clear on what's actually happening with method chaining and possibly with hooks. The specific question:
Can a hook can be modified to take state from this?
doesn't really make sense. So let's break down why then come back at the end to how you could approach this.
For method chaining, let's try a simple example using two methods, .filter and .map, that have two important properties:
They actually return arrays (unlike .push, which returns the new length of the array); and
They actually exist on arrays (unlike .quicksort, which exists on neither an array nor the integer you were calling it on).
function isDairy(item) {
return ["cheese", "milk"].includes(item);
}
function getPrice(item) {
return { bread: 0.58, cheese: 0.80, apples: 0.47, milk: 1.01 }[item];
}
const inStock = ["bread", "cheese", "apples"];
inStock
.filter(isDairy)
.map((item) => ({ item, price: getPrice(item) }));
// => [{ item: "cheese", price: 0.8 }]
There's nothing particularly special happening here, each method you're calling returns a new array on which you can also call any method an array has. You could assign the intermediate steps and get the same result:
const filteredStock = stock.filter(isDairy);
// => ["cheese"]
const pricedFilteredStock = filteredStock.map((item) => ({ item, price: getPrice(item) }));
// => [{ item: "cheese", price: 0.8 }]
It is not the case that:
these are standalone functions (like in e.g. Python where you map(callable, iterable)); or
that the item.name syntax is doing anything beyond just accessing a property named name on the item.
If I tried to use the filter method as a standalone function:
filter(isDairy, inStock);
that would be a ReferenceError, or if I defined another function and tried to access it as if it was a prop on an array:
function allUppercase() {
return this.map((item) => item.toUpperCase());
}
inStock.allUppercase();
it would be a TypeError (because isStock.allUppercase is undefined and undefined isn't callable).
Note you could do allUppercase.bind(inStock)() (or the neater allUppercase.call(inStock)), though; JavaScript does have a means of setting this for a function.
When you use the useState hook, you're calling a function that returns an array containing two objects, and destructuring that array to two local variables:
const [thing, setThing] = useState(initialValue);
is equivalent to:
const result = useState(initialValue);
const thing = result[0];
const setThing = result[1];
The thing, setThing naming is just a convention; really, we're accessing those two objects (current value and setter function) by position. They don't have names of their own, you can do const [foo, bar] = useState("baz") (but... don't).
As the setter is a function you might be wondering whether you can use setThing.bind here, but if setThing is written to use this (I didn't look into the implementation, as it's not directly relevant), it's not going to be happy if you change what this is!
So this comes together when you try to do:
const [basket, setBasket] = useState([]);
// ^^^^^^^^^
inStock.filter(...).map(...).setBasket();
// ^^^^^^^^^
As with the example above, this is a TypeError because setBasket doesn't exist on the array returned by .map. The fact that the same "word" setBasket appears twice is totally irrelevant as far as JavaScript is concerned; one is a local variable and the other is a prop on an array, there's no connection between them.
.map(...) returns a new array, one that we didn't already have a reference to, so the only way to make this work is to ensure all arrays have a setBasket method, which means patching the prototype (as covered in adding custom functions into Array.prototype):
Object.defineProperty(Array.prototype, "setBasket", {
value () {
setBasket(this);
},
});
One problem here is that the function setBasket is accessed via a closure, so it needs to happen inside the component where the hook is defined, so it's going to get defined every time the component is rendered (or you're going to useEffect), which is a problem because you can't redefine that method as written...
But let's ignore that because the bigger problem is that every array in your app now has that method, even in contexts where it's not relevant. If you have multiple state hooks, as seems likely in any non-trivial app, your arrays are gaining lots of methods globally that are only for use in small local scopes.
A more feasible approach is to add a generic method that can be used to apply any hook (in fact any function) to an array:
Object.defineProperty(Array.prototype, "andCall", {
value (func) {
return func(this);
},
});
This can be added once, globally, and used to apply whatever hook is relevant:
inStock.filter(...).map(...).andCall(setBasket);
Note that if you're using TypeScript, you'd also have to add the definition to the global array type, e.g.:
declare global {
interface Array<T> {
andCall<S>(func: (arr: Array<T>) => S): S;
}
}
thanks in advance for your attention with this (I believe) very basic question. I'm working on building my first "full-stack" application, and am running into something I can't quite wrap my head around with React-Redux. A brief explanation of the project: users can submit band idea names, and up or down vote others' submissions. Now, I believe that my problem is I'm not interacting with the state appropriately in my reducer dealing with MODIFY_BAND_SCORE actions. Here's the git repository, and I'll also copy and paste my store reducers here:
export const store = createStore(
combineReducers({
bands(bands = defaultState.bands, action) {
switch (action.type) {
case mutations.CREATE_BAND:
return [
...bands,
{
id: action.id,
owner: action.owner,
name: action.name,
score: 0,
flags: 0,
},
];
case mutations.MODIFY_BAND_SCORE:
let targetBandIndex = bands.findIndex(
(band) => band.id === action.bandID
);
let targetBand = bands.splice(targetBandIndex, 1)[0];
targetBand.score = targetBand.score + action.value;
bands.splice(targetBandIndex, 0, targetBand);
return bands;
}
return bands;
},
users(users = defaultState.users, action) {
return users;
},
}),
applyMiddleware(createLogger(), sagaMiddleware)
);
Hopefully that's enough context to make informed suggestions about what's going on here—my apologies for not having a truly minimal working example for this! The behavior I'm seeing from Redux-Logger when I dispatch an action of type MODIFY_BAND_SCORE is that I am (in a way) seeing the change reflected in that the correct band is having its score modified by the correct amount, but it is showing somehow in the previous and next states! Here's a screenshot:
I feel like I've maybe made this post longer than what it needs to be, am I correct in thinking that in my case for mutations.MODIFY_BAND_SCORE I'm actually modifying the state directly? This is probably occurring with my calling of .splice() on bands isn't it?
Like Siddharth mentioned,
let copyOfBands = [...bands]
will create a copy for you. It's important to remember that one of the key parts of Redux is that the store is read-only. It can be easy to forget that when dealing with non-primitive data (I've certainly done that a bunch), but you should always try to remember to make copies of the data, modify the copy, and then push the copy to store. This helps prevent you from getting really weird and hard to debug errors.
It is important to remember that the spread operator here will creates a shallow copy of the array, which means if you have other non-primitive objects inside the array (such as other arrays), you will have to copy those as well.
In Reducer for initial state I am using a seamless-immutable.
export const INITIAL_STATE = Immutable({
foo:[],
})
function orderReducer(state = initialState, action) {
console.log('reducer is runnig');
switch (action.type) {
case GET_MENU: {
return state.foo.push("newData") // how to achive this <----
}
default:
return state;
}
}
How do I to push new data into the foo?
If you want to stick with ImmutableJS you can use new List() that has a push method. Here are the docs. There are a bunch of data sets with different APIs that always return immutable objects. You will need to start learning those.
However if you are looking for a familiar way to deal with immutability I'd recommend you to switch to Immer. It let's you use plain javascript to return immutable objects, so you don't need to learn any new APIs. Take a look here:
https://github.com/mweststrate/immer
I personally always use plain javascript with the spread operator. I find it comfortable, fast, without any extra libraries. Very verbose when you get use to the syntax:
const pushedArr = [...oldArray, newElement]
const mergedObj = {...objA, ...objB}
Now that you know some alternatives, find the way that fits you and your team the most.
I am building a react/redux web app and am wondering where I should static configuration information that never changes (while the webapp is running anyway).
This is the data in question
This information is used in different parts of the app, for example: there is a form where you are able to select any item out of the main array, and by doing so populating another select field with properties of the selected array:
<select>Choose an exchange</select>
<select>Choose a market (that is available in the above exchange)</select>
This would lend itself nicely to some reducer logic (that sets state.markets based on what is selected in the first select), but should it filter based on other state in the tree, or just load the data in a closure inside the reducer (keeping everything unrelated outside of state tree)? Or is this not state at all (and should the container load this file in and filter based on a single state.exchange state prop)?
When the form is filled in the result will be handled like:
{exchange: 'a', market: 'b'}
So that would be state too (I guess?)
My understanding of redux is that we should only be storing stateful data in the store, that is, data that is subject to change. Static data by definition does not have state, and therefore does not need to be tracked as such.
As a result, I typically have a /common/app-const.js file where I store these types of static objects. In your case, you can simply move all the static data from exchange.js into a common file that you then import wherever you need it.
/common/app-const.js
export default {
markets: [
{ pair: ['USD', 'BTC'], minimalOrder: { amount: 0.01, unit: 'asset' } },
{ pair: ['RUR', 'BTC'], minimalOrder: { amount: 0.01, unit: 'asset' } },
{ pair: ['EUR', 'BTC'], minimalOrder: { amount: 0.01, unit: 'asset' } },
...
}
I understand your approach however, it would be nice to simply inject your data, by way of connect() via react-redux, however its a bit more straightforward to just import the static data from a file where needed.