MobX [mob-ex] – reactive updates via subscriptions.
How to run an open-source library
-
people do not inspect your source code seriously
-
people are not inspired by your ideas and solutions as you are
-
project without a critical mass of users does not look very attractive
-
if you build a library to solve something – make people aware that this "something" is really a problem
-
For MobX it was higher performance numbers comparing to Redux, because of smaller number of re-renders
-
Library should have a short and clear description describing its philosophy and mission
The good parts of open-source
- Learning via leaving the comfort zone
- Lots of positive feedback from happy customers
- Extra contributors, including core maintainers
- Grow community via conferences and talks
- More learning material done by external people, even books and workshops
The bad parts of open-source
- Users are resistant to add an extra thing to their deps
- Users want predictable support and SLA
- with OSS comes responsibility, you don't want to break many production projects that rely on you
- Users constantly demand updates, without respect to holidays
- Doing conference talks is necessary for advertizing, but exhausting
- and you still need to work to earn money for living
- work/life balance suffers
Conclusion
Don't focus on development a software, but focus on development yourself
Programming languages do not make products successful, but the features they have.
Before doing a feature, we need to read and understand the code. But what if we could avoid it? Behavior programming!
Programming is about requesting, waiting and blocking events. It can be decomposed into b-threads that work with these events. User sees the eventual event flow produced by the combination of all b-threads in the app. Inserting a new b-thread will change the flow for user. When we want to add a new feature, we can do it via a new b-thread, we do not need to modify the exiting b-threads. block verb can help you to affect other b-threads. You do not even need to look into the implementation of existing b-threads, you just look at the final event log.
shows reference implementation in react
Conclusion: this approach may help to write code that is closer to the real world behavior of things.
People are different so your users are too. You cannot make UI decisions that fits all of them at once. A/B testing is also not enough, since there are more than just binary choices.
https://github.com/guess-js/guess – a library that predicts user actions based on Google Analytics data
Examples of adaptive UIs
- MS Office that selectively shows buttons on the toolbar based on popular user's actions
- GMail compose email interface with message auto-completion
We represent our application in a flow chart with transitions between states. We can use a state machine for implementation, for example: https://github.com/davidkpiano/xstate
Then we use analytics data to find more popular user transitions, that have a potential to be adaptive. We can provide a shortcut for a popular flow.
For example, feedback dialog
- Show question: how was your experience? good/bad
- Show prompt dialog: can you tell us why
- Adaptive system finds out that people who chose "good" do not leave any feedback at step 2.
- We adapt our interface to immediately close the dialog for them.
The example above it very contrived, it can be also used in more complex flows.
We can supply more data into our decision model for adaptive UI
- extra account data
- previous user steps
- machine learning
Behavior is a reaction to an action. Components are composed from simple atomic behaviors. You can also detach a behavior from component and make it UI-agnostic, so called "headless" behaviors.
You can represent headless behavior via a state chart. For example, you can represent a volume controller as a bunch of states and transtions "volume X%" -> "volume Y%".
State machines defined in JSON can be ported across frameworks (Vue, React) and platforms (Web, Mobile)
Language is a finite system of basic units that is used to be combined into larger units. Design is also a language with its primitives – typography, spacing, colors. This primitive are composed into a design system.
Example: German government websites vs Austrian. German - unified, Austrian – are not.
In web world the design system is enforced via components: first BEM and Bootstrap, now React/Vue/Angular components. Designers also have a concept of components, but it is separated and built on different tools, not HTML/CSS.
Why not to make write designers to write HTML/CSS? It is a relatively complex technology, that requires an extra time to learn in depth (how many designers know how to use flex-box?).
Using different tools designers sometimes create impossible (or expensive) to implement visuals. Often simple designs lead to complex code for developers, if there were built without regard on available web technologies.
The solution could be to use the same terms in web and design. For example when building a button the component should repeat design variants: <Button variant="primary" />
. When implementing a design, developers should not build HTML from scratch, but pick up reusable components, that already have correct mappings to the design primitives.
By creating primitive as components, you can unify the language of design and developers. There are also tools like react-sketchapp
that make your code-based components available in Sketch for designers.
http://varya.me/react-finland-2019/
There are differences in a way how different roles consume a design system
Devs
- Technical release notes
- API documentation
Designers
- Tone and voice of components
- Contribution guidelines (designers more likely contribute to a system)
Managers
- Strategy
- Roadmap
There are also living style guide approach, where components style guide is used as a development playground (React-styleguidist, Storybook). With these tools you are documenting and developing component at the same time. This way you will make sure that documentation is always in sync with components implementation.
There are different approaches
- Documentation comments with examples for CSS-utility classes
- Markdown description with code snippets for React components (new format MDX = Markdown + JSX)
There is a problem of maintaining design system, that development is lagging behind the design.
-
Involving designers into the work on the documentation website can reduce the lag.
-
Introducing visual regression tests early on the process should also help designers to understand the impact of their changes, reducing number of back-n-forth
-
The recommended choice for building a website for design system is Gatsby
Documentation website may contain not only doc pages, but also blog posts with various announcements. Storing website content in Git helps contributors (easy to fix a typo via PR) and with history tracking. You will be able to find the initial authors of a document via git log.
The implementation is markdown with sprinkles of React. (Not React with sprinkles of markdown like we do).
Even with design system there is still a room for custom components. Do not hesitate to build custom flow-charts that help customers to understand the design library better.
Dealing with changelog:
- hard to collect, easy to miss an important change
- automate it! Collect information
- write posts! people like reading about updates
- via sharing upcoming features and roadmap you can receive valuable early feedback.
- writing posts in git can be hard, but you can fetch them from CMS.
- it is good to have information about use-cases of your customers. Code-search, built-in metrics in components should help you to focus on more important things
- sharing this data library consumers is also good, they can find anomalies in data.
Automation in software development helps to reduce the time between code merge and actual release to customers. Automated tests save time on QA, that's why it is worth investing in automation.
We use Circle CI for implementing our build pipeline. We are confident about this choice, because the React Native itself uses it too.
Challenges:
- Different platforms for Android and iOS builds
- Secret keys for signing artifacts need special handling
- Signing process is also convoluted and is different for iOS and Android. The process was intended for manual use and is not really ready for automation.
- Version number auto-increment
- derive from build number
- based on the number of commits in the build branch
- Dev-build should have a special indication. It can be done via "icon badges" feature in React Native
- Deploying to store. It is being deployed into a special beta space first, not available to all customers
- Google PlayStore: internal test track
- Apple AppStore: test flight
Configuring CI/CD pipeline for React Native took 2 weeks. Setting up the same pipeline for a different product took only 2 days, thanks to all previous learnings
In the end we have build a CLI tool that generates the build config for a new project: https://www.npmjs.com/package/react-native-ci
Everything begins with a change in code. By the modern standards then you have a CI/CD pipeline that builds and deploys your code somewhere. You normally have tests, but it is hard to find what exactly needs to be tested. At the top of the testing pyramid there is "manual exploratory testing". The exploratory testing is important to verify that your unit tests actually test the real use-cases. It does not necessary has to be manual, you can also exploratory test a function via a quasi-unit-test.
In exploratory testing we start with reading and understanding requirements to make sure we know what is the expected behavior.
Approval testing: a function supplied with different values by a special tool and then we manually approve if the result is expected. This way we can generate test cases. The tool accepts some possible input values, and then build a matrix of all possible permutations.
How can we find all possible input values? We check code coverage to make sure that we have visited all code branches
This strategy is very efficient to write test coverage for legacy code. If the code was running for a long time, you probably do not want to change its behavior, you want tests that will make sure it works as before.
These tests are not necessary supposed to be committed as automation. They help you to understand corner cases of your product, that's why they are called exploratory. You can write more reliable automated tests have better input values based on input values, that you have found doing exploration. The real power is in pairing these approaches, exploration and automation.
https://www.matuzo.at/blog/12-tips-for-more-accessible-react-apps-slides-react-finland-2019/
1. Create a sound document outline
Use h1/h2/h3 tags correctly. It helps screen readers to quickly walk through reading only section headers.
An example of a good accessible React UI library: https://www.tenon-ui.info
Tota11y browser extension helps to check the document outline.
2. Hide the invisible content correctly
Sometimes you need to add an extra content that is visible only for screen readers, but not visually shown. Use special css to make it visually-hidden.
If you have a button without text, only icon, do not forget to add visually hidden text.
3. Use button if you need a button
Sometimes people do clickable divs because it is easier to apply custom styles. But it breaks users' interaction, keyboard navigation. Buttons are:
- Focusable by default
- Click via keyboard by default
- Semantically correct
4. Use fragments to avoid invalid HTML
React has a feature to return multiple elements from a component. No extra wrapping element needed:
function TableColumns() {
return (
<React.Fragment>
<td>Column one</td>
<td>Column two</td>
</React.Fragment>
);
}
5. Take care of focus management
Focus follows the dom order. When you open a modal, you need to move focus to the open modal programmatically.
6. Make notifications accessible to everyone
Screen readers do not read dynamically rendered content by default. Use role="alert"
to indicate that this content is a notification. (Why not aria-live
?)
7. Announce page changes
In SPA with client side routing dynamic page changes are not visible to screen readers. Also focus stays on the link after navigation change, but it is expected to focus the loaded content.
You can move focus manually after the route transition, or use @reach/router
where it comes out of the box.
8. Test your React code automatically
https://github.com/dequelabs/react-axe
It will help you to find and fix easy wins.
But still, do not forget to do manual testing. For example, try to use the new feature you are working on with keyboard only.
Even Javascript has a type system, it just works in runtime, which may be too late for some aspects. It is called dynamic type system. There are also other languages that have statict types that are checked in advance, normally at a compilation step.
Static typing pros:
- Errors are found earlier
- Fewer production errors
Dynamic typing props:
- No extra clutter
- Easy polymorphism, because everything can be an argument.
There is a myth about static typing that it magically finds all bugs. Not true. But there is still a value of adding a little bit of static typing into Javascript code.
Strategies:
- Flow: a checker that infers types from existing javascript code. Does not require any preliminary changes in JS. You can later add explicit type annotations though.
- Typescript: a separate language, superset of Javascript. It also has a feature of checking existing JS, that was added later. But it also brings new features to the language when you decide to go full Typescript.
Configuration: Flow has more options, but they are hard to manage, Typescript is simpler and has enough options.
Performance: Flow is usually faster, but there can be instabilities.
Community: Flow has smaller community.
Typing libraries: Flow has a smaller number of typings available.
The explanation is that Flow begun from React community where Typescript was initially positioned as a general-purpose language, for all the same use-cases as Javascript has.
How to get started with typescript?
- Best editor support is in VSCode. Sublime text and others do not have 1st class support.
- Linters: there are currently two: ESLint and TSLint (deprecated, but still alive).
- Types tend to lead to more object-oriented style code
Why not other languages compiled to Typescript?
There are nice alternatives, Elm or Reason. However they have an extra cost of learning a totally new paradigm whereas in Typescript you can still use already learned Javascript patterns.
There is always a dilemma of choosing the right library. Normally, there are several alternatives available.
- Pick the first one and hope for the best
- Do a research, collect data and have a bike-shedding meeting
- Make an architectural decision, based on several aspects
Parameters
-
Github stars
-
Active maintenance status: latest commits date, open PRs
-
Active community: how many people are using it already
-
Platforms: stackoverflow tag, slack chat with support.
-
npm downloads – use with caution, may be impacted by automated build tools.
-
google trends – does not reflect the real situation
-
Complexity. If too simple – isn't worth having a dependency, you can copy that. If too complex – hard to assess and estimate the risks.
-
Is it over-engineered? Check the source code to make sure that you are able to understand it. Check that it uses available browser APIs, as opposed to legacy workarounds.
-
Who is the author? Is there a big company or an individual contributor? Did they already build something else before?
-
Speed
-
API
-
Size
-
Bugs
-
Dependencies
-
Documentation
-
Time Constraints
A good library solves at least 80% of the problem
Remember: the choice is up to you. You are responsible for the choice after all.
https://trusting-euclid-602f4e.netlify.com/react-finland/
Gatsby is a static website generator.
some quotes of happy users on Twitter
- At early days of react there were: https://github.com/react-boilerplate/react-boilerplate or https://github.com/coryhouse/react-slingshot They are boilerplate project where you clone all the provided structure into your projects. It led to complex projects for simple things
- Now there are gatsby starters, but they are compact and limited. The point is to give the most minimal setup needed to get started.
Gatsby themes – reusable block of a Gatsby site. These blocks can be shared, extended and customized.
Under the hood Gatsby takes config, data, assets, plugins and components and produces a static website. Gatsby theme is also like a mini-website with its own config, data, assets, etc.
Themes examples:
- Blogs – you can install a theme that will produce you static blog section for your website
- Documentation
- E-Commerce – you can embed a shopping platform into your website by adding a theme.
- Search – can also be added a theme
- Navigation
- Design system
- Conference slides – https://github.com/gatsbyjs/mdx-deck-theme
For now they are hidden behind the __experimentalThemes
flag. We are currently finalizing the API and will publish it soon.
Demo site: https://react-finland-gatsby.netlify.com
- You can add multiple themes that will add new routes or change existing
- You can override React components used in themes via placing an override file into
src
folder.
Animations are helpful for smooth UIs. What makes a good animation good? Material design has good explanation about good motion design: https://material.io/design/motion/
Animation = the illusion of movement. The good animation give a good illusion of movement. A good understanding of how human eyes work would help to create good animation. Smooth pursuit – brain engages with the most prominent moving object to predict where the focus should follow.
When something moves it has a direction that can be represented as a vector. It animation is chaotic, our brain cannot process every item and its direction. It will try to find an average direction of moving items. It is called global movement perception.
It is reflected in material design guidelines as a recommendation to have a single global animation as opposed to individual elements moving independently.
ReactMorph – toolkit for easy creation of animation in React.
import { useMorph } from "react-morph";
const morph = useMorph();
if (toggle) {
return <img {...morph} src="img-a.png" />;
} else {
return <img {...morph} src="img-b.png" />;
}
The library creates copies of elements and then build a transition using the FLIP technique:
F - first, initial state L - last, final state I - invert the frames sequence P - play
Using react-morph you can build a transition between any two react elements.
For more complex examples you can build compound transitions
import { useMorphKeys } from 'react-morph';
const morphs = useMorphKeys(["title", "content", "image"]);
<div>
<h1 {...morphs.title} />
<img {...morphs.image} />
<p {...morphs.content} />
</div>;
This should help to make sure that the relevant elements will transit correctly to their final states.
https://github.com/brunnolou/react-morph
- jQuery appeared as a solution to make cross-browser code easy. It used to be popular when the differences between browsers were too big.
- Backbone was awesome at some point, because it introduced the concept of data models for the first time in frontend.
- WebComponents were rock and promising in 2013, but they are still not there and not popular
- AngularJS was my next stop, that was ok, but then I found...
- React! But it had limited capabilities and was supposed to work with a separate FLUX library for state management.
- Redux was way simper comparing to other FLUX solution. But eventually we found the overhead of boilerplate there.
Conclusion: we need to judge technologies with the context of time.
And we also need to understand the most up to date solution available at the market. Maybe, it is react hooks? But it is not for sure.
How can we verify the idea now? You can check for different opinions in internet. For example, in this Twitter thread people posted a lot of valuable feedback.
Should I use hooks for my state management of context?
Let's see on an example – chat app. It contains navigation bar, contacts list, chat view.
With the initial context implementation your whole application will be re-rendered when user receives a message (as opposed to only the chat window). There is no built-in way to stop an unnecessary update.
We can divide contexts in parts: HistoryContext
, FriendsContext
, UserContext
. This should solve re-rendering issues.
But it still can become complicated: what if we want to show online status in the chat history. Online status comes from FriendContext
, so chat history will be re-rendered when a friend changes a status, even if it not the currently selected chat.
You can fight with it using React.memo()
to prevent elements from updating. The issue is that it breaks the separation of concerns. When you are working on the friends list, you need to consider, that there is an optimization in the chat history.
The solution can be found outside of React. For instance, Redux is still a thing, that may help you organize your code better and still performant. You can also look for a more simple solution, including writing your own – but keep it simple.
Think a few steps ahead when you are switching your technology
Imagine what future features can be added into your app. Will your architecture be ready for it?