traccar / traccar-web

Traccar GPS Tracking System
https://www.traccar.org
Apache License 2.0
842 stars 1.14k forks source link

Proposal(modern): Using Modern React Tools. #770

Closed dt-ap closed 2 years ago

dt-ap commented 4 years ago

@tananaev I propose two different modern react tools for this project.

Redux Toolkit

With this library there will be less boilerplate when creating redux actions/reducer/actions-creator. Furthermore, the library is already packaged with some middlewares:

Hooks

This is a personal preference. Using React component class is okay too, I guess. I like hooks more than using Render Props or Higher Order Component.

If you interested, I will write two different PR for each of these tools and you could make the decision to use it or not.

tananaev commented 4 years ago

Redux Tookit seems like a good idea. Removed boilerplate. Feel free to send a pull request.

Hooks not convinced yet. If you can give an example where it solves a problem. I guess it might make sense for some really small components where we don't want to write a class.

RodolfoSilva commented 4 years ago

@tananaev for example: Today we have a redux with positions, devices, selectedDevices.... A lot of boilerplate to use that. When you can use a structure like redux but more readable and isolated.

An example: It is if we want rewrite the devices state to hooks we can use something like this

import React from 'react';

const DevicesContext = React.createContext();

const reducer = (state, action) => {
  switch(action.type) {
    case 'UPDATE_DEVICES':
      return {
        ...state,
        ...action.payload,
      };
    default:
      throw new Error(`Unknown action ${action.type}`);
  }
};

export function DevicesProvider(props: DeviceProviderProps) {
  const { children } = props;

  const [state, dispatch] = React.useReducer(reducer, {});

  const updateDevices = React.useCallback((devices) => dispatch({ action: 'UPDATE_DEVICES', payload: devices }), [dispatch]);

  return <DevicesContext.Provider value={{ devices: state, updateDevices }}>{children}</DevicesContext.Provider>
}

export function useDevices() {
  const context = React.useContext(DevicesContext);

  if (context === undefined) {
    throw new Error(`useDevices must be used within a DevicesContext`)
  }

  return context
}

When we need to use anything related with devices information we just need import and use the useDevices;

import React from 'react';
import { useDevices } from './context-location';

export default function DeviceList() {
  const { devices } = useDevices();

  return (
    <ul>
      {Object.values(devices).map(({ deviceId }) => (<li key={deviceId}>{deviceId}</li>))}
    </ul>
  );
}
import React from 'react';
import { useDevices } from './context-location';

export default function DeviceForm() {
  const { updateDevices } = useDevices();

  const handleSubmit = React.useCallback((event) => {
    const values = event.target.values;
    updateDevices({ [values.deviceId]: values.deviceId });
  }, [updateDevices]);

  return (
    <form onSubmit={handleSubmit}>
      ...
    </form>
  );
}

I believe use hooks is easy to share resource and more readble than redux way.

dt-ap commented 4 years ago

@RodolfoSilva You do not need to use React Context for that though. Not all boilerplate needed to be abstracted away.

And you could do this with HOC too. But I agree, I like writing Hooks more for sharing functionality between components

dt-ap commented 4 years ago

@tananaev For hooks example. you could see my branch's MainMap.js. The component variable is on line 169

tananaev commented 4 years ago

Can you please explain how it's better than what we currently have? What are the benefits?

dt-ap commented 4 years ago

It's only an example of what you can do with Hooks. It's not really better, it's another way of doing things.

What I like about Hooks, is you localize logic in one place (a function/hooks). One example I could think of is cancelable data fetching.

If a component starting a fetch operation, is dismounted, then the fetch operation should be aborted. Using class component, the logic will be separated between componentDidMount and componentWillUnmount. Imagine if there are many startup and cleanup operation, then these two operation will be full of many different logics.

When using Hooks, you can create a custom Hooks Function with useEffect. Every operations will be contained inside each of those functions.

tananaev commented 4 years ago

How would the operation be cancelled automatically with Hooks?

dt-ap commented 4 years ago

No, its not cancelled automatically. Usually if you want to do a startup and cleanup operations you do it like this: useEffect(() => { /* Data Fetching Startup Operation */ return () => { /* Data Fetching Cleanup Operation */ } }, []);

tananaev commented 4 years ago

I see. I think for now we'll stick with standard components, but we can reassess it later if we see a good use case for Hooks.

dt-ap commented 4 years ago

Yeah. It's not really that important. I suggest you watch this video if you have the time. They explain it better

tananaev commented 4 years ago

Looks like Hooks is the official recommended way, so I think we should migrate. Feel free to send a pull request if anyone has time to migrate existing components.

RodolfoSilva commented 4 years ago

Ok, I can help, maybe we can describe a roadmap and tasks. Whats do you think @dt-ap ?

tananaev commented 2 years ago

We're already using both.