Today I Learned

23 posts by arkadiuszmachalica

Placement of startWith operator in rxjs is important

It is important where you put startWith operator in rxjs pipe, as it can be then subjected to following transformations.

In the application I had

from(axios.get(apiUrl)).pipe(
  map(normalizeResponse),
  map(({ data: { imageUrl } }) => loadImageSuccess({ apiUrl, imageUrl })),
  startWith(loadImageStart({ apiUrl }))
)

If the startWith would be higher in the pipe stack, it would be subjected to normalizeResponse and loadImageSuccess .

HERE is codesandbox illustrating this behavior.

How to find code making the browser freeze

When for some reason the application causes the browser to freeze, it is problematic to find the root cause because the devtools also stop responding.

Here’s one way to quickly find the issue.

  • Open the devtools on Sources tab
  • Enter the page that causes the browser to freeze
  • Quickly click “Pause script execution”

Likely the application will be stopped during execution of the problematic script. Now by using of stepping and checking callstack you can find the problematic piece of code.

Single command to rebase branch on updated master

Having branch protection enabled in github prevents merging PR’s if the branch is out of date with target branch. Imgur

In github UI is a button to update branch, but it creates additional merge commit. To not have additional merge commit rebase the branch yourself.

It requires a few steps:

  • checkout master
  • pull master branch
  • checkout to your branch
  • rebase your branch on the new master
  • push with force

Here is the command to make all that automatically

git checkout master & git pull & git checkout - && git rebase master && git push --force-with-lease

Adding alias makes it easier to remember.

alias grbm="git checkout master & git pull & git checkout - && git rebase master && git push --force-with-lease"

React app bundle optimalization

As with any performance improvements, it’s important to have the tooling to evaluate if any changes provide value.

Tooling

In case of React apps setup with CRA it is source-map-explorer non CRA users can use other tools like webpack-bundle-analyzer

browser devtools with network throttling Imgur

Step 1

Decide what’s the lowest network speed that need to work relatively fast. (I have picked fast 3G)

Step 2

Build the application as for production environment npm run build and then serve -s build -l 3002 in CRA or open the deployed app.

Step 3

Throttle the network and force load all resources without using cache CMD+Shift+R. If the application loads relatively fast, there might not be the need to split the code at all.

Step 4

Analyze generated bundle using source-map-explorer or webpack-bundle-analyzer (non CRA). Spot biggest/rarely used elements. In my case it was:

  • TinyMCE used in only one page
  • Lodash being included 3 times (lodash.js lodash.min.js lodash-es)
  • React-Player used only in a few pages
  • Translations (en and pl)
  • Constants contained some not needed data

Step 5

Tackle the biggest issues and evaluate the results (step 4)

  • Use React.lazy and Suspense to lazy load parts of the application
  • When doing manual webpack code splitting remember about defining shared dependencies
  • Check for babel transforms eg. babel-plugin-lodash
  • Some modules can be aliased lodash: 'lodash-es', so they are included only once.
  • Some packages offer optimization themselves eg. import ReactPlayer from 'react-player/lazy' lazy loading players for different video hostings.
  • Load translation files lazily using eg. i18next-xhr-backend

Details depend on every specific case.

Step 6

If tackling the biggest elements is not enough, continue splitting the app per route or group of routes accessible for different users. eg. split admin routes, regular user routes and not logged in guests routes.

Profit!

Unless your application is gigantic, following all steps should be enough. In my case handling initial issues found in step 4 was enough.

Tips for implementing RBAC in React

Implementing RBAC in React (Role-Based Access Control) means you will need to add logic in multiple components to check if user has the correct role to display part of the UI.

Here is how using HOC’s enables clean code.

step 1

The assumption is store user roles are stored in context or redux.

Create withRole HOC

export default const withRole = (roles) => (Component) => (props) => {
 const {userRoles} = useContext(userRoles)
 if (userRoles.match(roles)) {
  return <Component {props} />
 }
 return null
} 

step 2

This HOC can be easily applied on any component.

import Button from "./Button"
import Something from "./Something"

const RestrictedButton = withRole(['admin', 'manager'])(Button)
const RestrictedSomething = withRole(['editor'])(Something)

