Transformers are pure functions that take an initial object, a chunk of state, and then return a new object.
The use case for transformers is often when using selectors, the desired output is some derivation or combination of the pieces of state selectors return. This library serves to formalize a lot of the common transformations used, and provide the ability to pipe/compose them in a descriptive and functional way.
If we have the following state:
const state = {
first: '1',
second: {
innerProp: 'test',
},
}
And there are already selectors for the first and second props, but we only want the resulting object to have first and innerProp, my final, anonymous selector must perform a relatively complex transformation:
const desiredResult = {
first: '1',
innerProp: 'test',
};
const firstSelector = R.prop('first');
const secondSelector = R.prop('second');
const mySelector = createSelector(
firstSelector,
secondSelector,
(first, second) => {
return R.merge(first, { inner: R.prop('innerProp', second))
});
mySelector(state); //=> Outputs desired result
Although this isn't overly complex, you can imagine a scenario where you need state from multiple selectors and need to change all the key names, omit certain props, add new props that are derived from state, in order to get a final object that you can use for an API call etc. This results in a complex, highly coupled, and unreadable "final" selector function.
Instead, transformers look like this:
const initialObject = {};
const state = {
first: '1',
second: {
innerProp: 'test',
},
};
const myTransformer = createTransformer(
set('one', R.prop('first')),
set('innerProp', R.path(['second', 'innerProp']))
);
const result = myTransformer(initialObj, state);
//=>
// {
// first: '1',
// innerProp: 'test',
// }
The full state gets passed to each transformer in the chain. You can also turn a transformer into a selector by providing a "root" selector as the state input, which should define all of the state required for the transformer to work:
const mySelector = (
firstSelector,
secondSelector,
R.merge
);
const myTransformer = createTransformer(
set('one', R.prop('first')),
set('innerProp', R.path(['second', 'innerProp']))
);
const selectorToCallWithState = myTransformer(initialObj, mySelector);
// => Returns a selector
const selectorToCallWithState(state); //=>
// {
// first: '1',
// innerProp: 'test',
}
Takes an initialObject, another object (usually state), and returns a new object.
Takes multiple input transformers and returns a new transformer that will sequentially apply all the input transformers. This allows for reusability and cool composition.
These are just pre-built transformers that mostly wrap ramda functions or fulfill common use cases I've run into. You can easily add your own transformers as long as they follow the type signature above.
Takes a key name, a value, and returns a transformer. If the value is a function, it will be applied to the state before the value is set.
set('key', 'value')({}) // => { key: 'value'}
set('key', R.prop('stateKey'))({}, {stateKey: 'value'}) //=> { key: 'value'}
Like set, but will set the provided key to the value returned by calling the provided Transformer with an empty object and the state.
const setTransformer = set('key', 'value');
setWithTxr('key', setTransformer)({})
//=> {
key: {
key: 'value',
}
}
Takes a keymap object and returns a trasformer that will attempt to remap the keys of the provided initial object. Can use dot delimited paths for nested properties. If the input object is missing a prop, the key will be set to null in the output object.
const keyMap = {
first: 'one',
'second.innerProp': 'two.coolProp',
}
const myKeymapper = mapKeys(keyMap);
const initialObj = {
first: 'first',
second: {
innerProp: 'second',
}
};
myKeymapper(initialObj);
// => {
one: 'first',
two: {
coolProp: 'second',
}
}
Wrapper for R.omit. Will omit keys from the initial object.
const omitter = omit(['one']);
omitter({ one: 'one', two: 'two'}); //=> { two: 'two'}
Effectively a wrapper for R.cond. Takes a conditional, a true case trx, and a false case trx. Returns a transformer that is just the transformer for the case.
If the conditional is a function, the function will be applied to the state before the appropriate transformer is returned.
const state = { importantFlag: false };
const myCond = cond(
R.prop('importantFlag'),
set('key', 'true'),
set('key', 'false')
);
cond({}, state) //=> { key: 'false' }
Dummy transformer that logs its input arguments and passes the initialObject back. Generally side effects shouldn't happen inside transformers, but is useful for debugging long chains in createTransformer.
const myTxr = createTransformer(
set('key', 'valeu'), //whoops, typo;
logger,
set('key2', 'value2'),
);
myTxr({})
//=> Will console.log intermediate object { key: 'value'};
These are some convenience functions that can be used as transformer arguments (mostly cond/set) that keep things simple.
Takes a dot delimited string, or a path array, and a value. Returns a function that accepts the state, and returns whether the value found at the path equals the input value.
const state = { test: 'test '};
cond(pathEqual('test', 'test'), txr1, txr2)(state)
//=> Will return txr1
Same as pathEqual, but checks that the value at the path is not equal.
Will lookup the value at the path in initialObject and apply the provided function to it. Useful for setting things from the input object and applying simple changes to the value.
If no function is provided, it defaults to R.identity
Can take list of strings or dot delimited string for path.
const txr = set('initialVal', fromInitial('test', R.upper);
const initialObj = { test: 'initial' };
txr(initialObj);
//=> { initialVal: 'INITIAL' }
Like fromInitial, but looks up the path in state.