Also, the action creator overrides toString() so that the action type becomes its string representation - redux

I'm learning redux-toolkit from the official docs and came across this line- Also, the action creator overrides toString() so that the action type becomes its string representation. What does it mean?
Here's the code from the docs:
const INCREMENT = 'counter/increment'
function increment(amount) {
return {
type: INCREMENT,
payload: amount
}
}
const action = increment(3)
// { type: 'counter/increment', payload: 3 }
const increment = createAction('counter/increment')
let action = increment()
// { type: 'counter/increment' }
action = increment(3)
// returns { type: 'counter/increment', payload: 3 }
console.log(increment.toString())
// 'counter/increment'
console.log(`The action type is: ${increment}`)
// 'The action type is: counter/increment'
So, for example, when I write something like
const increment = createAction("INCREMENT")
console.log(increment.toString())
It's logging INCREMENT. So is this overriding of toString()? I'm really confused.
I'm new to redux-toolkit and any help would be appreciated. Thanks.

Normally, if you call toString() on a function, it returns the literal source text that was used to define the function:
function myFunction() {
const a = 42;
console.log(a);
}
myFunction.toString()
"function myFunction() {
const a = 42;
console.log(a);
}"
However, in this case, we want someActionCreator.toString() to return the action type that will be part of the action objects it creates:
const addTodo = createAction("todos/addTodo");
console.log(addTodo("Buy milk"));
// {type: "todos/addTodo", payload: "Buy milk"}
console.log(addTodo.toString());
// "todos/addTodo"
To make this happen, createAction overrides the actual implementation of toString for these action creators:
export function createAction(type: string): any {
function actionCreator(...args: any[]) {
return { type, payload: args[0] }
}
actionCreator.toString = () => `${type}`
actionCreator.type = type
return actionCreator;
}
This is especially useful because ES6 object literal computed properties automatically try to stringify whatever values you've passed in. So, you can now use an action creator function as the key in an object, and it'll get converted to the type string:
const reducersObject = {
[addTodo]: (state, action) => state.push(action.payload)
}
console.log(reducersObject);
// { "todos/addTodo": Function}

Related

"Force unwrapping" in Flow?

I have this helper function in my reducer, which has the given state:
type CustomerCollection = { [number]: Customer }
type CustomerState = {
+customers: ?CustomerCollection,
+newItem: ?(Customer | Review),
+searchResults: ?(Customer[]),
+error: ?string,
+isLoading: boolean
};
function customerWithReview(review: Review): Customer {
const id: number = review.customerId;
const oldCustomer: Customer = state.customers[id];
const newReviews: Review[] = [review, ...oldCustomer.reviews];
return Object.assign(oldCustomer, { reviews: newReviews });
}
I get a Flow error on the id of const oldCustomer: Customer = state.customers[id]; saying Cannot get state.customers[id] because an index signature declaring the expected key/value type is missing in null or undefined.
This is happening because of the nullable/optional ?CustomerCollection type of state.customers.
I can silence the error by making sure customers isn't null:
if (state.customers) {
const oldCustomer: Customer = state.customers[id];
const newReviews: Review[] = [review, ...oldCustomer.reviews];
return Object.assign(oldCustomer, { reviews: newReviews });
}
But then the problem just goes up the chain because I don't have anything to return from the function.
I can certainly expand it to:
function customerWithReview(review: Review): Customer {
if (!state.customers) {
return new Customer();
} else {
const id: number = review.customerId;
const oldCustomer: Customer = state.customers[id];
const newReviews: Review[] = [review, ...oldCustomer.reviews];
return Object.assign(oldCustomer, { reviews: newReviews });
}
}
But in actual practice, the action that gets us to this branch of the reducer will never be called if state.customers is null, and we'd never return new Customer() and would have no use for it if we did. state.customers is nullable in order to tell the difference between "we haven't fetched the customers yet (state.customers == null)" and "we've fetched the customers but there are none (state.customers == {}).
It would be a lot easier if I could just assert that state.customers would always exist in these cases, which in Swift I would do with force-unwrapping:
const oldCustomer: Customer = state.customers![id];
Can I do anything like this with Flow?
Or, given that only my GET_CUSTOMERS_FAILURE action would ever deal with state.customers == null, is there some other way to restructure my reducer so that this is a little easier? An entirely separate fetchReducer that is has a nullable customer collection while the rest of the actions fall under a different reducer?
You can use invariant function (Check that it works here):
type Customer = { id: number, reviews: Array<Review> };
type Review = { customerId: number };
type CustomerCollection = { [number]: Customer }
type CustomerState = {
+customers: ?CustomerCollection,
+newItem: ?(Customer | Review),
+searchResults: ?(Customer[]),
+error: ?string,
+isLoading: boolean
};
declare var state: CustomerState;
declare function invariant(): void;
function customerWithReview(review: Review): Customer {
const id: number = review.customerId;
invariant(state.customers, 'No customers and I don\'t know why');
const oldCustomer: Customer = state.customers[id];
const newReviews: Review[] = [review, ...oldCustomer.reviews];
return Object.assign(oldCustomer, { reviews: newReviews });
}
You can implement it somewhere in your project and import when necessary.
You can implement it like this:
export function invariant<T>(value: ?T, falsyErrorMessage: string, errorParams?: Object): void {
if (!value) {
log.error(falsyErrorMessage, errorParams || {});
throw new Error(INVARIANT_ERROR_MESSAGE);
}
}
Unfortunately, the name of the function is hard-coded in flow.
Alternative variant is just to add an if and to throw an error in your customerWithReview function directly.