const SomeComponent = () => {
 return (
  <div>
    <RestrictedButton>Only admin and manager sees me</RestrictedButton>. 
    <RestrictedSomething someProp={someProp}/>
  </div>
)}

step 3

It’s useful to create helpers with already applied roles.

export const withAdminRole = withRole(['admin'])
export const withEditorRole = withRole(['editor'])
export const withManagmentRole = withRole(['admin', 'editor'])

and use them like that:

const RestrictedButton = withManagmentRole(Button)
const RestrictedSomething = withEditorRole(Something)

important

HOC’s should always be created outside of the components, to prevent them being recreated on every render.

const MyComponent = () => {
  // VERY BAD
  const EditorButton = withRole('editor')(Button)
  const AdminButton = withAdminRole(Button)
  return (
    <>
      <EditorButton />
      <AdminButton />
    </>
  )
}
// GOOD
const EditorButton = withRole('editor')(Button)
const AdminButton = withAdminRole(Button)
const MyComponent = () => {
  return (
    <>
      <EditorButton />
      <AdminButton />
    </>
  )
}

Make sure translation files have the same keys

It’s relatively easy to add/remove translation and forgot to update files for other languages.

Here’s how make sure your translation files always have the same keys.

If the translations are in .json

.json translation files can be linted using eslint-plugin-i18n-json. It has documentation and examples.

If the translations are in .js

The simplest option is to add jest test.

import en from '.translations/en.js'
import pl from '.translations/pl.js'
import de from '.translations/de.js'

const enKeys = Object.keys(en)

describe('Translations', () => {
  test('PL translation have matching keys', async () => {
    expect(enKeys).toEqual(Object.keys(pl))
  })

  test('DE translation have matching keys', async () => {
    expect(enKeys).toEqual(Object.keys(de))
  })
})

Imgur

Profit

Now it will be impossible to desynchronize translation files if the PR merging is blocked by linting or tests failure.

Configure graphql syntax highlight in IntelliJ IDE's

To enable syntax highlight in graphql query/mutation definition in IntelliJ IDE’s follow few steps.

Initially gql query is displayed as regular string. Imgur

Step 1

In editor preferences > plugins > marketplace find JS GraphQL plugin Imgur

Step 2

Then you need to configure .graphqlconfig by selecting: new > GraphQL Configuration File in project root file Imgur schemaPath property should point to schema.graphql file in your project

Step 3

If you configure the application correctly you should see the schema discovered and “No errors found” Imgur

End result

Now you should see syntax highlight, suggestions and validation based on graphql schema. Imgur

Additional info

If you have problems with the setup checkout the plugin documentation

Monitor Redux store on production

With redux devtools disabled on production it’s problematic but possible to monitor dispatched actions and state changes. Here’s how it can be done.

Step 1

You need to find this code in your Sources. imgur

Even if they are minified, you can search for text 'Reducers may not dispatch actions.' which is close to the code we are interested in. Here is how it look in my case. Imgur

Step 2

Now you only need to add logpoint(s) to display action and state. Imgur

Imgur STATE BEFORE

{
  actionType: e.action.type, 
  action: e.action, 
  stateBeforeAction: s.computedStates[s.computedStates.length - 1].state
}

STATE AFTER

{
  actionType: e.action.type,
  action: e.action, 
  stateAfterAction: s.computedStates[s.computedStates.length - 1].state
}

IMPORTANT

In your case the code might be minified differently so you might have different letters instead of “e” and “s”.

Now then you click through the app (or reload the page) you should see in the console the feed of actions and state. Imgur

Redux devtools config with redux-toolkit

When configuring redux store you have to manually enable devtools. Often they are only enabled in development environment.

Redux toolkit by default enables devtools on all environments. It is easy to overlook that and forget disabling devtools on production.

However some say having devtools enabled in production is not an issue.

  const store = configureStore({
    reducer: rootReducer,
    middleware: getDefaultMiddleware(),
    // do not forget this
    devTools: process.env.NODE_ENV !== 'production',
  })

Redux suggested store structure.

