formidablelabs / freactal Goto Github PK
View Code? Open in Web Editor NEWClean and robust state management for React and React-like libs.
License: MIT License
Clean and robust state management for React and React-like libs.
License: MIT License
I was reading the new article by @tptee on freactal which puts some emphasis on the breach on contract that for example Redux is forcing on us because a component should manage and contain its own state.
This kinda implies that freactal does not do this and therefore doesn't allow arbitrary descendants to pick state from ancestors. However the documentation says otherwise:
Because Child is contained within the subtree where Parent is the root node, it has access to the Parent component's state.
This seems to imply that no matter how deep the child is, if it's an descendant of the parent, it can access and mutate the ancestor's state which essentially breaks the encapsulation.
Now this is a super useful behaviour when you actually need to share state between components meaning the state does not belong to a given component. However when you do need to create a state that does belong to a given component alone, it does not seem that there is a way to do it right now and that's why i had opened #22 to discuss this.
So is my understanding on how freactal manage and provide state to ancestors wrong or is the article incorrect and little bit misleading in that regard ?
On a side note, while the example of child / parent / grand-parent has been fixed in the Readme on Github, the article is still using the incorrect one.
And don't get me wrong, I very much like this project and it's great article, I just find the stance on breach of contract a bit misleading because as far as I see it, freactal is in the same boat as redux when it comes to encapsulation of component's state for now.
Trying to test out the example app but npm start
returns:
> [email protected] start /Users/charlesdu/src/rails/freactal/example
> NODE_ENV=development babel-node ./src/server
module.js:472
throw err;
^
Error: Cannot find module './lib/server'
at Function.Module._resolveFilename (module.js:470:15)
at Function.Module._load (module.js:418:25)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at Object.<anonymous> (/Users/charlesdu/src/rails/freactal/server.js:1:18)
at Module._compile (module.js:571:32)
at loader (/Users/charlesdu/src/rails/freactal/example/node_modules/babel-register/lib/node.js:144:5)
at Object.require.extensions.(anonymous function) [as .js] (/Users/charlesdu/src/rails/freactal/example/node_modules/babel-register/lib/node.js:154:7)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
seems it is looking to load a module ./lib/server
from ~/freactal/server.js but there is no /lib directory there?
I have the following effects:
const setLoading = softUpdate(({ loading }, val) => ({ loading: loading += val ? 1 : -1 }));
const getLayouts = efects => (async () => {
await effects.setLoading(true);
const {data: layouts} = await axios.get('/layouts');
await effects.setLoading(false);
return state => ({ ...state, layouts });
})();
const effects = {
getLayouts,
setLoading
};
export default effects;
However, the await effects.setLoading
call does not seem to actually trigger the setLoading effect.
Is there a recommended way I can use async functions for effects?
I'm trying out freactal
for an internal admin tool, and I'm running into an issue with handling history changes as a response to effects.
I'm using react-router@v4
for routing, which no longer exposes a history singleton, meaning that I have to wrap my components in withRouter
to get access to the history object on my component's props and be able to make changes to it. However, I'd like to redirect the user after making an API call, but there isn't a way to get at the props in an effect, so I end up writing component methods like these:
addAsset = async params => {
const { effects: { addAsset }, history } = this.props;
await addAsset(params);
history.push('/marketing/assets');
};
I've thought about wrapping my root component in withRouter
and then setting the history in my state, but then I still can't access the state within an effect to be able to make changes to it.
Do you have any thoughts on how to best get around this issue without having to write a bunch of boilerplate component methods?
As I understand, freactal provide only local state concept. Maybe it would be make sense to add possibility to share some state (example: between pages in next.js) without composition on app root for preventing reconciliation on whole components tree.
Here @divmain twits about it https://twitter.com/divmain/status/861077765830852608
Right now when you want some states, you have to create and provide it through the context using provideState
and then inject it into your component with injectState
. This is great when you are dealing with state containers you intend to access from multiple components and in that regards they are pretty much akin to a Mobx Store with their respective Provider
and Inject
. (In respect to how they are declared and injected).
However when I want to to provide a state only for a given component, I kinda want to encapsulate the state with my component without exposing it though the context.
What do you think about introducing a withState
which takes the same object as provideState
and create an HoC that I can use to provide state through props directly to my component ?
Hi,
Is there a way to access the current state from inside an effect ?
My use case is a saveToRemoteStuff
effect that would take a computed value from the state, send it with a POST
request and then, when the POST
succeed update the state with { dataSaved: true }
(or something like this...)
The problem is that I don't have access to the state in my effect ๐ข
computed: {
dataToSave: ({ ... }) => (...)
},
effects: {
saveToRemoteStuff: () => {
return save(/* Can't access dataToSave here :/ */)
.then(() => mergeIntoState({ dataSaved: true }))
}
My solution for now is to get computed value in the component and then pass it to the effect but it feels wrong.
computed: {
dataToSave: ({ ... }) => (...)
},
effects: {
saveToRemoteStuff: (effects, dataToSave) => save(dataToSave)
.then(() => mergeIntoState({ dataSaved: true }))
}
Is there any better way to do this kind of things ?
Ask me if you need more details :)
Etienne.
I have read some of the issues posted here but can't seems to understand it clearly so I decided to create it myself.
Here's my code
<Form onSubmit={_handleSearching()}>
<Form.Input
type="text"
id="barcode"
icon="search"
placeholder="Search barcode..."
/>
</Form>
Current state value will appear here and will update each form submission
<Step
icon="inbox"
title={"Indibox : " + state.indibar}
description="date processed: "
/>
However getting the state a value is behind like this doing a console.log
const _handleSearching = event => {
event.preventDefault();
const barcode = document.getElementById("barcode").value; //Z09J43H
effects.readBarcode(barcode);
console.log(state.indibar);
};
My state wrapper
const stateWrapperIndibox = provideState({
initialState: () => ({ indibar: "val" }),
effects: {
readBarcode: (effects, newVal) => state =>
Object.assign({}, state, { indibar: newVal })
}
});
Thanks
I did not understand from examples, how can I change the state of stateless component with socket.io
Currently my component's state heavily depends on data arrived from socket.io.
I see that you solved fetch-like pull requests, but how can I set state on push-like socket messages?
Right now I have stateful HOC with lifecycle.
I'm making socket initialization on componentDidMount, saving socket to redux store if componentWillUnmount to reuse socket connection on repeated mount. It feels like I need to keep my HOC components stateful, but how can I inject Freactal
state from HOC component's lifecycle?
BTW for sure your lib is straight to the point replacement to redux. Thank you very much for your creative mindset!
I'm using node v6.11.0 and npm 3.10.10
Here is the console output:
> [email protected] test /Users/XXX/freactal
> mocha spec/
/Users/XXX/freactal/spec/integration/nested-state-injection.spec.js:63
it("children are updated when intermediate state injections are present", async function () {
^^^^^
SyntaxError: missing ) after argument list
at createScript (vm.js:56:10)
at Object.runInThisContext (vm.js:97:10)
at Module._compile (module.js:542:28)
at loader (/Users/XXX/freactal/node_modules/babel-register/lib/node.js:144:5)
at Object.require.extensions.(anonymous function) [as .js] (/Users/XXX/freactal/node_modules/babel-register/lib/node.js:154:7)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.require (module.js:497:17)
at require (internal/module.js:20:19)
at fs.readdirSync.forEach.filename (/Users/XXX/freactal/spec/index.js:50:5)
at Array.forEach (native)
at recursiveRequire (/Users/XXX/freactal/spec/index.js:45:69)
at Suite.describe (/Users/XXX/freactal/spec/index.js:48:30)
at Object.create (/Users/XXX/freactal/node_modules/mocha/lib/interfaces/common.js:114:19)
at context.describe.context.context (/Users/XXX/freactal/node_modules/mocha/lib/interfaces/bdd.js:44:27)
at fs.readdirSync.forEach.filename (/Users/XXX/freactal/spec/index.js:48:5)
at Array.forEach (native)
at recursiveRequire (/Users/XXX/freactal/spec/index.js:45:69)
at Object.<anonymous> (/Users/XXX/freactal/spec/index.js:54:1)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.require (module.js:497:17)
at require (internal/module.js:20:19)
at /Users/XXX/freactal/node_modules/mocha/lib/mocha.js:230:27
at Array.forEach (native)
at Mocha.loadFiles (/Users/XXX/freactal/node_modules/mocha/lib/mocha.js:227:14)
at Mocha.run (/Users/XXX/freactal/node_modules/mocha/lib/mocha.js:495:10)
at Object.<anonymous> (/Users/XXX/freactal/node_modules/mocha/bin/_mocha:469:18)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.runMain (module.js:604:10)
at run (bootstrap_node.js:389:7)
at startup (bootstrap_node.js:149:9)
at bootstrap_node.js:504:3
npm ERR! Darwin 15.6.0
npm ERR! argv "/Users/XXX/.nvm/versions/node/v6.11.0/bin/node" "/Users/XXX/.nvm/versions/node/v6.11.0/bin/npm" "run" "test"
npm ERR! node v6.11.0
npm ERR! npm v3.10.10
npm ERR! code ELIFECYCLE
npm ERR! [email protected] test: `mocha spec/`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] test script 'mocha spec/'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the freactal package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR! mocha spec/
npm ERR! You can get information on how to open an issue for this project with:
npm ERR! npm bugs freactal
npm ERR! Or if that isn't available, you can get their info via:
npm ERR! npm owner ls freactal
npm ERR! There is likely additional logging output above.
npm ERR! Please include the following file with any support request:
npm ERR! /Users/XXX/freactal/npm-debug.log
npm ERR! Darwin 15.6.0
npm ERR! argv "/Users/XXX/.nvm/versions/node/v6.11.0/bin/node" "/Users/XXX/.nvm/versions/node/v6.11.0/bin/npm" "run" "check"
npm ERR! node v6.11.0
npm ERR! npm v3.10.10
npm ERR! code ELIFECYCLE
npm ERR! [email protected] check: `npm run lint && npm run test`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] check script 'npm run lint && npm run test'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the freactal package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR! npm run lint && npm run test
npm ERR! You can get information on how to open an issue for this project with:
npm ERR! npm bugs freactal
npm ERR! Or if that isn't available, you can get their info via:
npm ERR! npm owner ls freactal
npm ERR! There is likely additional logging output above.
npm ERR! Please include the following file with any support request:
npm ERR! /Users/XXX/freactal/npm-debug.log
Depends on FormidableLabs/rapscallion#51.
I've been spending way too long on this, is there some reason this doesn't work?
export const getWeather = (effects, coords) => axios.get(`http://api.openweathermap.org/data/2.5/weather?lat=${coords[1]}&lon=${coords[0]}&units=metric&appid=APIKEY`).then(response => hardUpdate({ weather: response.data.main }) );
Was there any discussions on that topic. Do you have any documentation or something? I was thinking it would work out of the box with just react-hot-loader and sadly enough it didn't. You can check out the code here and try run it yourself - https://github.com/Piliponful/react-starter/tree/freactal
Have you used freactal
with webpack
hot module replacement and the react-hot-loader
? Just built a small test project with freactal
and am not seeing component updates via the webpack-dev-server
when I update files. Perhaps shouldComponentUpdate
is preventing the re-render?
I'm going to look more closely as I'm able, but wanted to put this question out there in case others have an answer.
The testing the state container part of the Readme doesn't work.
const wrapComponentWithState = provideState({
initialState: () => ({
givenName: "Walter",
familyName: "Harriman"
}),
effects: {
setGivenName: softUpdate((state, val) => ({ givenName: val })),
setFamilyName: softUpdate((state, val) => ({ familyName: val }))
},
computed: {
fullName: ({ givenName, familyName }) => `${givenName} ${familyName}`,
greeting: ({ fullName }) => `Hi, ${fullName}, and welcome!`
}
});
const { effects, getState } = wrapComponentWithState();
console.log(wrapComponentWithState())
getState().fullName.should.equal("Walter Harriman");
This is the output I'm getting,
Warning: Accessing PropTypes via the main React package is deprecated. Use the prop-types package from npm instead.
{ [Function: StatefulComponent]
childContextTypes: { freactal: { [Function: bound checkType] isRequired: [Function: bound checkType] } },
contextTypes: { freactal: { [Function: bound checkType] isRequired: [Function: bound checkType] } } }
/home/pyros2097/Code/rad/test/abc.js:56
getState().fullName.should.equal("Walter Harriman");
^
TypeError: getState is not a function
at Object.<anonymous> (/home/pyros2097/Code/rad/test/abc.js:49:1)
at Module._compile (module.js:571:32)
at loader (/home/pyros2097/Code/rad/node_modules/babel-cli/node_modules/babel-register/lib/node.js:144:5)
at Object.require.extensions.(anonymous function) [as .js] (/home/pyros2097/Code/rad/node_modules/babel-cli/node_modules/babel-register/lib/node.js:154:7)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Function.Module.runMain (module.js:605:10)
at Object.<anonymous> (/home/pyros2097/Code/rad/node_modules/babel-cli/lib/_babel-node.js:154:22)
at Module._compile (module.js:571:32)
I'm using,
babel-node 6.24.0
The README states that it could effectively replace redux and redux-saga. The main reason (for me at least) to use redux-saga over redux-thunk is testability.
The testing section is a bit sparse on testing side effects (in the form of e.g. API calls). Could you provide some more examples?
If there is an error thrown in a child render method under provide
then componentDidMount
won't fire before componentWillUnmount
, causing this.unsubscribe
to be undefined.
When componentWillUnmount
is called to unmount the partial tree this.unsubscribe
is called, resulting in TypeError: this.unsubscribe is not a function at StatefulComponent.componentWillUnmount
. This masks the actual error being thrown in the child render method, making it difficult to debug in development.
After #4, the README is incorrect and very incomplete.
Currently All my projects using redux with immutable.js
instead of Object.assign
approach.
If you do not mind, I could help you and make a pull request of immutable.js
lib usage for better performance and all goods which comes with immutability.
Right now, freactal
provides a hardUpdate
helper:
import { provideState, hardUpdate } from "freactal";
const wrapComponentWithState = provideState({
// ...
effects: {
myEffect: hardUpdate({ setThisKey: "to this value..." })
}
});
...and a softUpdate
helper:
import { provideState, softUpdate } from "freactal";
const wrapComponentWithState = provideState({
// ...
effects: {
myEffect: softUpdate(state => ({ counter: state.counter + 1 }))
}
});
... where the core difference is that hardUpdate
takes a partial state object (independent of previous state), while softUpdate
takes a function that's called with state
and returns a partial state object.
What if we combined both into a single update
helper that 1.) behaves like hardUpdate
if you call it with an object and 2.) behaves like softUpdate
if you call it with a function? The idea is that both of these would be possible:
effects: {
resetCounter: update({ counter: 0 }),
incrementCounter: update(state => ({ counter: state.counter + 1 }))
}
Currently, middleware is just invoked with the entire state
tree and effects
list on every update. Middleware is especially useful when it can respond to a specific subset of effects, as well as know what that effect contained.
The existing recommendation is to just overwrite the effects with a wrapping utility, but that doesn't seem consistent with what a middleware API should provide since that means you'd be redefining your effects every time an effect is triggered, which I can't imagine is great for performance either. You want your middleware to get the data it needs by default as well.
Can middleware be invoked at a different point in the code path so it has access to effect names
and effect payload without having to redefine effects on every update?
Context is still considered an experimental API and the React team recommends avoiding it in most situations.
If you aren't an experienced React developer, don't use context. There is usually a better way to implement functionality just using props and state.
If you insist on using context despite these warnings, try to isolate your use of context to a small area and avoid using the context API directly when possible so that it's easier to upgrade when the API changes.
If freactal
requires that user's to explicitly use contextTypes
I believe it will hurt adoption as it's unlikely community leaders or the React team will support an API that requires new users to use context directly. While context is still used in a number of popular data management libraries, it's typically hidden from the user via HOCs. I recommend one of two approaches:
Attach contextTypes
to the component passed to addState
for the user, so it automatically gets the context data.
Only attach contextTypes
to StatefulComponent
and pass state
and effects
down via props instead of asking the user to use context
I would prefer option 2 if possible as it removes context from the user-facing API completely.
Is there a way to read state in effects, prior to the atomic state transformation?
In redux
, a prototypical example would be:
export function fetchPostsIfNeeded(subreddit) {
// Note that the function also receives getState()
// which lets you choose what to dispatch next.
// This is useful for avoiding a network request if
// a cached value is already available.
return (dispatch, getState) => {
if (shouldFetchPosts(getState(), subreddit)) {
// Dispatch a thunk from thunk!
return dispatch(fetchPosts(subreddit))
} else {
// Let the calling code know there's nothing to wait for.
return Promise.resolve()
}
}
}
In that example, getState()
is used to inspect whether a fetch is needed.
this
const wrapWithPending = (pendingKey, cb) => effects =>
effects.setFlag(pendingKey, true)
.then(cb)
.then(value => effects.setFlag(pendingKey, false).then(() => value));
needs to be
const wrapWithPending = (pendingKey, cb) => effects =>
effects.setFlag(pendingKey, true)
.then(() => cb)
.then(value => effects.setFlag(pendingKey, false).then(() => value));
I am wondering if there is a plan to add a subscription or side effects api. The use case would be to execute functions on state change outside of component rendering. Example: syncing with local storage.
For example, effects like this:
effects: {
commentVote: (effects, id) => {
fetch(url, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: id
})
}).then(resp => {
// response: { code: 200, result: 1}
// do not change state here
return resp
})
}
}
Use it:
effects.commentVote("78907890").then(resp => {
if (resp.result === 1) {
alert("Comment vote success!")
}
})
In this case, commentVote
doesn't change state.
Promise.resolve
returns an object.
export const getEffects = (hocState, effectDefs, parentEffects) => {
const applyReducer = reducer => {
const result = reducer ? reducer(hocState.state) : null;
// In this case, reducer should be an object
// reducer = { code: 200, result: 1 }
if (result) {
hocState.setState(result);
}
return result;
};
const effects = Object.keys(effectDefs).reduce((memo, effectKey) => {
const effectFn = effectDefs[effectKey];
memo[effectKey] = (...args) => Promise.resolve()
.then(() => effectFn(effects, ...args))
.then(applyReducer);
return memo;
}, Object.assign({}, parentEffects));
return effects;
};
Great library, looking forward to trying it out in real projects.
Is there any reason why I have to work with state
and effects
props in my components? Any reason for not having some sort of mapping functionality like in Redux, i.e. mapStateToProps/mapEffectsToProps?
At the moment API feels slightly angular-esque and if I was to use dependency injection analogy - currently I have to pass an entire container into my components rather than individual dependencies.
Hope this makes sense.
When provideState
is invoked like so:
provideState({
namespace: "myNamespace",
initialState: () => ({ value: "myValue" }),
effects: { setVal: softUpdate((state, value) => ({ value })) }
});
All state values and effect functions will be silo'd within that namespace. Consuming from a child component would look like:
injectState(({ state, effects }) => (
<div>
{state.myNamespace.value}
<button
onClick={() => effects.myNamespace.setVal("newValue")}
>
Click me!
</button>
</div>
));
Some people still use/prefer UMD libraries. Providing one also means you could load freactal from a CDN like unpkg, which would be useful for reproducing issues (creating JSFiddles, for example)
Ironically, the VDOM partial render in freactal
doesn't play nice with rapscallion
's caching technique. A small extension library may be necessary for freactal
to rely on rapscallion
's render rather than its own internal partial-render implementation.
What's the best way to initialize state to a specific prop value?
Or, how do I access props within initialState?
Obviously, using React's state you would just set the initial state to this.props.value
, but what is the correct way to do so with freactal?
For example:
const wrapComponentWithState = provideState({
initialState: () => ({
testValue: //Initialize to prop value here
})
})
Hi There,
I face a issue when I use softupdate method to edit Textfield.
I use [email protected] and [email protected].
I try to use softupdate to reset my state value, however, when I try to insert a word within old value. I only could add the first character and then the cursor just jumps to the end.
for example, if I want add "World" before string "University". what I got is "Wuniveristyorld". since after type "W", the cursor jumps to end of textfield.
initialState: () => ({streetNameNumber: ''})
in effects object
setTextValue: softUpdate((state, key, value) => ( {[key]: value })),
in render
<TextField floatingLabelText="Street Number/Name" name="streetNameNumber" id="streetNameNumber" value={state.streetNameNumber} onChange={e => effects.setTextValue(e.target.name, e.target.value)}/>
Anyone know what is going on?
very appreciate.
hardUpdate
effects
and state
into props of injected componentIn the following code, I have a parent component providing state with an effect to toggle a value.
There are 4 copies of a child component that displays and toggles the value.
The 4 are mounted and wrapped in different ways.
All 4 can use the effect to toggle the value, only 3 ever show the updated value.
For the life of me, I can't figure out why that one doesn't work! Am I doing something weird? Is this a bug?
Thanks in advance!
import React from 'react'
import { provideState, injectState, softUpdate } from 'freactal'
const wrapParentWithState = provideState({
initialState: () => ({
toggleMe: true
}),
effects: {
toggle: softUpdate(state => ({ toggleMe: !state.toggleMe }))
}
})
const ParentWithState = wrapParentWithState(() => (
<Parent>
props.children: <Child />
<hr />
BROKEN props.children, with provideState: <ChildWithState />
</Parent>
))
const Parent = injectState(({ state: { toggleMe }, children }) => (
<div>
<div>Parent: toggleMe is {toggleMe ? 'true' : 'false'}</div>
<hr />
<div>{children}</div>
<hr />
<div>direct: <Child /></div>
<hr />
<div>direct, with provideState: <ChildWithState /></div>
</div>
))
const Child = injectState(({ state: { toggleMe }, effects: { toggle } }) => (
<div>
<button onClick={() => toggle()}>Toggle</button>
toggleMe is {toggleMe ? 'true' : 'false'}
</div>
))
const wrapChildWithState = provideState({
initialState: () => ({}),
effects: {}
})
const ChildWithState = wrapChildWithState(Child)
export default ParentWithState
State is available to the component, so does the effects. (They are in different folders, effects is one folder above, dunno if that makes any difference to my problem).
// initial-state.js
export default () => ({
selected: "internal",
isOpen: true
})
// effects.js
export default {
handleDialog: () => state => ({ ...state, isOpen: !state.isOpen })
}
PS: I've tried SoftUpdate and Object.assign as well in the above function.
// React Component that triggers the effects
const NavButtons = ({ effects }) => (
<Wrapper>
<RaisedButton primary label="Request Appraisal" onClick={effects.handleDialog} />
</Wrapper>
);
NavButtons.propTypes = {
effects: PropTypes.shape({
handleDialog: PropTypes.func.isRequired
}).isRequired
};
export default injectState(NavButtons);
// React component that holds the mentioned state
const RequestAppraisal = ({ state, effects }) => {
const actions = [
<RaisedButton label="Submit" primary onTouchTap={effects.handleDialog} />
];
return (
<Dialog
autoScrollBodyContent
title="Create new feedback round"
actions={actions}
modal={false}
open={state.isOpen}
>
<RowWrapper>
<RowTitle>Survey Template</RowTitle>
<Wrapper>
<SurveyTemplate />
</Wrapper>
</RowWrapper>
<RowWrapper>
<RowTitle>Type of Feedback</RowTitle>
<Wrapper>
<TypeOfFeedback />
</Wrapper>
</RowWrapper>
<RowWrapper>
<RowTitle>Add Assessors</RowTitle>
<Wrapper>
<AddAssessors />
</Wrapper>
</RowWrapper>
<RowWrapper>
<Wrapper>
<Assessors />
</Wrapper>
</RowWrapper>
</Dialog>
)
}
RequestAppraisal.propTypes = {
state: PropTypes.shape({
isOpen: PropTypes.bool.isRequired
}).isRequired,
effects: PropTypes.shape({
handleDialog: PropTypes.func.isRequired,
}).isRequired
};
export default provideState({
initialState
})(injectState(RequestAppraisal));
The problem is that the despite being available to the component, the state is only updated if I use the state/effect in the parent component. If I just inject/provide without using them, the state does not update.
Here is an example:
The component above doesn't update if the App.js written like this:
const Dashboard = () => (
<div>
<NavBar />
<Wrapper>
<UserInfo />
<DashboardData />
<RequestAppraisal />
</Wrapper>
</div>
);
export default withState(injectState(Dashboard));
But it does update if App.js has props state:
// NavButtons component is inside NavBar.
const Dashboard = ({ state }) => (
<div>
{console.log(state.isOpen)}
<NavBar />
<Wrapper>
<UserInfo />
<DashboardData />
<RequestAppraisal />
</Wrapper>
</div>
);
export default withState(injectState(Dashboard));
Is that the correct behaviour? Because like in my example, I don't need to use the state/effects in the parent component.
Or I am doing something wrong?
Thanks
In freactal v1.1.0, I want to track the state changes in middleware for debugging just like what redux-logger did. However I find that I can't access the latest state after effects complete in middleware.
Am I missing anything? And it would be fabulous if you could help me out. Thanks!
so i'm trying to have two different provideState
s for pagination, one that handles the pagination state, and one that does the fetching.
const withFetching = provideState({
initialState: () => {
return {
entities: [],
};
},
effects: {
fetchItems: (effects, params) =>
state => {
// TODO: use { page , pageSize } from params
return fetch('/things')
.then(res => res.json())
.then(({things}) => state => ({...state, entities: things}));
},
},
});
const justPagination = provideState({
initialState: () => {
return {
page: 1,
pageSize: 10,
};
},
effects: {
nextPage: effects => {
return state => {
const nextState = {...state, page: page + 1};
return effects.fetchItems(nextState).then(() => nextState);
};
},
},
});
const Container = withFetching(justPagination(YourComponent));
This doesn't work, but I'd like to do something like that. Any suggestions?
I am experiencing an issue when directly passing an effect to an element as an event handler.
It is to do with the empty https://github.com/FormidableLabs/freactal/blob/master/src/effects.js#L9
causing the effectFn
to be defered to the next tick, after which React has nullified the event, making it impossible to extract the values purely via effects.
Changing:
Promise.resolve()
.then(() => effectFn(effects, ...args))
to:
Promise.resolve(effectFn(effects, ...args))
Fixes the issue by calling effectFn
immediately. I am not sure if this will negatively affect the timing of anything else.
I can see in the testing example in the readme (https://github.com/FormidableLabs/freactal/blob/master/README.md#L640) that a wrapper function is used in the render to immediately extract the value from the element.
This works fine, however I have always avoided function declaration within render for performance reasons.
Feel free to close this if it isn't considered an issue, however it is something that I have run into.
setGivenName and setFamilyName should be effects.setGivenName and effects.setFamilyName
/*** state.spec.js ***/
import { wrapComponentWithState } from "./state";
describe("state container", () => {
it("supports fullName", () => {
// Normally, you'd pass a component as the first argument to your
// state template. However, so long as you don't try to render the
// thing, you can get by without doing so, which makes testing your
// state container that much easier.
const { effects, getState } = wrapComponentWithState();
expect(getState().fullName).to.equal("Walter Harriman");
// Since effects return a Promise, we're going to make it easy
// on ourselves and wrap all of our assertions from this point on
// inside a Promise.
return Promise.resolve()
// When a Promise is provided as the return value to a Promise's
// `.then` callback, the outer Promise awaits the inner before
// any subsequent callbacks are fired.
.then(() => setGivenName("Alfred"))
// Now that `givenName` has been set to "Alfred", we can make an
// assertion...
.then(() => expect(getState().fullName).to.equal("Alfred Harriman"))
// Then we can do the same for the family name...
.then(() => setFamilyName("Hitchcock"))
// And make one final assertion.
.then(() => expect(getState().fullName).to.equal("Alfred Hitchcock"));
});
// You could write similar assertions here
it("supports a greeting");
});
injectState is never used.
/*** app.js ***/
import { injectState } from "freactal";
import { wrapComponentWithState } from "./state";
export const App = ({ state, effects }) => {
const { givenName, familyName, fullName, greeting } = state;
const { setGivenName, setFamilyName } = effects;
const onChangeGiven = ev => setGivenName(ev.target.value);
const onChangeFamily = ev => setFamilyName(ev.target.value);
return (
<div>
<div id="greeting">
{ greeting }
</div>
<div>
<label for="given">Enter your given name</label>
<input id="given" onChange={onChangeGiven} value={givenName}/>
<label for="family">Enter your family name</label>
<input id="family" onChange={onChangeFamily} value={familyName}/>
</div>
</div>
);
};
/* Notice that we're exporting both the unwrapped and the state-wrapped component... */
export default wrapComponentWithState(App);
This one is totally confusing. Who is supposed to render what? Where is GrandChild
?
const Child = injectState(({ state }) => (
<div>
This is the GrandChild.
{state.fromParent}
{state.fromGrandParent}
</div>
));
const Parent = provideState({
initialState: () => ({ fromParent: "ParentValue" })
})(() => (
<div>
This is the Child.
<GrandChild />
</div>
));
const GrandParent = provideState({
initialState: () => ({ fromGrandParent: "GrandParentValue" })
})(() => (
<div>
This is the Parent.
<Child />
</div>
));
Hi โ ,
I've been thinking about my problem (see #59 ) and I have a proposal to solve this.
First I think it might be useful to clarify some vocabulary :
modifier : (prevState) => nextState
A modifier
take a state and return a state ยฏ_(ใ)_/ยฏ.
Example :
const counterIncrement = (state) => ({ ...state, counter: state.counter + 1 })
softModifier
It might be useful to create a helper like this :
const softModifier = (newStateCreator) => (state) => ({ ...state, ...(newStateCreator(state)) })
// And then to use it :
const counterIncrement = softModifier(state => ({ counter: state.counter + 1 }))
modifierCreator : (...params) => modifier
A modifierCreator
is just a function that return a modifier
.
Example :
const counterIncrementBy = (amount) => (state) => ({ ...state, counter: state.counter + amount })
// same as
const counterIncrementBy = (amount) => softModifier(state => ({ counter: state.counter + amount }))
effect : (effects, state) => Promise(modifier)
An effect would be similar to what it's now but it will not accept additional arguments and instead it will take the current state as second argument.
Just like in current API the result would be wrapped in a Promise if necessary so we don't have to create a promise for synchronous effects.
Example :
const incrementCounter = (effects, state) => counterIncrement;
// same as
const incrementCounter = (effects, state) => (state) => ({ ...state, counter: state.counter + 1 });
// you can also use `modifierCreator` :
const incrementCounterBy2 = (effects, state) => counterIncrementBy(2);
effectCreator = (...params) => affect
Finally an effectCreator
is a function that return a effect.
This is what you would use to pass argument to effect.
Example:
const updateCounterBy = (amount) => (effects, state) => counterIncrementBy(amount);
// but we can omit effects and state here
const updateCounterBy = (amount) => () => counterIncrementBy(amount);
The idea is that instead of declaring effects
like in the current API we would declare effectCreator
.
// we would replace this :
updateCounterBy: (effects, addVal) => state => ({ ...state, counter: state.counter + addVal })
// by this
updateCounterBy: (addVal) => () => state => ({ ...state, counter: state.counter + addVal })
An interesting consequence of modifiers
is that it's easy to compose then :
const incrementTwice = () => () => compose(counterIncrement, counterIncrement);
// we might want some helper so we can write
const incrementTwice = modifierEffect(compose(counterIncrement, counterIncrement));
Waiting for you feedback about this ๐ค
I could create a PR if you like the idea ๐
The idea of deferring state management to freactal
is solid but as far as I can tell, users will still need to use class components if they want to trigger effects in lifecycle methods, and the README seems to imply that you can always use SFCs because freactal
is orchestrating state, but that feels slightly misleading since React provides more than just state management APIs
Maybe add an example uses a stateless class component triggering effects in lifecycle methods to make it clear that stateless doesn't always mean functional components?
Super excited to try this out. Mad props to you guys at formidable ๐
Would someone care to help me see how freactal fits alongside some of the points from the zen of python? I'm new to this set of 'zen' principles. So far I like them, I'd like to use them myself. It'd be good to know how you interpret these points and see how they align with freactal.
How can I simply call one synchronous effect from another (synchronous) effect? In my example below, the reactLevel*
effects call the changeLevel
effect. I'd like to write it very cleanly, with resetLevelBad
, but that (spoiler) doesn't work.
export default withState({
...
effects: {
changeLevel: (effects, level) => state => doSomethingInteresting(level),
resetLevelBad: effects => state => effects.changeLevel(state.level),
resetLevelGood: effects => state => {
effects.changeLevel(state.level);
return state; // why do I have to return state here?
},
},
});
Specifically, resetLevelBad
sets the state to undefined
for one tick (I know this because my computed
functions were erroring), and then changeLevel
is called and all is well again. Maybe have freactal assume that returning a Promise from an effect means don't alter the state? Or better yet, just wait on that promise.
Just had read through the docs and can't wait to try it. However I have came up with questions when reading sections on changeBothStates
effect.
In the doc the effect is defined in Parent
state, which it accesses an effect on GrandParent
. This seem like a design that asks for trouble as the Parent
state should be aware of the information in GrandParent
. This causes questions on separation of concerns. What if changeGrandParentState
has been removed or changed to multiple functions? I would have to change changeBothStates
in Parent
effects and all states that accesses changeGrandParentState
effect. What is the thoughts behind the merge all effects into one global effects design?
Another concern is that instead of Promise.then()
, in some cases Promise.all()
may be preferred, in Redux world it would be one action triggers changes in multiple reducers.
Would this work for these situations?
changeBothStates: (effects, value) =>
Promise.all([
effects.changeGrandParentState(value),
effects.changeParentState(value),
])
Quick search for the keyword lodash
shows that 3 functions are used, and they are painlessly replaceable:
keys
- Object.keys
assign
- Object.assign
isFunction
- instanceof Function
or just steal the snippet https://github.com/lodash/lodash/blob/3.0.8-npm-packages/lodash.isfunction/index.jsWould this something you are interested in?
we should probably be using this ponyfill instead:
Hi @divmain,
Thanks for freactal
I really love how it composes things. I have been playing with it for past few days and wanted your opinion on few things I've encountered. These may very well be an issue with how I am using the library.
I created a simple client-side rendered app with two state container components (i.e. components wrapped with provideState
one at main level and the second one is nested
within parent).
Here is the gist with complete code: https://gist.github.com/niki4810/57f9ca785ccfe00c9789b16668e4c6a4
These are the issues I am noticing:
fetch
requests which calling service from effects.initialize
:effects.initialize
to make an fetch
call to initialize the state data. I noticed that when I do this, this results in multiple fetch requests. I don't notice the same behavior when I remove the nested container component.
effect.initialize
?injectState
call the async effect from within componentDidMount
function. For e.g:class Title extends React.Component {
componentDidMount() {
this.props.effects.getItemDetails(); // async call to fetch initial state data
}
render () {
const {selectedItem = {}} = state;
const {title = ""} = selectedItem;
return (
<h1>{title}</h1>
);
}
}
const StatefulTitle = injectState(Title);
This solution fixes the multiple network requests issue, but not sure if this is recommended.
initialState
returns empty object, the state does not get's merged when server returns datainitialState: () => {
return { };
},
effects: {
getItemDetails: effects => fetch("http://output.jsbin.com/nuxodel.json")
.then(result => result.json())
.then(data => state => Object.assign({}, state, data)),
},
If I later on call effects.getItemDetails
call to my data providers (for e.g. here my service : http://output.jsbin.com/nuxodel.json), the state does not get merged correctly. Am I doing something wrong ?
Please let me know if you need further code samples. Looking forward for you feedback.
Thanks,
Nikhilesh
The README gives the following examples for effects:
addOne: () => state => Object.assign({}, state, { counter: state.counter + 1 })
/* vs */
addOne: () => state => Promise.resolve(Object.assign({}, state, { counter: state.counter + 1 }))
/* vs */
addOne: () => state => new Promise(resolve => resolve(Object.assign({}, state, { counter: state.counter + 1 })))
To put it explicitly, the value you provide for each key in your effects object is:
- A function that takes in some arguments (we'll cover those shortly) and returns...
- A promise that resolves to...
- A function that takes in state and returns...
- The updated state.
...
So, you might define the following effect:
updatePosts: () => fetch("/api/posts")
.then(result => result.json())
.then(({ posts }) => state => Object.assign({}, state, { posts }))
The fetch
example matches the flow described (and makes sense), but the first set of examples above are functions that take some arguments that return functions that take in state and return promises. I.e. they have 2 and 3 the wrong way round. Is this also valid?
Happy to PR if the addOne
examples need changing, or to shut up if I've just misunderstood.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.