Flowtype generic function with different parameters

I'm struggling with flowtype declaration for a generic function with different pairs of parameters.
My goal is to have a function which return an object of certain union type depending on input parameters.
I'm having a big load of messages that i want to type (for this example i'm using only two)
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
type MessageFactory<T> = (type: $PropertyType<T, 'type'>, payload: $PropertyType<T, 'payload'>) => T;
export const factory: MessageFactory<Message1> = (type, payload) => {
return {
event: 'UI',
type,
payload
}
}
factory('receive', true);
// factory('send', { foo: true, bar: "bar" });
when i change
MessageFactory<Message1>
to
MessageFactory<Message1 | Message2>
it will throw an error
Could not decide which case to select. Since case 1 [1] may work but if it doesn't case 2 [2] looks promising too. To fix add a type annotation to `payload` [3] or to `type` [4]
You can ty it here
any idea how to declare this function?
or is it stupid idea and i'm going to the wrong direction?
any better solutions?
Create a GenericMessage with type parameters for your desired properties (type and payload), then have your factory return a GenericMessage:
(Try)
type GenericMessage<TYPE: string, PAYLOAD> = {
event: 'UI',
type: TYPE,
payload: PAYLOAD
}
const factory = <T: string, P>(type: T, payload: P): GenericMessage<T, P> => {
return {
event: 'UI',
type,
payload
}
}
const test1 = factory('receive', true);
const test2 = factory('send', { foo: true, bar: "bar" });
// Let's check the new type against Message1 and Message2:
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
// Type assertions
(test1: Message1);
(test2: Message2);
(test1: Message2); // Error!
If you want, you can create a MessageFactory type that returns a GenericMessage<T, P>. You can also create an EVENT type parameter if you need to control the event property on the object.
(You don't need to call it GenericMessage, I just called it that to make a distinction between your existing types and this new one)

Flow Type Check: How to iterate on possibly undefined property in redux action?

I have a Redux reducer for preferences and I am using Flow Type Checker. My reducer can take two types of actions. One for loading in all of the preferences which happens at initial app load. The second action type happens when the user updates a specific preference. Here is the code for my reducer. Where I run into problems is when I try to do action.prefs.forEach at which point flow throws an error saying ...'prefs': Property not found in 'object type'
// #flow
import {
UPDATE_PREF,
LOAD_PREFS_SUCCESS
} from '../actions/prefs';
export type actionType = {
+type: string,
prefs: Array<{_id: string, value: any}>
} | {
+type: string,
id: string,
value: any
};
export default (state: stateType = {}, action: actionType) => {
switch (action.type) {
case LOAD_PREFS_SUCCESS: {
const newState = {};
action.prefs.forEach(p => {
newState[p._id] = p.value;
});
return newState;
}
case UPDATE_PREF: {
return { ...state, [action.id]: action.value };
}
default:
return state;
}
};
As you can see I have two types of actions. When loading all of the preferences, the action has an array of preferences. [ { _id: 'color', value: 'blue' } ] And when updating a single preference, I get an id and a value. So give my two action types that have different properties, how do I get flow to not throw errors about this variation in action flow types?
In order to tell Flow which type to choose in the disjoint union, +type must be a value instead of string. Change your actionType to use values:
// #flow
import {
UPDATE_PREF,
LOAD_PREFS_SUCCESS
} from '../actions/prefs';
export type actionType = {
+type: LOAD_PREFS_SUCCESS, // not just `string`
prefs: Array<{_id: string, value: any}>
} | {
+type: UPDATE_PREF, // not just `string`
id: string,
value: any
};
Thanks to some guidance from #ross-allen and some playing around on flow.org I have found a working solution.
The short of it is that in addition to Ross' answer of adding +type: UPDATE_PREF, I also needed add typeof. So the working actionType is:
export type actionType = {
+type: typeof LOAD_PREFS_SUCCESS,
prefs: Array<{_id: string, value: any}>
} | {
+type: typeof UPDATE_PREF,
id: string,
value: any
};
Thanks again #ross-allen.

Flow Type: HOC with cloneElement

I am trying to create a higher order component, Hoc, that gives its children some extra props through React.cloneElement. I have not been able to get flowtype to know that the extra props were in fact passed down.
Below is my failed attempt, which throws the error foo type cannot be found on object literal. I would like to know what I can do to fix this.
type Props = {
foo: string,
bar: string,
};
type DefaultProps = {
foo: string,
};
declare class React2$Element<Config, DP> extends React$Element{
type: _ReactClass<DP, *, Config, *>;
}
declare function Hoc<Config, DP: DefaultProps, R: React$Element<Config>>(props: {children: R}) : React2$Element<Config, DP>
function TestComponent({foo, bar}: Props){
return <div>{bar}</div>;
}
function Hoc(props){
return React.cloneElement(props.children, {foo: 'form2wr'});
}
function Test(){
return <Hoc children={<TestComponent bar='yo' />}></Hoc>;
}
I don't have an answer to this question, but I do have a workaround.
type Props = {
foo: string,
bar: string,
};
type DefaultProps = {
foo: string,
};
type WithHOCProps<X> = $Diff<X, DefaultProps>
declare function TestComponent(props: WithHOCProps<Props>) : React$Element;
function TestComponent({foo, bar}: Props){
return <div>{foo + bar}</div>;
}
function Test(){
return <TestComponent bar='yo' />;
}
Tadahhh, no errors.

Lazy loading references from normalized Redux store

Yo! I'm using Redux and Normalizr. The API I'm working with sends down objects that look like this:
{
name: 'Foo',
type: 'ABCD-EFGH-IJKL-MNOP'
}
or like this
{
name: 'Foo2',
children: [
'ABCD-EFGH-IJKL-MNOP',
'QRST-UVWX-YZAB-CDEF'
]
}
I want to be able to asynchronously fetch those related entities (type and children) when the above objects are accessed from the state (in mapStateToProps). Unfortunately, this does not seem to mesh with the Redux way as mapStateToProps is not the right place to call actions. Is there an obvious solution to this case that I'm overlooking (other than pre-fetching all of my data)?
Not sure that I have correctly understood your use-case, but if you want to fetch data, one simple common way is to trigger it from a React component:
var Component = React.createClass({
componentDidMount: function() {
if (!this.props.myObject) {
dispatch(actions.loadObject(this.props.myObjectId));
}
},
render: function() {
const heading = this.props.myObject ?
'My object name is ' + this.props.myObject.name
: 'No object loaded';
return (
<div>
{heading}
</div>
);
},
});
Given the "myObjectId" prop, the component triggers the "myObject" fetching after mounting.
Another common way would be to fetch the data, if it's not already here, from a Redux async action creator (see Redux's doc for more details about this pattern):
// sync action creator:
const FETCH_OBJECT_SUCCESS = 'FETCH_OBJECT_SUCCESS';
function fetchObjectSuccess(objectId, myObject) {
return {
type: FETCH_OBJECT_SUCCESS,
objectId,
myObject,
};
}
// async action creator:
function fetchObject(objectId) {
return (dispatch, getState) => {
const currentAppState = getState();
if (!currentAppState.allObjects[objectId]) {
// fetches the object if not already present in app state:
return fetch('some_url_.../' + objectId)
.then(myObject => (
dispatch(fetchObjectSuccess(objectId, myObject))
));
} else {
return Promise.resolve(); // nothing to wait for
}
};
}

Resources