Redux itself is not opinionated about how we design the store structure, but having good structure allows us to write simpler, less bug prone code. Based on redux style guide, state normalization and own experiences I would like to suggest some data shape for single resource. Depending on the specific needs of the project the structure should be adjusted, but it should be sufficient for standard use-cases.

const resource = {
  // normalized data for resource
  byId: {
    id1: {id: "id1", ...rest_of_item1_data},
    id2: {id: "id2", ...rest_of_item2_data},
  },

  // default list of items (key 'list' is just example)
  list: {
    // ids of users in the list
    // ids could be sorted, filtered
    // so there is no need to calculate them in selectors
    ids: ["userId1", "userId2"],
    // status of list operation
    // can be idle, loading, loaded, updating, etc.
    status: 'idle',
    // error data for the whole list operations (when appears)
    error: false,
    // metadata for the whole list
    // loadedAt and state are just example properties
    meta: {
      loadedAt: 7305798374957,
      stale: false,
    }
  },

  // when there is a need to manage another list
  someOtherList: {
    ids: ["userId4", "userId5"],
    status: 'loading',
    error: false,
    meta: {}
  },

  // default singular resource (key 'single' is just example)
  // for CRUD operations on single resource
  single: {
    id: 'id1',
    status: 'idle',
    error: false,
    meta: {}
  },

  // when there is a need to manage another singular resource
  anotherSingle: {
    id: 'id1',
    status: 'idle',
    error: false,
    meta: {}
  }
}

Use debugger in jest tests

Normally debugger does not stop test execution and allow us to check the application when running jest tests. However we can do it by passing --inspect flag to node and using chrome devtools for node.

Step 1

When using CRA add to your package.json scripts:

"test:debug": "react-scripts --inspect test --runInBand --no-cache",

When not using CRA add to your package.json scripts:

"test:debug": node --inspect node_modules/.bin/jest --runInBand

Step 2

In Chrome open chrome://inspect and click Open dedicated DevTools for Node you should see new inspector window open. (do not close it)

Step 3

Add debugger keyword in the tested code/component.

Step 4

Run npm run test:debug

And there it is. In a moment in the open inspector window, you should see test executions stopped at your breakpoint.


You could also use debugger in terminal or your IDE. More info in the article and node docs

React Redux code-smells: Storing props in state

With only a few exceptions storing props in state is anti-pattern, because:

  • storing the same/similar data is not optimal usage of memory
  • keeping two sources of data in sync is brittle and bug prone
  • often there is a cleaner way of implement the feature

bad case: keeping transformed data

export function MyComponent({ dateString }) {
  // DON'T
  const [timestamp, setTimestamp] = useState(() => new Date(dateString).getTime());

  return <div>Some UI do not mind me</div>;
}

if timestamp is not changed by MyComponent, it could be implemented simply by useMemo

export function MyComponent({ dateString }) {
  // DO
  const timestamp = useMemo(() => new Date(dateString).getTime(), [dateString])

  return <div>Some UI do not mind me</div>;
}

if timestamp is changed by MyComponent, maybe parent component should handle it

export function MyComponent({ timestamp, setTimestamp }) {
  return <div>Some UI do not mind me</div>;
}

That way we’ll not have duplication of data

good case: props provide only initial/defaultValue and we do not care if it is updated

If we receive defaults from parent, then they can be safely used by the children in state. That way we could also implement state reset.

export function MyComponent({ defaultTimestamp }) {
  // OK
  const [timestamp, setTimestamp] = useState(() => defaultTimestamp);
  const resetTimestamp = () => setTimestamp(defaultTimestamp)

  return <div>Some UI do not mind me</div>;
}

React Redux code-smells: Data preparation in component

If you have Redux in React project you should not handle data preparation in components. Why? Take a look on example below.

export function MyComponent({ userId, uniqueness }) {
  const user = useSelector((state) => getUser(state, userId));
  const selectedFilter = useSelector(getFilter);
  const selectedSorting = useSelector(getSorting);

  // in function components we can use useMemo for optimization
  // but in class components we would not be able to optimize this properly
  const answers = useMemo(() => {
    if (!user) return [];
    const uniqAnswers = uniqBy(user.answers, uniqueness);
    const filteredAnswers = filter(uniqAnswers, selectedFilter);
    return sortBy(filteredAnswers, selectedSorting);
  }, [user, selectedFilter, selectedSorting]);

  if (!user) return "Loading";

  return (
    <div>
      {user.name}
      {answers.map((answer) => (
        <div key={answer.id}>{answer.description}</div>
      ))}
    </div>
  );
}

If we ever need to display answers, we would need to copy-paste the logic, or extract it to the reusable hook which still would not be the perfect solution.

Wherever possible preparation of data should be handled by the selector. Even if the data comes from different reducers, or component state/props.

That way:

  • Code can be reused in other places
  • Component has less code
  • Reselect selectors have better performance by default
const getUserWithSortedAnswers = createSelector(
  getUser,
  getFilter,
  getSorting,
  (_, _2, uniqueness) => uniqueness,
  (user, selectedFilter, selectedSorting, uniqueness) => {
    if (!user) return [];
    const uniqAnswers = uniqBy(user.answers, uniqueness);
    const filteredAnswers = filter(uniqAnswers, selectedFilter);
    const sortedAnswers = sortBy(filteredAnswers, selectedSorting);

    // in this example sorted answers are modified in user object
    // but this can be done as separate selector eg. getSortedAnswers
    return { ...user, answers: sortedAnswers };
  }
);

export function MyComponent({ userId, uniqueness }) {
  // selector can receive component data from state or props (uniqueness)
  const user = useSelector((state) => 
     getUserWithSortedAnswers(state, userId, uniqueness)
  );

  if (!user) return "Loading";

  return (
    <div>
      {user.name}
      {user.answers.map((answer) => (
        <div key={answer.id}>{answer.description}</div>
      ))}
    </div>
  );
}

If you do not have redux in your React project you can do data preparation in reusable hooks or in wrapper components. As it also provides some reusability and decreases component size.

React code-smells: Passing unnecessary props

Component receiving many props might suggest something is wrong.

In this case MyComponent receives many props used only to calculate another values.

function MyComponent({ value, format, isVisible, color, height, width }) {
  const displayValue = useMemo(() => {
    if (!isVisible) return "";
    if (!value) return "";

    value.transform(format);
  }, [isVisible, value, format]);

  return (
    <div style={{ color, height, width }}>Some content and {displayValue}</div>
  );
}

function MyParentComponent() {
  // value, format, isVisible, color, height, width come from state or somewhere else

  return (
    <div>
      <h2>Here will be my component</h2>
      <MyComponent
        value={value}
        format={format}
        isVisible={isVisible}
        color={color}
        height={height}
        width={width}
      />
    </div>
  );
}

In such cases we could simplify the component by calculating the values in parent or additional wrapper component and pass only that.

function MyComponent({ style, displayValue }) {
  return <div style={style}>Some content and {displayValue}</div>;
}

function MyParentComponent() {
  // value, format, isVisible, color, height, width come from state or somewhere else

  const style = useMemo(
    () => ({ color, height, width }), 
    [color, height, width]
  );

  const displayValue = useMemo(() => {
    if (!isVisible) return "";
    if (!value) return "";

    value.transform(format);
  }, [isVisible, value, format]);

  return (
    <div>
      Here will be my component
      <MyComponent style={style} displayValue={displayValue} />
    </div>
  );
}

React code-smells: Big form components

Big components suggest that there might be too much going on inside. However sometimes it is hard to remove/extract the logic, as it really belongs there. That’s the case often with form components. In such cases we can extract related groups of logic into hooks.

function QuestionForm({ defaultQuestion }) {
  const [questionData, setQuestionData] = useState(defaultQuestion);

  // ====== stuff related to image refresh ======

  const [imageTimer, setImageTimer] = useState({});

  useEffect(
    function refreshFrontendSignedImages() {
      // logic for refreshing
    },
    []
  );

  useEffect(function setupRefreshForInitialImages() {
    // logic to refresh setup
  }, []);

  // ====== stuff related to submitting ======

  const dispatch = useDispatch();

  const [questionValidationErrors, setQuestionValidationErrors] = useState([]);

  const cleanupSaveActions = useCallback(() => {
    // cleanup handler logic
  }, []);

  const onSubmit = useCallback(() => {
    // submit handler logic
  }, []);

  // ====== stuff related to handling changes ======

  const handleTitleChange = useCallback(({ target }) => {
    //  handle title change
  }, []);

  const handleChoicesUpdate = useCallback((choice, index) => {
    // handle choices update
  }, []);

  const handleTopicsUpdate = useCallback((topicIds) => {
    // handle topic update
  }, []);

  return <form>Some Form UI</form>;
}

After extracting imageRefresh, submitHandlers and changeHandlers logic to hooks we have certain benefits:

  • more readable component without dozens of functions flying around
  • functions are grouped by their utility, so it’s easier to reason about them as we don’t have submit functions surrounded by change handlers
  • whenever you need to work on part of the functionality (like image refresh) you have all functions in one place
  • hooks can be reused (but this is not the main point of this refactoring)
function QuestionForm({ defaultQuestion }) {
  const [questionData, setQuestionData] = useState(defaultQuestion);

  const { setImageTimer } = useSignedImagesRefresh({
    setQuestionData,
    defaultQuestion,
  });

  const {
    onSubmit,
    onSubmitAndCreateAnother,
    validationErrors,
  } = useQuestionFormSubmitHandlers({ questionData });

  const {
    handleTitleChange,
    handleChoicesUpdate,
    handleTopicsUpdate,
  } = useQuestionFormChangeHandlers({ setQuestionData });

  return <form>Some Form UI</form>;
}

React code-smells: Mapping UI having methods for items

Whenever mapped elements have their own functions, they should be extracted to their own components.

const UsersList = () => {
  const [users, setUsers] = useState([])
  const fetchUsers = () => { ...fetchingLogic }

  const getHeader = (item, index) =>
    item.isAdmin 
      ? `${index + 100} :: ${item.superName} :: ${item.rank}` 
      : `${index} :: ${item.name} :: ${item.age}`

  const getImage = (item) =>
   item.url ? `${item.url}?token=${item.authToken}` : 'http://default.img.com'

  return (
    <div>
      {list.map((user, index) => (
        <div key={user.id}>
          <h3>{getHeader(user, index)}</h3>
          <p>Some introduction</p>
          {getImage(user)}
        </div>
      ))}
    </div>
  )
}

That way:

  • Item methods (getImage & getHeader) do not pollute UsersList component having own methods (fetchUsers)
  • There is no need to pass params to item methods
const User = ({ user, index }) => {
    const getHeader = () =>
      user.isAdmin
        ? `${index + 100} :: ${item.superName} :: ${item.rank}`
        : `${index} :: ${user.name} :: ${user.age}`
  
    const getImage = () =>
      user.url ? `${user.url}?token=${user.authToken}` : 'http://default.img.com'
  
    return (
      <div>
        <h3>{getHeader()}</h3>
        <p>Some introduction</p>
        {getImage()}
      </div>
    )
  }
  
  const UsersList = ({ list }) => {
    const [users, setUsers] = useState([])
    const fetchUsers = () => { ...fetchingLogic }

    return (
      <div>
        {users.map((user, index) =>
          <User user={user} index={index} key={user.id}/>
        }
      </div>
    )
  }

React Optimization of lists in big components

Big or often rerendering component which displays list of elements, can be optimized by extracting the list to separate component and using React.memo.

In example below it provided 40x speed improvement. YMMV of course.

In the examples I have provided different stages and mistakes this optimization. They also help understand why are useMemo and useCallback hooks useful.

react optimalization examples

Using Light Sensor in Google Chrome

Light Sensor needs to be enabled in Chrome: go to: chrome://flags/#enable-generic-sensor-extra-classes and enable it.

Then you will be able to get sensor readings

const sensor = new AmbientLightSensor();
sensor.start();
sensor.addEventListener('reading', () => {
  console.log(sensor.illuminance);
});

There are also activate and error events on the sensor object that can be listened to.

Full article: https://blog.arnellebalane.com/using-the-ambient-light-sensor-api-to-add-brightness-sensitive-dark-mode-to-my-website-82223e754630